I am playing with a script that, among other things, list a selection-list. As in:
1) Item 1 # (highlighted) 2) Item 2 3) Item 3 # (selected) 4) Item 4
- When user press
down-arrow
next items is highlighted - When user press
up-arrow
previous items is highlighted - etc.
- When user press
tab
item is selected - When user press
shift+tab
all items are selected / deselected - When user press
ctrl+a
all items are selected - …
This works fine as of current use, which is my personal use where input is filtered by my own setup.
Question is how to make this reliable across various terminals.
I use a somewhat hackish solution to read input:
while read -rsn1 k # Read one key (first byte in key press)
do
case "$k" in
[[:graph:]])
# Normal input handling
;;
$'\x09') # TAB
# Routine for selecting current item
;;
$'\x7f') # Back-Space
# Routine for back-space
;;
$'\x01') # Ctrl+A
# Routine for ctrl+a
;;
...
$'\x1b') # ESC
read -rsn1 k
[ "$k" == "" ] && return # Esc-Key
[ "$k" == "[" ] && read -rsn1 k
[ "$k" == "O" ] && read -rsn1 k
case "$k" in
A) # Up
# Routine for handling arrow-up-key
;;
B) # Down
# Routine for handling arrow-down-key
;;
...
esac
read -rsn4 -t .1 # Try to flush out other sequences ...
esac
done
And so on.
As mentioned, question is how to make this reliable across various terminals: i.e. what byte sequences define a specific key. Is it even feasible in bash?
One thought was to use either tput
or infocmp
and filter by the result given by that. I am however in a snag there as both tput
and infocmp
differ from what I actually read when actually pressing keys. Same goes for example using C over bash.
for t in $(find /lib/terminfo -type f -printf "%f\n"); {
printf "%s\n" "$t:";
infocmp -L1 $t | grep -E 'key_(left|right|up|down|home|end)';
}
Yield sequences read as defined for for example linux
, but not xterm
, which is what is set by TERM
.
E.g. arrow left:
tput
/infocmp
:\x1 O D
read
:\x1 [ D
What am I missing?
Best Answer
What you are missing is that most terminal descriptions (
linux
is in the minority here, owing to the pervasive use of hard-coded strings in.inputrc
) use application mode for special keys. That makes cursor-keys as shown bytput
andinfocmp
differ from what your (uninitialized) terminal sends. curses applications always initialize the terminal, and the terminal data base is used for that purpose.dialog
has its uses, but does not directly address this question. On the other hand, it is cumbersome (technically doable, rarely done) to provide a bash-only solution. Generally we use other languages to do this.The problem with reading special keys is that they often are multiple bytes, including awkward characters such as escape and ~. You can do this with bash, but then you have to solve the problem of portably determining what special key this was.
dialog
both handles input of special keys and takes over (temporarily) your display. If you really want a simple command-line program, that isn'tdialog
.Here is a simple program in C which reads a special key and prints it in printable (and portable) form:
Supposing this were called
tgetch
, you would use it in your script like this:Further reading:
dialog
— Script-driven curses widgets (application and library)