I have a slight problem controlling the cursor position in a Bash terminal window. I have a function ask a question and then wait for an answer which is either 'y' or 'n' or a carriage return. Whenever the user enters anything else it just erases the answer and waits for the next one. However, the script behaves differently when the cursor is on the bottom line of a window as compared to any other line.
#!/bin/bash
# -----
# function: askYesOrNo
# purpose : ask question passed as $1
# return : 0 if answer was y or Y,
# 1 otherwise
# -----
function askYesOrNo {
printf "\e[1;31;47m$1 [Y|n]\e[0m " # print $1 in color
printf "\e[s" # save cursor x-pos
while true ; do
read answer
if [ -z "$answer" ] ; then
return 0
elif [ "$(tr -d "NnYy" <<< $answer)" != "$answer" ] ; then
break
fi
# restore cursor x-pos, erase rest of line
# printf "\e[u\e[K"
# restore cursor x-pos, erase rest of line
printf "\e[u\e[1A\e[K"
done
if [ "${answer^}" == "Y" ]; then
return 0
fi
return 1
}
for (( i = 100, max = 110; i <= max; i++ )) ; do
printf " %d\n" $i
if [ $i -eq $max ] ; then
if askYesOrNo "Do you wish to continue?" ; then
(( max += 10 ))
fi
fi
done
The for-loop is the 'productive' part. It just puts out numbers in a column. Every 10 numbers it asks if the user wants to continue and uses the function askYesOrNo for that purpose.
The problem lies with the two lines in the function askYesOrNo (at the end of the while-loop) and when the user enters an answer that is not Y, y, N, n or Enter. The first line (now commented out) works fine when the cursor is not in the bottom line of the window. But if it is then the window scrolls up and the next answer appears in the next line.
The second version moves the cursor one line up and that's fine with the cursor at the bottom of the window but not if the question is asked elsewhere.
Some terminals don't always follow terminal escape codes to the _letter_.
You could try and force the line prompt, force the cursor to the correct position and force clearing after the line prompt.
An example of the cursor forcing, it is just as easy to manipulate this to write the prompt and clear the line
Cool manual page but no... the page still scrolled up and I ended up in the next line, rather than restarting in the same line.
I think I have to check whether the cursor is in the last line of the window or not and do the reverse line feed only in that case. Together with an answer that I found elsewhere on this forum I came up with this version of the function which apparently works now:
function askYesOrNo {
printf "\e[1;31;47m$1 [Y|n]\e[0m " # print $1 in color
printf "\e[s" # save cursor x-pos
while true ; do
read answer
if [ -z "$answer" ] ; then
return 0
elif [ "$(tr -d "NnYy" <<< $answer)" != "$answer" ] ; then
break
fi
# How many lines in this window?
LINES=$(tput lines)
# find cursor y-position ( line number )
printf "\e[6n" ; read -sd R POS
CURPOS=${POS#*[}; CUR_Y=${CURPOS%;*}
if [ "$CUR_Y" -eq "$LINES" ] ; then
# printf "\e[u\e[1A\e[K"
printf "\e[u\eM\e[K"
else
printf "\e[u\e[K"
fi
done
if [ "${answer^}" == "Y" ]; then
return 0
fi
return 1
}
It still duplicates the question lines when the cursor is in the middle of the screen.
What do the lines printf "\e7\e[$LINES;120r\e8" and printf "\e7\e[1;${LINES}r\e8" accomplish? I suppose you set the value of LINES to the number of lines of the window - let's say 20. Then the first snippet would say "\e7\e[20;120r\e8" and the second printf "\e7\e[1;20\e8" and r means 'set top and bottom lines of window.'
Not with my linux lxterminal . But I admit it might benefit from some more tweaking.
man bash :
The 120 is just an arbitrary value way beyond the lower screen boundary. The construct sets the scroll region from last line seen to somewhere way down...
For some reason LINES is not set in a non-interactive shell - even if checkwinsize is on. (I'm using Bash 5.0 but noticed that back in Bash 4.4, Kali Linux (which is Debian 9) and Raspian, too:
Take a look at the two command below.
The first will give you your terminal size in lines, columns.
IF your terminal can do it, (xterm for example), the second will auto adjust it for you...
Interesting. What do the outer parentheses around $(stty size) accomplish? I know they are used to force execution in a subshell. But what are they doing here?
I would have gone for
lines=$(stty size)
lines=${lines% *}
The printf statement doesn't seem to accomplish anything over here. What is it supposed to do? Is that documented somewhere?
In terms of documentation I was looking at this and this.
I did quote early on in this thread that some terminals do not respond correctly to some terminal escape codes. Some of those escape codes will not work at all.
So in the first part the outside parentheses create an array in advanced shells like bash so therefore longhand:
Last login: Tue Feb 4 16:17:10 on ttys000
AMIGA:amiga~> term_size=($( stty size ))
AMIGA:amiga~>
AMIGA:amiga~> printf "%b\n" "${term_size[0]}"
24
AMIGA:amiga~>
AMIGA:amiga~> printf "%b\n" "${term_size[1]}"
80
AMIGA:amiga~> _
As for the second 'printf' line, changing the values 24 and 80 to say 30 and 120 will expand the terminal size on certain terminals, (xterm as an exmaple), to that size for the duration of that terminal session. Of course calling it again with 24 and 80 restores it back to the original.
IF and a big if, it doesn't work then many of those terminal commands in the URLs won't work either.
Array... right. I wasn't thinking straight. Was early in the morning then.
Yes, probably quite a few of these escape codes won't work everywhere. Fortunately I don't need to resize the terminal. All I want is to keep the cursor in place until an acceptable answer arrives.
What about those code in man console_codes? Can't those be used to program in a reasonably safe / portable way in Bash?
100
101
102
103
104
105
106
107
108
109
110
Do you wish to continue? [Y|n] z
When a user enters an answer other than Y, y, N, n or <Enter> - for example 'z' above - then the cursor is supposed to stay where it is until an acceptable answer comes.
That has to work everywhere on the screen.
The problem I had was that the carriage return moved the cursor to the next line, one line below the question. I first fixed it for everywhere except the last line of the window and that's when I posted my original question.
This version works for me now:
#!/bin/bash
function askYesOrNo {
printf "\e[1;31;47m$1 [Y|n]\e[0m " # print $1 in color
printf "\e[s" # save cursor x-pos
while true ; do
read answer
if [ -z "$answer" ] ; then
return 0
elif [ "$(tr -d "NnYy" <<< $answer)" != "$answer" ] ; then
break
fi
# How many lines in this window?
LINES=$(tput lines)
# find cursor y-position ( line number )
printf "\e[6n" ; read -sd R POS
CURPOS=${POS#*[}; CUR_Y=${CURPOS%;*}
if [ "$CUR_Y" -eq "$LINES" ] ; then
# printf "\e[u\e[1A\e[K"
printf "\e[u\eM\e[K"
else
printf "\e[u\e[K"
fi
done
if [ "${answer^}" == "Y" ]; then
return 0
fi
return 1
}
for (( i = 100, max = 110; i <= max; i++ )) ; do
printf " %d\n" $i
if [ $i -eq $max ] ; then
if askYesOrNo "Do you wish to continue?" ; then
(( max += 10 ))
fi
fi
done
Could be condensed a bit but this is easier to read.
It works here. Does it work on your system?
I think the carriage return at the end of the input is scrolling up the terminal. How about prompting for a single Y or N response (no need to CR):
#!/bin/bash
# -----
# function: askYesOrNo
# purpose : ask question passed as $1
# return : 0 if answer was y or Y,
# 1 otherwise
# -----
function askYesOrNo {
printf "\e[s" # save cursor x-pos
while true ; do
printf "\e[1;31;47m$1 [Y|n]\e[0m " # print $1 in color
read -n 1 answer
case $answer in
Y|y|N|n) break;;
esac
# restore cursor x-pos
printf "\e[u"
done
# restore cursor x-pos, erase rest of line
printf "\e[u\e[K"
# set return status
[ "${answer^}" == "Y" ]
}
for (( i = 100, max = 110; i <= max; i++ )) ; do
printf " %d\n" $i
if [ $i -eq $max ] ; then
if askYesOrNo "Do you wish to continue?" ; then
(( max += 10 ))
fi
fi
done