A Weird Imagination

Interpreting keypresses in shell

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.