How to create a menu in a shell script that will display 3 options that a user will use the arrows keys to move the highlight cursor and press enter to select one?

  • I think you are out of luck WRT to arrow key functionality and highlighting in a pure shell script (you might be able to do the latter with tput, but I think the former is not possible), but you can create simple menus in bash with select: tldp.org/LDP/Bash-Beginners-Guide/html/sect_09_06.html
    – goldilocks
    Jul 25 '14 at 15:03
  • Do you mean a GUI menu (using something like [zenity]( Ben Browder) or a text-based one using something like ncurses?
    – terdon
    Jul 25 '14 at 15:37
  • I am trying to create a menu that is like the one you get to if you had to select the boot option for windows ("safe mode" "normal" etc)
    – Mrplow911
    Jul 25 '14 at 17:49
  • 1
    There is the dialog package which creates basic faux-GUI terminal interfaces in scripts.
    – HalosGhost
    Jul 25 '14 at 18:31
  • @HalosGhost Do you know of any examples of this?
    – Mrplow911
    Jul 25 '14 at 18:41

dialog is a great tool for what you are trying to achieve. Here's the example of a simple 3-choices menu:

dialog --menu "Choose one:" 10 30 3 \
    1 Red \
    2 Green \
    3 Blue

The syntax is the following:

dialog --menu <text> <height> <width> <menu-height> [<tag><item>]

The selection will be sent to stderr. Here's a sample script using 3 colors.


dialog --menu "Choose one:" 10 30 3 \
    1 Red \
    2 Green \
    3 Blue 2>$TMPFILE


case $RESULT in
    1) echo "Red";;
    2) echo "Green";;
    3) echo "Blue";;
    *) echo "Unknown color";;


On Debian, you can install dialog through the package of the same name.


Here is a pure bash script solution in form of the select_option function, relying solely on ANSI escape sequences and the built-in read.

Works on Bash 4.2.45 on OSX. The funky parts that might not work equally well in all environments from all I know are the get_cursor_row(), key_input() (to detect up/down keys) and the cursor_to() functions.

#!/usr/bin/env bash

# Renders a text based list of options that can be selected by the
# user using up, down and enter keys and returns the chosen option.
#   Arguments   : list of options, maximum of 256
#                 "opt1" "opt2" ...
#   Return value: selected index (0 for opt1, 1 for opt2 ...)
function select_option {

    # little helpers for terminal print control and key input
    ESC=$( printf "\033")
    cursor_blink_on()  { printf "$ESC[?25h"; }
    cursor_blink_off() { printf "$ESC[?25l"; }
    cursor_to()        { printf "$ESC[$1;${2:-1}H"; }
    print_option()     { printf "   $1 "; }
    print_selected()   { printf "  $ESC[7m $1 $ESC[27m"; }
    get_cursor_row()   { IFS=';' read -sdR -p $'\E[6n' ROW COL; echo ${ROW#*[}; }
    key_input()        { read -s -n3 key 2>/dev/null >&2
                         if [[ $key = $ESC[A ]]; then echo up;    fi
                         if [[ $key = $ESC[B ]]; then echo down;  fi
                         if [[ $key = ""     ]]; then echo enter; fi; }

    # initially print empty new lines (scroll down if at bottom of screen)
    for opt; do printf "\n"; done

    # determine current screen position for overwriting the options
    local lastrow=`get_cursor_row`
    local startrow=$(($lastrow - $#))

    # ensure cursor and input echoing back on upon a ctrl+c during read -s
    trap "cursor_blink_on; stty echo; printf '\n'; exit" 2

    local selected=0
    while true; do
        # print options by overwriting the last lines
        local idx=0
        for opt; do
            cursor_to $(($startrow + $idx))
            if [ $idx -eq $selected ]; then
                print_selected "$opt"
                print_option "$opt"

        # user key control
        case `key_input` in
            enter) break;;
            up)    ((selected--));
                   if [ $selected -lt 0 ]; then selected=$(($# - 1)); fi;;
            down)  ((selected++));
                   if [ $selected -ge $# ]; then selected=0; fi;;

    # cursor position back to normal
    cursor_to $lastrow
    printf "\n"

    return $selected

Here is an example usage:

echo "Select one option using up/down keys and enter to confirm:"

options=("one" "two" "three")

select_option "${options[@]}"

echo "Choosen index = $choice"
echo "        value = ${options[$choice]}"

Output looks like below, with the currently selected option highlighted using inverse ansi coloring (hard to convey here in markdown). This can be adapted in the print_selected() function if desired.

Select one option using up/down keys and enter to confirm:


Update: Here is a little extension select_opt wrapping the above select_option function to make it easy to use in a case statement:

function select_opt {
    select_option "$@" 1>&2
    local result=$?
    echo $result
    return $result

Example usage with 3 literal options:

case `select_opt "Yes" "No" "Cancel"` in
    0) echo "selected Yes";;
    1) echo "selected No";;
    2) echo "selected Cancel";;

You can also mix if there are some known entries (Yes and No in this case), and leverage the exit code $? for the wildcard case:

options=("Yes" "No" "${array[@]}") # join arrays to add some variable array
case `select_opt "${options[@]}"` in
    0) echo "selected Yes";;
    1) echo "selected No";;
    *) echo "selected ${options[$?]}";;
  • 2
    This is beautiful and amazing; thank you very much for sharing! Is this your own originally? Is there a repo online to clone/fork? The only thing I could find that seemed to be in version control was on GitHub in stephenmm's Gist (with line editing added) which points back to here, lol. Working on my own modifications (in a Gist, but planning to make a repo) here though I need to update with the latest changes still.
    – l3l_aze
    Sep 14 '18 at 2:14
  • 1
    I used it in some non public code. Pulled it together from various bits and pieces found on the web :-) Sep 14 '18 at 15:51
  • 1
    Wow; nice work. I started a repo with my modifications at https://github.com/l3laze/sind. So far the biggest differences are upgraded input handling and the addition of a title bar. I'm hoping to add single and multi-line editing, but haven't done anything towards those yet beyond looking at some code
    – l3l_aze
    Sep 15 '18 at 2:19
  • 1
    @AlexanderKlimetschek I was able to figure it out. I wanted the first argument as a number and the list of options as an array (for clearer distinguishing of arguments). Here is the solution: pastebin.com/Qe1zHGtN
    – srigi
    May 17 at 15:40
  • 1
    This is exactly what I was looking for. Thanks for sharing. I modified this to add a few new features. Each option is labeled with an index and you can select that option by pressing the number. Also, the menu erase itself once a selection is made. This is nice if you want to have a multi-page menu. You can add sub-menus that replace the previous menu. Code in gist: gist.github.com/RobertMcReed/05b2dad13e20bb5648e4d8ba356aa60e Aug 23 at 3:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.