The problem#
Last time, I presented a script read_keypress.sh
which would read any single keypress in a shell script. For printable
characters, it's straightforward what this means, but for keys like left
arrow (π ), it's not obvious how to deal with them.
The solution#
keytest.sh
demonstrates using read_keypress.sh
to respond to keypresses including π and
Ctrl+c:
#!/bin/bash
esc=$(printf '\e')
ctrlc=$(printf '\x03')
while true
do
key="$(./read_keypress.sh)"
case $key in
q|"$ctrlc")
echo "Quitting..."
exit
;;
"${esc}[D")
echo "Moved left."
;;
*)
echo "Unexpected key: $key ($(echo -n "$key" | xxd))"
;;
esac
done
The details#
Identifying keypresses#
The script above sends the unknown keypresses to xxd
to
show the actual bytes of the keypress, as many include non-printable
characters. For example for typing the left arrow (π ) (on
a version of the script without an explicit case for it, that is):
$ ./keytest.sh
Unexpected key: (00000000: 1b5b 44 .[D)
Here's a few other key presses, with the information the script prints reformatted into a table to be more readable:
Key | Printed | Hex | Hex printed |
---|---|---|---|
π | 1b5b 44 |
.[D |
|
Shift+π | 1b5b 313b 3244 |
.[1;2D |
|
Ctrl+π | 1b5b 313b 3544 |
.[1;5D |
|
Alt+π | 1b5b 313b 3344 |
.[1;3D |
|
a | a |
61 |
a |
Shift+a | A |
41 |
A |
Ctrl+a | 01 |
. |
|
Alt+a | 1b61 |
.a |
|
Ctrl+Alt+a | 1b01 |
.. |
Note that some of the modifier combinations are omitted because they were captured by my terminal or desktop environment instead of being sent to the script. For example, in my setup, Ctrl+Alt+π switches to the desktop to the left and Shift+Ctrl+a selects all in the terminal window.
echo -n
#
Without the -n
, echo
adds a newline (0x0a
) to the end of the
string it outputs, as does using the bash
<<<
feature to pipe a string to a command (xxd <<<$key
). I added the -n
to make it less confusing when piping to xxd
to see the exact
bytes.
Matching keypresses#
For printable characters, it's simple. Just type the character on the
keyboard and it will be the string bash
will match. But I had
some trouble figuring out how to get bash
to actually match
those non-printable characters in a case
. "\e[D"
or "\x1b[D"
don't work. The solution I eventually came up with is shown above: use
printf
to get the non-printable characters into variables, and
then use those variables in the case patterns:
esc=$(printf '\e') # "\e" is equivalent to "\x1b"
ctrlc=$(printf '\x03')
case $(./read_keypress.sh) in
q|"$ctrlc")
echo "Quitting..."
exit
;;
"${esc}[D")
echo "Moved left."
;;
esac
Note that $esc[D
doesn't work because it looks like a broken array
reference. ${esc}[D
avoids that confusion.
Comments
Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.
There are no comments yet.