Menus and Choices: case and select
case matches one value against a list of patterns and runs the lines for the first match, which suits a script's options and yes-or-no answers, and select turns a list of words into a numbered menu.
Apprentice · Unix · Linux · Updated
After this lesson you can
- Match one value against several patterns with case, using a pipe between patterns and *) for anything else
- Show a numbered menu with select, use the chosen word from its variable, and end the menu with break
Key takeaways
- case matches one value against patterns, top to bottom, and runs only the lines for the first match. esac, case backwards, ends it.
- | between patterns means or, *) matches anything else, and ;; ends each choice.
- case "$1" in with -h|--help) and --list) is the usual way a script handles its options.
- select shows the words after in as a numbered menu, puts the chosen word in its variable, and runs the lines between do and done.
- break ends a select menu after one choice. Without it, select asks again until you press Ctrl+D.
Call a pharmacy, and a recording says "press 1 for opening hours, 2 for prescriptions". Run almost any command with --help, and it explains itself. Both are menus: one choice, picked from a short list. Your backup script can have them too, a --help and a --list option, or a numbered list of files to pick from. You could write them with if and elif, but [[ "$1" == ... ]] on every line gets long and hard to read. Bash has a tidier way for each: case for matching one value against many, and select for a numbered menu. You'll find case at the top of most scripts administrators write, sorting out the options they were given.
The examples use the smart-backup folder and backup.sh from Checking Before You Act: File Tests: cd ~/terminal-practice/smart-backup.
case: one value, many patterns
Picture a mail sorter reading each envelope's address and dropping it into the first slot that matches. case reads one value and tries a list of patterns, top to bottom. Try it at the prompt, with an answer someone might type:
answer=Y
case "$answer" in
y|Y|yes|Yes) echo "Backing up." ;;
n|N|no|No) echo "Nothing copied." ;;
*) echo "Please answer yes or no." ;;
esac
Backing up.
Read it piece by piece:
case "$answer" in(read "in case the answer is…") names the value to match.- Each choice is a pattern, a
), then its commands.|between patterns (read "or") lets one choice match several answers, soYmatches as well asyes. ;;(read "end of choice") ends a choice's commands.*)matches anything at all, so as the last choice it catches every answer the others didn't. Withanswer=maybe, the script says "Please answer yes or no."esac,casebackwards, ends the whole thing, the wayfiendsif.
Only the first match runs. The patterns are the wildcards from Wildcards: *, ? and [ ], so *.jpg) would match every name ending in .jpg.
Options for your script
Most scripts handle their options with case "$1". Add these lines to backup.sh, right under its comment line:
case "$1" in
-h|--help)
echo "Usage: ./backup.sh FILE, or ./backup.sh --list"
exit 0
;;
--list)
ls backup
exit 0
;;
esac
./backup.sh --help
./backup.sh --list
./backup.sh essay.txt
Usage: ./backup.sh FILE, or ./backup.sh --list
essay.txt notes.txt
essay.txt hasn't changed since the last backup.
exit 0 stops the script with "it worked", so the backup lines below never run for an option. There's no *) choice here on purpose: anything that isn't an option matches nothing, so case does nothing and the script carries on to back up the file. Adding a new option later means adding one more choice.
select: a numbered menu in three lines
Sometimes picking from a list beats typing a name. select shows a list of words as a numbered menu and waits for a number. PS3 holds the question it asks (without it, select asks #?). Type these lines at the prompt, then answer 4:
PS3="Back up which file? "
select file in *.txt; do
./backup.sh "$file"
break
done
1) empty-draft.txt 3) notes.txt
2) essay.txt 4) reading-list.txt
Back up which file? 4
Copied reading-list.txt.
The words after in become the menu, here every .txt file in the folder. The word you pick goes into file, and the lines between do and done run with it. select is a loop, a set of lines that runs again and again (For Every File: for Loops teaches loops next chapter): without break, it would ask again after every choice, until you pressed Ctrl+D. A number that isn't on the menu leaves file empty, so backup.sh would answer "There's no here.", with a gap where the name should be.
In a script on a Mac, case and select run in bash 5 like the rest of it; zsh has both too.
Check yourself
2 questions to try on the lesson page.
So what
case keeps a script's choices readable however many it grows, and select gives anyone who runs your script a menu instead of a blank prompt: two small steps that make a script feel like an application.