Skip to main content
5 of 7
added direct links to zsh implementation
Guss
  • 3.8k
  • 3
  • 38
  • 48

If you only want a very simple menu that shows "in place" and you can continue typing after that - without any fancy external dialog program, then you can use ANSI escape sequences and a simple loop to render the list and allow a cursor to be moved on top of it.

The answer here by user360154 already has everything you need, but it is also super fancy, does much more than needed and while the code is also formatted to look fancy - it isn't easy to read and understand.

Here's the same approach as user360154's but much simpler:

function choose_from_menu() {
    local prompt="$1" outvar="$2"
    shift
    shift
    local options=("$@") cur=0 count=${#options[@]} index=0
    local esc=$(echo -en "\e") # cache ESC as test doesn't allow esc codes
    printf "$prompt\n"
    while true
    do
        # list all options (option list is zero-based)
        index=0 
        for o in "${options[@]}"
        do
            if [ "$index" == "$cur" ]
            then echo -e " >\e[7m$o\e[0m" # mark & highlight the current option
            else echo "  $o"
            fi
            (( index++ ))
        done
        read -s -n3 key # wait for user to key in arrows or ENTER
        if [[ $key == $esc[A ]] # up arrow
        then (( cur-- )); (( cur < 0 )) && (( cur = 0 ))
        elif [[ $key == $esc[B ]] # down arrow
        then (( cur++ )); (( cur >= count )) && (( cur = count - 1 ))
        elif [[ $key == "" ]] # nothing, i.e the read delimiter - ENTER
        then break
        fi
        echo -en "\e[${count}A" # go up to the beginning to re-render
    done
    # export the selection to the requested output variable
    printf -v $outvar "${options[$cur]}"
}

Here is an example usage:


selections=(
"Selection A"
"Selection B"
"Selection C"
)

choose_from_menu "Please make a choice:" selected_choice "${selections[@]}"
echo "Selected choice: $selected_choice"

Which should look like this:
demo

Update:

While the above code is for Bash, @lukeflo ported the code to zsh, as he commented below. He has moved things around, so disregard the links in the comments and his example implementation can currently be found here, but if it goes missing (again) a snapshot can be found here.

Guss
  • 3.8k
  • 3
  • 38
  • 48