How can I create a select menu in a shell script?
Andrew Henderson
I'm creating a simple bash script and I want to create a select menu in it, like this:
$./script
echo "Choose your option:"
1) Option 1
2) Option 2
3) Option 3
4) Quit And according to user's choice, I want different actions to be executed. I'm a bash shell scripting noob, I've searched the web for some answers, but got nothing really concrete.
212 Answers
#!/bin/bash
# Bash Menu Script Example
PS3='Please enter your choice: '
options=("Option 1" "Option 2" "Option 3" "Quit")
select opt in "${options[@]}"
do case $opt in "Option 1") echo "you chose choice 1" ;; "Option 2") echo "you chose choice 2" ;; "Option 3") echo "you chose choice $REPLY which is $opt" ;; "Quit") break ;; *) echo "invalid option $REPLY";; esac
doneAdd break statements wherever you need the select loop to exit. If a break is not performed, the select statement loops and the menu is re-displayed.
In the third option, I included variables that are set by the select statement to demonstrate that you have access to those values. If you choose it, it will output:
you chose choice 3 which is Option 3You can see that $REPLY contains the string you entered at the prompt. It is used as an index into the array ${options[@]} as if the array were 1 based. The variable $opt contains the string from that index in the array.
Note that the choices could be a simple list directly in the select statement like this:
select opt in foo bar baz 'multi word choice'but you can't put such a list in a scalar variable because of the spaces in one of the choices.
You can also use file globbing if you are choosing among files:
select file in *.tar.gz 12 Using dialog, the command would look like this:
dialog --clear --backtitle "Backtitle here" --title "Title here" --menu "Choose one of the following options:" 15 40 4 \ 1 "Option 1" \ 2 "Option 2" \ 3 "Option 3"
Putting it in a script:
#!/bin/bash
HEIGHT=15
WIDTH=40
CHOICE_HEIGHT=4
BACKTITLE="Backtitle here"
TITLE="Title here"
MENU="Choose one of the following options:"
OPTIONS=(1 "Option 1" 2 "Option 2" 3 "Option 3")
CHOICE=$(dialog --clear \ --backtitle "$BACKTITLE" \ --title "$TITLE" \ --menu "$MENU" \ $HEIGHT $WIDTH $CHOICE_HEIGHT \ "${OPTIONS[@]}" \ 2>&1 >/dev/tty)
clear
case $CHOICE in 1) echo "You chose Option 1" ;; 2) echo "You chose Option 2" ;; 3) echo "You chose Option 3" ;;
esac 4 Not a new answer per se, but since there's no accepted answer yet, here are a few coding tips and tricks, for both select and zenity:
title="Select example"
prompt="Pick an option:"
options=("A" "B" "C")
echo "$title"
PS3="$prompt "
select opt in "${options[@]}" "Quit"; do case "$REPLY" in 1) echo "You picked $opt which is option 1";; 2) echo "You picked $opt which is option 2";; 3) echo "You picked $opt which is option 3";; $((${#options[@]}+1))) echo "Goodbye!"; break;; *) echo "Invalid option. Try another one.";continue;; esac
done
while opt=$(zenity --title="$title" --text="$prompt" --list \ --column="Options" "${options[@]}")
do case "$opt" in "${options[0]}") zenity --info --text="You picked $opt, option 1";; "${options[1]}") zenity --info --text="You picked $opt, option 2";; "${options[2]}") zenity --info --text="You picked $opt, option 3";; *) zenity --error --text="Invalid option. Try another one.";; esac
doneWorth mentioning:
Both will loop until the user explicitly chooses Quit (or Cancel for zenity). This is a good approach for interactive script menus: after a choice is selected and action performed, menu is presented again for another choice. If choice is meant to be one-time only, just use
breakafteresac(the zenity approach could be further reduced also)Both
caseare index-based, rather than value-based. I think this is easier to code and maintainArray is also used for
zenityapproach."Quit" option is not among the initial, original options. It is "added" when needed, so your array stay clean. Afterall, "Quit" is not needed for zenity anyway, user can just click "Cancel" (or close the window) to exit. Notice how both uses the same, untouched array of options.
PS3andREPLYvars can not be renamed.selectis hardcoded to use those. All other variables in script (opt, options, prompt, title) can have any names you want, provided you do the adjustments
You can use this simple script for creating options
#!/bin/bash echo "select the operation ************" echo " 1)operation 1" echo " 2)operation 2" echo " 3)operation 3" echo " 4)operation 4"
read n case $n in 1) echo "You chose Option 1";; 2) echo "You chose Option 2";; 3) echo "You chose Option 3";; 4) echo "You chose Option 4";; *) echo "invalid option";; esac
I have one more option that is a mixture of these answers but what makes it nice is that you only need to press one key and then the script continues thanks to the -n option of read. In this example, we are prompting to shutdown, reboot, or simply exit the script using ANS as our variable and the user only has to press E, R, or S. I also set the default to exit so if enter is pressed then the script will exit.
#!/bin/bash
read -n 1 -p "Would you like to exit, reboot, or shutdown? (E/r/s) " ans;
case $ans in r|R) sudo reboot;; s|S) sudo poweroff;; *) exit;;
esac 1 #!/bin/sh
show_menu(){ normal=`echo "\033[m"` menu=`echo "\033[36m"` #Blue number=`echo "\033[33m"` #yellow bgred=`echo "\033[41m"` fgred=`echo "\033[31m"` printf "\n${menu}*********************************************${normal}\n" printf "${menu}**${number} 1)${menu} Mount dropbox ${normal}\n" printf "${menu}**${number} 2)${menu} Mount USB 500 Gig Drive ${normal}\n" printf "${menu}**${number} 3)${menu} Restart Apache ${normal}\n" printf "${menu}**${number} 4)${menu} ssh Frost TomCat Server ${normal}\n" printf "${menu}**${number} 5)${menu} Some other commands${normal}\n" printf "${menu}*********************************************${normal}\n" printf "Please enter a menu option and enter or ${fgred}x to exit. ${normal}" read opt
}
option_picked(){ msgcolor=`echo "\033[01;31m"` # bold red normal=`echo "\033[00;00m"` # normal white message=${@:-"${normal}Error: No message passed"} printf "${msgcolor}${message}${normal}\n"
}
clear
show_menu
while [ $opt != '' ] do if [ $opt = '' ]; then exit; else case $opt in 1) clear; option_picked "Option 1 Picked"; printf "sudo mount /dev/sdh1 /mnt/DropBox/; #The 3 terabyte"; show_menu; ;; 2) clear; option_picked "Option 2 Picked"; printf "sudo mount /dev/sdi1 /mnt/usbDrive; #The 500 gig drive"; show_menu; ;; 3) clear; option_picked "Option 3 Picked"; printf "sudo service apache2 restart"; show_menu; ;; 4) clear; option_picked "Option 4 Picked"; printf "ssh lmesser@ -p 2010"; show_menu; ;; x)exit; ;; \n)exit; ;; *)clear; option_picked "Pick an option from the menu"; show_menu; ;; esac fi
done 4 Since this is targeted at Ubuntu you should use whatever backend debconf is configured to use. You can find out the debconf backend with:
sudo -s "echo get debconf/frontend | debconf-communicate"If it says "dialog" then it likely uses whiptail or dialog. On Lucid it's whiptail.
If that fails, use bash "select" as explained by Dennis Williamson.
2Bash fancy menu
Try it out first, then visit my page for detailed description ... No need for external libraries or programs like dialog or zenity ...
#/bin/bash
# by oToGamez
# E='echo -e';e='echo -en';trap "R;exit" 2 ESC=$( $e "\e") TPUT(){ $e "\e[${1};${2}H";} CLEAR(){ $e "\ec";} CIVIS(){ $e "\e[?25l";} DRAW(){ $e "\e%@\e(0";} WRITE(){ $e "\e(B";} MARK(){ $e "\e[7m";} UNMARK(){ $e "\e[27m";} R(){ CLEAR ;stty sane;$e "\ec\e[37;44m\e[J";}; HEAD(){ DRAW for each in $(seq 1 13);do $E " x x" done WRITE;MARK;TPUT 1 5 $E "BASH SELECTION MENU ";UNMARK;} i=0; CLEAR; CIVIS;NULL=/dev/null FOOT(){ MARK;TPUT 13 5 printf "ENTER - SELECT,NEXT ";UNMARK;} ARROW(){ read -s -n3 key 2>/dev/null >&2 if [[ $key = $ESC[A ]];then echo up;fi if [[ $key = $ESC[B ]];then echo dn;fi;} M0(){ TPUT 4 20; $e "Login info";} M1(){ TPUT 5 20; $e "Network";} M2(){ TPUT 6 20; $e "Disk";} M3(){ TPUT 7 20; $e "Routing";} M4(){ TPUT 8 20; $e "Time";} M5(){ TPUT 9 20; $e "ABOUT ";} M6(){ TPUT 10 20; $e "EXIT ";} LM=6 MENU(){ for each in $(seq 0 $LM);do M${each};done;} POS(){ if [[ $cur == up ]];then ((i--));fi if [[ $cur == dn ]];then ((i++));fi if [[ $i -lt 0 ]];then i=$LM;fi if [[ $i -gt $LM ]];then i=0;fi;}
REFRESH(){ after=$((i+1)); before=$((i-1)) if [[ $before -lt 0 ]];then before=$LM;fi if [[ $after -gt $LM ]];then after=0;fi if [[ $j -lt $i ]];then UNMARK;M$before;else UNMARK;M$after;fi if [[ $after -eq 0 ]] || [ $before -eq $LM ];then UNMARK; M$before; M$after;fi;j=$i;UNMARK;M$before;M$after;} INIT(){ R;HEAD;FOOT;MENU;} SC(){ REFRESH;MARK;$S;$b;cur=`ARROW`;} ES(){ MARK;$e "ENTER = main menu ";$b;read;INIT;};INIT while [[ "$O" != " " ]]; do case $i in 0) S=M0;SC;if [[ $cur == "" ]];then R;$e "\n$(w )\n";ES;fi;; 1) S=M1;SC;if [[ $cur == "" ]];then R;$e "\n$(ifconfig )\n";ES;fi;; 2) S=M2;SC;if [[ $cur == "" ]];then R;$e "\n$(df -h )\n";ES;fi;; 3) S=M3;SC;if [[ $cur == "" ]];then R;$e "\n$(route -n )\n";ES;fi;; 4) S=M4;SC;if [[ $cur == "" ]];then R;$e "\n$(date )\n";ES;fi;; 5) S=M5;SC;if [[ $cur == "" ]];then R;$e "\n$($e by oTo)\n";ES;fi;; 6) S=M6;SC;if [[ $cur == "" ]];then R;exit 0;fi;; esac;POS;done 4 I have used Zenity, which seems always there in Ubuntu, works very well and has many capabilities. This is a sketch of a possible menu:
#! /bin/bash
selection=$(zenity --list "Option 1" "Option 2" "Option 3" --column="" --text="Text above column(s)" --title="My menu")
case "$selection" in
"Option 1")zenity --info --text="Do something here for No1";;
"Option 2")zenity --info --text="Do something here for No2";;
"Option 3")zenity --info --text="Do something here for No3";;
esac 2 There is already the same question in serverfault answered. The solution there uses whiptail.
1If 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=$(( $index + 1 )) done read -s -n3 key # wait for user to key in arrows or ENTER if [[ $key == $esc[A ]] # up arrow then cur=$(( $cur - 1 )) [ "$cur" -lt 0 ] && cur=0 elif [[ $key == $esc[B ]] # down arrow then cur=$(( $cur + 1 )) [ "$cur" -ge $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" Assuming you want to use a plain shell script menu (no fancy UI), check the menu example from .
1