How To Create Simple Menu with the Shell Select Loop?

  • HOME
  • >
  • BASH
  • >
  • How To Create Simple Menu with the Shell Select Loop?
Last Updated: 

The select loop construct in bash is not part of the posix standard. Though, it is widely used to easily generate interactive menus in a shell script. This post covers how to use a select loop with the select keyword in bash to create a simple selectable menu in a shell script.

What is a select loop in a shell script?

The select construct is a bash shorthand to generate a numbered menu. It is not part of the POSIX shell standard. It is a useful command to easily ask a user to choose from a list of options.

Despite its name, the select loop is not part of the bash loop constructs. Like the bash if keyword, the select keyword is part of the conditional constructs in the bash manual.

The select loop syntax is very close to the one of a for loop: select name [in words …]; do commands; done.

The list of words after the in keyword is expanded by the bash shell to generate a list of items (menu). The menu items are printed on the standard error output stream (STDERR), preceded by a number. Similar to the bash for loop, if the [in words ...] is omitted, the positional parameters are used as if using in "$@".

The select loop uses the PS3 variable to prompt the user. The user selection is read from the standard input. If the user selection is empty, the menu and the PS3 prompt is shown again. If a number is provided by the user, then the name variable from the select loop is set to the corresponding word (menu item). Any other value read causes name to be set to null.

The select command complete when an EOF is read or the command is interrupted with a break. The line read (number) is saved to the variable REPLY.

Below is a classic example to prompt a user to choose a filename from the current directory, and displays the filename and index of the file selected.

select filename in *;
do
	echo "You selected $filename ($REPLY)"
	break;
done

👉 The select loop can be nested to create submenus, though the PS3 prompt variable is not changed when entering a nested loop. In such a case, make sure to set the PS3 variable accordingly.

How can I create a select menu in bash?

Below is an example using the bash select loop to generate a selectable menu from a bash array, using the PS3 variable to set the user prompt, and displaying the menu and index selected.

The menu display a list of animal and object and prompt the user to select one. The select loop continues if a non-animal item is selected from the menu and end when an animal is selected from the menu. This example does not test for invalid options which could be accomplished by using a bash case construct.

PS3="Choose an animal: "
options=(cat dog mouse chair cow bird apple)
select menu in "${options[@]}";
do
  echo -e "\nyou picked $menu ($REPLY)"
  if [[ $menu == "chair" || $menu == "apple" ]]; then
    echo -e "$menu is not an animal\n"
  else
    echo "$menu is an animal"
    break;
  fi
done

When running this example, the output would look like below when first selecting the option 4 then 1.

[me@linux ~]$ ./my-example-script
1) cat
2) dog
3) mouse
4) chair
5) cow
6) bird
7) apple
Choose an animal: 4

you picked chair (4)
chair is not an animal

Choose an animal: 1

you picked cat (1)
cat is an animal

How to pipe options to a select loop in bash?

As we mentioned earlier, the select command completes when EOF is read and it reads the user selection from the standard input STDIN which is the keyboard input in an interactive script. Though this is not the case when the command is pipped to your script, the standard output STDOUT of the previous pipped command becomes the STDIN.

#!/usr/bin/bash
# script: select-loop.sh
select opts;
do
	echo "You selected $opts ($REPLY)"
	break;
done

# Output of executing `select-loop.sh option1 option2` and selecting 1
1) option1
2) option2
#? 1
You selected option1 (1)

# Executing the script with a pipe result in no output / no select menu
[me@linux ~]$ echo "option1 option2" | ./select-loop.sh
[me@linux ~]$

To fix this behavior, we need to ensure the select menu will read from the /dev/tty and that we are passing the option with proper word delimiter using the echo or printf commands.

#!/usr/bin/bash
# script: select-loop.sh
readarray -t opts
select item in "${opts[@]}";
do
  echo "You selected $item ($REPLY)"
  break;
done < /dev/tty

# Executing the script with a pipe and using the echo command
[me@linux ~]$ echo -e "option1\noption2" | ./select-loop.sh
1) option1
2) option2
#? 1
You selected option1 (1)

# Executing the script with a pipe and using the printf command
[me@linux ~]$ printf "%s\n" "option 1" "option 2" | ./select-loop.sh
1) option1
2) option2
#? 1
You selected option1 (1)
Related bash posts that you may like...
The Complete How To Guide of Bash Functions
Learn how to write shell scripts with bash functions. This guide includes examples and best practices on how to define, call, and debug functions in bash.
What is the Right Way to do Bash Loops?
Looping over a list of numbers or words is a building block in shell scripts. Learn how to write Bash loops, including for loop, while loop, and until loop.
What is the Best Way to Count Files in a Directory?
Learn how to count the number of files in a directory using the Linux command line ls, find, and a native bash shell solution with globs and arrays.
5 Mistakes To Avoid For Writing High-Quality Bash Comments
Adding comments in your Bash scripts is necessary to ensure maintainability over time. This post covers 5 Bash comments mistakes to avoid in your shell scripts.