Read special keys in bash

Have you tried using dialog? It comes standard with most Linux distros and can create all kinds of text-based dialogs, including checklists.

For example:

exec 3>&1 # open temporary file handle and redirect it to stdout

#                           type      title        width height n-items    
items=$(dialog --no-lines --checklist "Title here" 20    70     4 \
          1 "Item 1" on \
          2 "Item 2" off \
          3 "Item 3" on \
          4 "Item 4" off \
            2>&1 1>&3) # redirect stderr to stdout to catch output, 
                       # redirect stdout to temporary file
selected_OK=$? # store result value
exec 3>&- # close new file handle 

# handle output
if [ $selected_OK = 0 ]; then
    echo "OK was selected."
    for item in $items; do
        echo "Item $item was selected."
    done
else
    echo "Cancel was selected."
fi

You'll get something like this:

enter image description here

And the output will be:

 OK was selected.
 Item 1 was selected.
 Item 3 was selected.

(or whichever items you selected).

man dialog will get you information on the other kinds of dialogs you can create, and how to customize the appearance.


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 by tput and infocmp 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't dialog.

Here is a simple program in C which reads a special key and prints it in printable (and portable) form:

#include <curses.h>

int
main(void)
{   
    int ch;
    const char *result;
    char buffer[80];

    filter();
    newterm(NULL, stderr, stdin);
    keypad(stdscr, TRUE);
    noecho();
    cbreak();
    ch = getch();
    if ((result = keyname(ch)) == 0) {
        /* ncurses does the whole thing, other implementations need this */
        if ((result = unctrl((chtype)ch)) == 0) {
            sprintf(buffer, "%#x", ch);
            result = buffer;
        }
    }
    endwin();
    printf("%s\n", result);
    return 0;
}

Supposing this were called tgetch, you would use it in your script like this:

case $(tgetch 2>/dev/null) in
KEY_UP)
   echo "got cursor-up"
   ;;
KEY_BACKSPACE|"^H")
   echo "got backspace"
   ;;
esac

Further reading:

  • My cursor keys do not work (ncurses FAQ)
  • dialog — Script-driven curses widgets (application and library)
  • keyname/key_name

Tags:

Keyboard

Bash