Bash case Statement with Examples

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
Short on time?Beta Get the gist of this 10-minute read in seconds, then ask follow-up questions

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

text
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 *):

bash
#!/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
    ;;
esac

Save the script as service.sh, then make it executable:

bash
chmod +x service.sh

Run start:

bash
./service.sh start
output
Starting the service...

An invalid verb uses the default branch:

bash
./service.sh pause
output
Usage: ./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:

bash
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
    ;;
esac

Save this example as yesno.sh and run:

bash
bash yesno.sh yes
output
Confirmed.

You can shorten case variations with bracket patterns:

text
[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.

bash
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"
    ;;
esac

Save this as ext.sh, then test a .tar.gz filename:

bash
bash ext.sh backup.tar.gz
output
Extract with tar -xzf

The 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 |:

text
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:

bash
case bad in
  ok) false ;;
esac
echo $?
output
0

Here 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:

bash
case 'YES' in
  yes) echo 'matched' ;;
  *)   echo 'no match' ;;
esac
output
no match

nocasematch 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:

bash
shopt -s nocasematch

case 'YES' in
  yes) echo 'matched (any case)' ;;
esac

shopt -u nocasematch
output
matched (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:

bash
case a in
  a)
    echo 'clause a'
    ;&
  b)
    echo 'clause b'
    ;;
  *)
    echo 'default'
    ;;
esac
output
clause a
clause b

After a) runs, ;;& tells Bash to test the remaining patterns. b) does not match a, but *) matches every value, so the default clause also runs:

bash
case a in
  a)
    echo 'clause a'
    ;;&
  b)
    echo 'clause b'
    ;;
  *)
    echo 'default'
    ;;
esac
output
clause a
default

Parse 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:

bash
#!/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
    ;;
esac

Save as tool.sh, make it executable, and test the create subcommand:

bash
chmod +x tool.sh
./tool.sh create demo
output
Creating demo

An unknown verb hits the default branch:

bash
./tool.sh unknown
output
Unknown command: unknown

For 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:

bash
action="${1:-}"

if [[ "$action" == start ]]; then
  echo 'Starting...'
elif [[ "$action" == stop ]]; then
  echo 'Stopping...'
else
  echo "Usage: $0 {start|stop}" >&2
  exit 1
fi

case 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:

bash
case "$name" in
  foo*) echo 'starts with foo' ;;
esac

With regex:

bash
if [[ "$name" =~ ^foo ]]; then
  echo 'starts with foo'
fi

Both 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


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 ||.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)