Bash – Script to Detect Right Arrow Key Press

bashshell-script

Why does this always detect as true, even if the keycode is not right-arrow-key?

stty_state=`stty -g`
stty raw; stty -echo
keycode=`dd bs=1 count=1 2>/dev/null`
stty "$stty_state"  

echo $keycode

if [ "$keycode"=39 ]; then
echo "Right Arrow Key Pressed!"
fi

Best Answer

You (likely) read first out of two+ bytes. $keycode in your script would be ESC when arrow key is pressed.

Arrow keys can be:

\x1b + some value

It always evaluates to true because of missing spaces in conditional expression.

Edit: an update on that statement.

Your if operates on the exit status of the [ command. The [ command is equivalent to test. The fact that it is a command is a very important fact. As a command it require spaces between arguments. The [ command is further special in that it require ] as last argument.

[ EXPRESSION ]

The command exits with the status determined by EXPRESSION. 1 or 0, true or false.

It is not an exotic way to write parenthesis. In other words it is not part of the if syntax as for example in C:

if (x == 39)

By:

if [ "$keycode"=39 ]; then

you issue:

[ "$keycode"=39 ]

which expands to

[ \x1b=39 ]

here \x1b=39 is read as one argument. When test or [ is given one argument it exits with false only if EXPRESSION is null – which is is never going to be. Even if $keycode was empty, it would result in =39 (which is not null / empty).

Another way to look at it is that you say:

if 0 ; then # When _command_ exit with 0.
if 1 ; then # When _command_ exit with 1.

Read these questions and answers for more details – as well as discussion on [ vs [[:

In that regard you could also research back ticks `` vs $( )


Multibyte escape sequence with arrow keys:

As mentioned at the top: You (likely) read first out of two+ bytes. $keycode in your script would be ESC when arrow key is pressed.

Arrow and other special keys result in escape sequences to be sent to the system. The ESC byte signals that "here comes some bytes that should be interpreted differently". As for arrow keys that would be the ASCII [ followed by ASCII A, B, C or D.

In other words you have to parse three bytes when dealing with arrow keys.

You could try something in the direction of this to check:

{   stty_state=$(stty -g)
    stty raw isig -echo
    keycode=$(dd bs=8 conv=sync count=1)
    stty "$stty_state"
} </dev/tty 2>/dev/null
printf %s "$keycode" | xxd

Yield:

HEX        ASCII
1b 5b 41   .[A # Up arrow
1b 5b 42   .[B # Down arrow
1b 5b 43   .[C # Right arrow
1b 5b 44   .[D # Left arrow
 |  |  |
 |  |  +------ ASCII A, B, C and D
 |  +--------- ASCII [
 +------------ ASCII ESC

Not sure how portable this is, but have earlier played around with code like this for catching arrow keys. Press q to quit:

while read -rsn1 ui; do
    case "$ui" in
    $'\x1b')    # Handle ESC sequence.
        # Flush read. We account for sequences for Fx keys as
        # well. 6 should suffice far more then enough.
        read -rsn1 -t 0.1 tmp
        if [[ "$tmp" == "[" ]]; then
            read -rsn1 -t 0.1 tmp
            case "$tmp" in
            "A") printf "Up\n";;
            "B") printf "Down\n";;
            "C") printf "Right\n";;
            "D") printf "Left\n";;
            esac
        fi
        # Flush "stdin" with 0.1  sec timeout.
        read -rsn5 -t 0.1
        ;;
    # Other one byte (char) cases. Here only quit.
    q) break;;
    esac
done

(As a minor note you also (intend to) test against decimal 39 – which looks like a mixup between decimal and hexadecimal. First byte in an escape sequence is ASCII value ESC, which is decimal 27 and hexadecimal 0x1b, while decimal 39 is hexadecimal 0x27.)

Related Question