Parsing command-line flags?
case inside a while loop is the standard flag parser; Bash Argument Parsing builds the whole thing, including getopts and --long options. This page is the case statement on its own.
case looks like a switch statement and behaves like one only until it doesn't. The first matching pattern wins, not the best one, so a general rule above a specific one quietly swallows it. Patterns are globs, so [1-10] does not mean one to ten. A pattern in a quoted variable is not a pattern at all. And the two fallthrough terminators, ;& and ;;&, do different things that most examples never show side by side.
How Do Patterns and | Alternatives Work?
Reproduced here on 2026-10-03 (Kali, bash 5.3.15). A yes/no prompt, the classic use:
[Yy] matches one character from the set, | separates alternatives, "" matches the empty string (a user who pressed Enter), and *) catches everything else. Character classes work on one character at a time:
[1-10] is a bracket expression for one character: the range 1-1 plus 0. It can never match the two-character string 10.
Why Does Pattern Order Matter?
Same file, same patterns, different order, different answer. case stops at the first match. Specific patterns go first, the catch-all goes last.
What Do ;;& and ;& Do?
| Terminator | After this body runs | Example above |
|---|---|---|
;; | leave the case | *pp* stops apple there |
;;& | keep testing the following patterns, run any that match | apple matches a*, then *pp* too |
;& | run the next body without testing its pattern | Bob has no an but still prints the *an* line |
Apple shows that patterns are case-sensitive: it misses a*. Both extra terminators arrived in bash 4.0 and do not exist in POSIX sh.
Why Doesn't My Pattern in a Variable Match?
Quoting a variable in a pattern position makes it literal: "$pat" matches only a file actually named *.log. Leave it unquoted when it holds a glob. The word being tested ("$f") is the opposite: quote it, always.
How Do I Use extglob Patterns?
shopt -s extglob adds +(…) (one or more), *(…), ?(…), @(…) (exactly one) and !(…) (anything except). It has to be on a line before the code that uses it, because bash parses a whole line before running any of it:
On separate lines it works:
The Script
A real job for case: unpack any archive with the right tool. Save as extract.sh. The pattern order is the whole point.
Prerequisites
bash 4 or later (for ${f,,}), tar, and whichever decompressors your archives need: gzip, bzip2, xz, zstd, unzip. No root.
How Does the Script Work?
case "${f,,}"tests a lowercased copy of the name, so.TGZand.Zipmatch without doubling every pattern. The real name"$f"is still what gets passed to the tool.- Pattern order puts every two-part extension above its single-part twin. Swap
*.tar.gzbelow*.gzandsite.tar.gzwould be gunzipped into asite.taryou then have to untar by hand. cmd=(…)arrays carry the command and its file argument with spaces intact, asOLD BACKUP.TGZbelow shows.-kand-nkeep the original.gz/.bz2/.xzand stopunzipoverwriting existing files, so running it twice is harmless.STATUSrecords any failure without stopping the loop, so one bad file does not hide the rest, and the exit code still says something went wrong.
What Does the Script Print?
case also pairs naturally with string tests and prefix stripping; Bash String Manipulation covers ${var#prefix}, ${var,,} and friends, and Bash If Else Examples covers the conditions case cannot express. The Production Bash Toolkit ships its scripts ShellCheck-clean, case blocks included.
Frequently Asked Questions
What does ;; mean in a bash case statement?
It ends the body of a case item and leaves the case statement. bash also accepts two other terminators: ;& runs the next item's body as well, without checking its pattern, and ;;& continues testing the patterns after the current one and runs any that also match. Both were added in bash 4.0 and are not in POSIX sh, so scripts that must run under dash should use ;; only.
Can I use regex in a bash case statement?
No. case patterns are shell glob patterns: *, ?, [...] and | between alternatives. With shopt -s extglob you get +(...), *(...), ?(...), @(...) and !(...), which cover many regex uses such as +([0-9]) for one or more digits. For a real regular expression, use [[ $var =~ regex ]] in an if statement and read the captures from BASH_REMATCH.
How do I match multiple values in one case branch?
Separate the patterns with |: [Yy]|[Yy][Ee][Ss]) matches y, Y, yes, YES and every mixed-case spelling. Character classes help too: [0-9] matches one digit, [a-z]* matches anything starting with a lowercase letter. Note that [1-10] is not the range 1 to 10; it is the set of characters 1, 0 and the range 1-1, so it matches the single characters 0 and 1 only.
Why does my case statement match the wrong pattern?
Because case runs the first pattern that matches, not the most specific one. If *.gz comes before *.tar.gz, report.tar.gz matches *.gz and never reaches the tarball rule. Put longer, more specific patterns first and the catch-all *) last. The other common cause is quoting: a pattern stored in a variable must be unquoted to act as a glob, because "$pat" matches only the literal text.
Should I use case or if/elif in bash?
Use case when you compare one value against several patterns: command-line options, file extensions, yes/no answers, subcommands like start|stop|status. It is shorter, does not need quoting inside the patterns, and runs in a single test. Use if/elif when each branch tests something different, compares numbers, or needs && and || between conditions.
Part of the bash snippets collection
Related Scripts
- Bash Argument Parsing — case inside a while loop, the standard flag parser
- Bash If Else Examples — the tests case cannot do: numbers, files, && and ||
- Bash String Manipulation — lowercase, strip prefixes and suffixes before matching
- Bash Heredoc — multi-line usage text for the *) branch