| Tested on | RHEL 10.2 (Coughlan) |
|---|---|
| Package | bash 5.2.26-6.el10 |
| Applies to | Linux distributions with Bash installed |
| Privilege | Normal user; no root required for the examples |
| Scope | Bash case syntax, glob pattern matching, multiple patterns with |, the * default branch, nocasematch, terminators ;;, ;&, and ;;&, subcommand parsing, and when to prefer case over if/elif. Does not cover full getopts flag parsing or in-depth regular-expression matching. |
| Related guides | Bash shell script tutorial Shell scripting course hub |
The Bash case statement checks one value against several possible patterns and runs the commands for the first one that matches. It is the shell equivalent of a switch statement: there is no separate switch keyword. If you are looking for a Bash switch or switch case statement, case ... in ... esac is the construct you want.
case fits when one value, often $1, must match several known strings or shell patterns such as start, stop, or *.log. Bash tests patterns top to bottom, runs the first match, and stops unless you use the alternate terminators ;& or ;;&.
Bash case Syntax
case WORD in
PATTERN1)
COMMANDS
;;
PATTERN2|PATTERN3)
COMMANDS
;;
*)
DEFAULT_COMMANDS
;;
esac| Part | Role |
|---|---|
WORD |
The value Bash compares against the patterns, such as $1 or $action |
in |
Begins the list of patterns to test |
PATTERN |
Glob-style pattern, not a regular expression |
) |
Ends a pattern before its commands begin |
| |
Separates alternative patterns in the same clause |
;; |
End the clause and stop matching (default) |
*) |
* matches any string; when placed last, it acts like the default branch of a switch statement |
esac |
Closes the case block (case spelled backward) |
Each pattern together with the commands beneath it forms one case branch (called a clause in the Bash documentation). Because * matches any value, put the *) default branch last so more specific patterns get a chance to match first.
Quoting the value, such as case "$1" in, is a clear and consistent style. case does not perform normal word splitting or pathname expansion on the expanded word before matching.
Basic case Example
$1 is the first argument passed to the script — see Bash script arguments for how positional parameters are set. ${1:-} uses that value when it exists, or an empty string when no argument was supplied. $0 contains the name used to run the script, which makes it useful in usage messages.
Match the first positional argument to a verb and handle anything else in *):
#!/usr/bin/env bash
case "${1:-}" in
start)
echo 'Starting the service...'
;;
stop)
echo 'Stopping the service...'
;;
*)
echo "Usage: $0 {start|stop}" >&2
exit 1
;;
esacSave the script as service.sh, then make it executable:
chmod +x service.shRun start:
./service.sh startStarting the service...An invalid verb uses the default branch:
./service.sh pauseUsage: ./service.sh {start|stop}When you run ./service.sh start, Bash stores start in $1. The case statement compares that value against each pattern from top to bottom. The start) pattern matches, so Bash runs its echo command. The ;; ends that branch, and Bash continues after esac. The >&2 in the usage message sends it to standard error rather than normal output because the input was invalid. exit 1 ends the script with a nonzero status so the caller can tell that the argument was invalid (exit codes).
Match Multiple Conditions or Values with |
In a case pattern list, | means “match any of these patterns.” It is not the same as the || logical operator used between commands.
Several patterns can share one clause when they should run the same commands:
case "${1:-}" in
y|Y|yes|Yes|YES)
echo 'Confirmed.'
;;
n|N|no|No|NO)
echo 'Cancelled.'
;;
*)
echo 'Please answer yes or no.' >&2
exit 1
;;
esacSave this example as yesno.sh and run:
bash yesno.sh yesConfirmed.You can shorten case variations with bracket patterns:
[Yy]|[Yy][Ee][Ss])Bracket patterns such as [Yy] match one character from the set. [Yy][Ee][Ss] matches every uppercase/lowercase combination of yes, including yes, Yes, yEs, and YES, without changing shell options. For interactive prompts, pair case with Bash read.
Use Wildcards and Glob Patterns
case uses Bash shell pattern matching, often called glob-style matching. Patterns such as *.log, ?, and [0-9] match text; they are not regular expressions.
filename="${1:-}"
case "$filename" in
*.tar.gz|*.tgz)
echo 'Extract with tar -xzf'
;;
*.zip)
echo 'Extract with unzip'
;;
*.log)
echo 'Tail or archive log file'
;;
*)
echo "Unknown extension: $filename"
;;
esacSave this as ext.sh, then test a .tar.gz filename:
bash ext.sh backup.tar.gzExtract with tar -xzfThe file does not need to exist for this example. case is only matching the text stored in filename.
Glob patterns
* matches zero or more characters, so by itself it matches any string, including an empty one.
| Pattern | Meaning | Example |
|---|---|---|
* |
Zero or more characters | *.log matches app.log |
? |
Exactly one character | file?.txt matches file1.txt |
[abc] |
One listed character | [Yy] matches Y or y |
[0-9] |
One digit | file[0-9] matches file7 |
[a-z] |
One character from a range | [a-z]* matches text beginning with a lowercase letter |
To make several patterns run the same clause, separate them with |:
start|stop|restart)Default * Branch and Exit Status
The *) clause runs when no earlier pattern matched. Use it for usage text and exit 1 when an invalid argument should fail the script.
When a pattern matches, the exit status of case is the status of the last command executed in that clause. If no pattern matches, the status is 0.
The false builtin normally returns status 1, but in this example its branch never runs. With no *) branch, a non-matching value still leaves status 0:
case bad in
ok) false ;;
esac
echo $?0Here the value is bad, so the ok) branch is never executed. Since no pattern matches, Bash returns 0 for the case statement.
Add *) with exit 1 when unknown input should fail so callers see a nonzero status.
Case-Insensitive Matching
Bracket patterns such as [Yy]|[Yy][Ee][Ss] handle a few common spellings without changing shell options. When many patterns need case-insensitive matching, enable nocasematch instead.
By default, pattern yes does not match the value YES:
case 'YES' in
yes) echo 'matched' ;;
*) echo 'no match' ;;
esacno matchnocasematch is a Bash shell option that tells pattern matching to ignore uppercase and lowercase differences. shopt -s enables the option; shopt -u disables it.
nocasematch stays enabled in the current Bash session until you disable it, so turn it off afterward if later matching should remain case-sensitive:
shopt -s nocasematch
case 'YES' in
yes) echo 'matched (any case)' ;;
esac
shopt -u nocasematchmatched (any case);; vs ;& vs ;;&
Most scripts use only ;;, which stops after the first matching clause. Think of them this way: ;; = stop, ;& = run next, ;;& = test again.
| Terminator | Simple meaning | Behavior |
|---|---|---|
;; |
Stop | End this clause and stop matching (default) |
;& |
Run next | Run this clause, then run the next clause without testing its pattern |
;;& |
Test again | Run this clause, then continue testing later patterns |
;& runs the next clause without testing its pattern:
case a in
a)
echo 'clause a'
;&
b)
echo 'clause b'
;;
*)
echo 'default'
;;
esacclause a
clause bAfter a) runs, ;;& tells Bash to test the remaining patterns. b) does not match a, but *) matches every value, so the default clause also runs:
case a in
a)
echo 'clause a'
;;&
b)
echo 'clause b'
;;
*)
echo 'default'
;;
esacclause a
defaultParse Script Subcommands with case
For scripts with verbs such as create and delete, treat $1 as the command and $2 as the associated argument. For ./tool.sh create demo, $1 is create and $2 is demo. Match help aliases in one clause, check required arguments, then run the commands for the selected subcommand:
#!/usr/bin/env bash
cmd="${1:-}"
name="${2:-}"
case "$cmd" in
-h|--help|help)
echo "Usage: $0 {create|delete|list} [name]"
;;
create)
if [[ -z "$name" ]]; then
echo "Usage: $0 create NAME" >&2
exit 1
fi
echo "Creating $name"
;;
delete)
if [[ -z "$name" ]]; then
echo "Usage: $0 delete NAME" >&2
exit 1
fi
echo "Deleting $name"
;;
list)
echo 'Listing items...'
;;
*)
echo "Unknown command: $cmd" >&2
exit 1
;;
esacSave as tool.sh, make it executable, and test the create subcommand:
chmod +x tool.sh
./tool.sh create demoCreating demoAn unknown verb hits the default branch:
./tool.sh unknownUnknown command: unknownFor short flags such as -f and -v, use Bash getopts and dispatch each option letter with an inner case.
case vs if/elif
| Situation | Prefer |
|---|---|
Match $1 to start, stop, or help |
case |
| Match one value against several wildcard patterns | case |
Test files with -f or -d |
Bash if else |
| Many fixed string literals | case |
Numeric ranges or && / || tests |
if |
The same service verbs in if form:
action="${1:-}"
if [[ "$action" == start ]]; then
echo 'Starting...'
elif [[ "$action" == stop ]]; then
echo 'Stopping...'
else
echo "Usage: $0 {start|stop}" >&2
exit 1
ficase is usually clearer than a long if/elif chain when one value must match several strings or shell patterns.
case Uses Globs, Not Regex
case uses Bash shell pattern matching, not regular expressions. *.log means “any string ending in .log” as a text pattern, not a regex.
With case:
case "$name" in
foo*) echo 'starts with foo' ;;
esacWith regex:
if [[ "$name" =~ ^foo ]]; then
echo 'starts with foo'
fiBoth can solve this example, but they use different pattern languages. ^foo does not mean “starts with foo” in a case pattern; ^ is treated literally. For regular-expression matching, use Bash's [[ string =~ regex ]] inside if, not case — see Bash regex for =~ details. case also does not perform numeric comparisons: pattern 10 matches the text 10; use if with arithmetic tests for numeric ranges.
Common Pattern-Matching Mistakes
| Mistake | Fix |
|---|---|
Forgetting ;; |
End each clause with ;; or an alternate terminator |
Using $1 without handling a missing argument |
Use ${1:-} and handle an empty or invalid value in *) |
Expecting regex in case |
Use globs in case; use =~ in if for regex |
Using case to test whether a file exists |
Use if [[ -f "$path" ]] |
Leaving nocasematch on globally |
Run shopt -u nocasematch after the block |
| Assuming no match is an error | Add *) with exit 1 when invalid input should fail |
Long elif chains for simple verbs |
Replace with case for easier review |
Summary
The Bash case statement matches one value against shell patterns and runs the first matching clause. List multiple inputs with |, classify text with wildcard patterns, and handle unknown input in a *) default branch with a meaningful exit code when scripts must fail on bad arguments. When a clause matches, case returns that clause's last command status; with no match and no *, the status is 0.
Use nocasematch briefly when case-insensitive matching is required. Reserve ;& and ;;& for rare fall-through needs. Prefer case for subcommand verbs; reach for if when you need file tests, numeric ranges, or regex. Pair positional-argument parsing with getopts when the script must parse flags.
References
- GNU Bash Manual — Conditional Constructs (
case) —;;,;&,;;&, and matching rules - GNU Bash Manual — Pattern Matching — glob behavior used by
case - GNU Bash Manual —
shopt(nocasematch)
Frequently Asked Questions
Can case use regex?
No. case patterns are globs. Use [[ $var =~ pattern ]] inside if when you need regular-expression matching.
How do I match multiple values in one clause?
Separate patterns with | before the closing ), for example start|stop|restart) or [Yy]|[Yy][Ee][Ss]). The | separates patterns in the list; it is not a glob wildcard.
Does Bash have a switch statement?
Bash has no switch keyword. Use case ... in ... esac when one value must be matched against several strings or patterns.
When is case better than if?
Use case when one variable must match many fixed strings or globs. Use if for file tests, numeric comparisons, regex, or compound conditions with && and ||.

