Shell Script Case Statements Made Simple Part 12 / 34)






Bash Case Statement with Examples — Shell Script Case Statements Made Simple | LinuxTeck


You may be familiar with the challenge of having to use multiple if, elif, and else, blocks when checking for many different cases in one shell script. The bash case statement with examples provides a much simpler and easier-to-read method to check for multiple possibilities within a single block. As either an administrator creating service control scripts or as a developer (DevOps) processing command line input arguments, using bash case esac syntax makes your scripts shorter and easier to keep up to date.

Why This Guide Is Different:

Most articles out there only show toy examples. This guide covers what they miss:

  • Real-world service control scripts (start / stop / restart / status)
  • Fall-through operators ;& and ;;& that no one explains clearly
  • CLI argument parsing the way DevOps engineers actually write it
  • A full troubleshooting section with the exact errors beginners hit
  • Distro-specific notes for Ubuntu 22.04/24.04 and Rocky Linux 8/9

By the end you will know exactly when to use case over if, and how to write production-quality scripts.

Before diving in, if you are completely new to shell scripting, the beginner introduction to bash scripting on LinuxTeck gives you a solid foundation to build on.

#01

What Is the Bash Case Statement with Examples and How Does It Work

A bash case statement evaluates one variable or expression against a list of patterns. When a pattern matches, the block of commands below it runs. Then the script jumps past the esac keyword. If nothing matches, the optional default *) block runs instead.

Here is the basic syntax you need to memorise:

bash
LinuxTeck.com
# Basic case...esac syntax
case $variable in
pattern1)
commands
;;
pattern2)
commands
;;
# Match multiple patterns with pipe
pattern3 | pattern4)
commands
;;
# Default / catch-all
*)
default commands
;;
esac

Key things to understand right away. The word esac is just "case" spelled backwards. The double semicolons ;; act like a break statement: they stop execution and jump to the end of the case block. The pipe | lets you match more than one value in the same clause.

Note:

The variable you test inside case does not need to be quoted in Bash, but quoting it is a good habit. Writing case "$variable" in protects against unexpected word splitting when the value has spaces. You can read more about how variables work in the bash PATH and variable lookup guide.

#02

Case vs If-Elif: When to Use Which One

One question that comes up a lot is: when should you pick case over a chain of if/elif blocks? The honest answer is that both work, but case has real advantages in specific situations.

Scenario Use case Use if/elif
Matching one variable against many fixed values Yes No
Checking numeric ranges like greater than / less than No Yes
Parsing CLI arguments ($1, $2) Yes Either
Menu-driven interactive scripts Yes No
Complex boolean logic with AND / OR conditions No Yes
5 or more branches on the same variable Yes - cleaner Gets messy fast

There is also a performance reason. When Bash evaluates an if/elif chain, it runs each condition test in order until one is true. With a case statement, the shell uses pattern matching internally which is more efficient for long lists. On a server running hundreds of cron jobs, this kind of small saving does add up over time.

Performance Note:

For scripts with 10 or more branches on the same variable, case is meaningfully faster than chained if/elif. Bash does not re-evaluate the variable on each branch the way if does. For most scripts the difference is tiny, but in tight loops or frequently called wrapper scripts it is worth knowing. See the bash scripting automation guide for 2026 for more on writing efficient scripts.

Below is a side-by-side comparison. Both scripts do the same thing. Notice how much harder the if/elif version is to scan:

bash
LinuxTeck.com
#!/bin/bash
# if/elif version -- harder to read
DAY=$(date +%A)
if [[ $DAY == "Monday" ]]; then
echo "Start of week tasks"
elif [[ $DAY == "Wednesday" ]]; then
echo "Mid-week check"
elif [[ $DAY == "Friday" ]]; then
echo "Weekly report"
else
echo "Normal day"
fi
bash
LinuxTeck.com
#!/bin/bash
# case version -- same result, much cleaner
DAY=$(date +%A)
case $DAY in
Monday)
echo "Start of week tasks"
;;
Wednesday)
echo "Mid-week check"
;;
Friday)
echo "Weekly report"
;;
*)
echo "Normal day"
;;
esac

Want to go deeper on if before you come back to case? The bash if elif else guide with examples covers every condition operator you might need.

#03

Real-World Example: Service Control Script (Start / Stop / Restart)

This is the example that sysadmins search for most often. Writing a wrapper script that manages a service using case is one of the most practical uses you will see in production. The script below mimics how systemctl wrapper tools are built in real environments.

bash
LinuxTeck.com
#!/bin/bash
# service-control.sh -- manages nginx via case
SERVICE="nginx"
ACTION="$1"

if [ -z "$ACTION" ]; then
echo "Usage: $0 {start|stop|restart|status|reload}"
exit 1
fi

case "$ACTION" in
start)
echo "Starting $SERVICE..."
systemctl start "$SERVICE"
echo "$SERVICE started successfully."
;;
stop)
echo "Stopping $SERVICE..."
systemctl stop "$SERVICE"
echo "$SERVICE stopped."
;;
restart)
echo "Restarting $SERVICE..."
systemctl restart "$SERVICE"
;;
reload)
echo "Reloading $SERVICE config..."
systemctl reload "$SERVICE"
;;
status)
systemctl status "$SERVICE"
;;
*)
echo "Unknown action: $ACTION"
exit 2
;;
esac
exit 0

OUTPUT
$ ./service-control.sh start
Starting nginx...
nginx started successfully.

$ ./service-control.sh boom
Unknown action: boom
Usage: ./service-control.sh {start|stop|restart|status|reload}

Exit Code Behavior:

Notice how the script uses exit 1 for missing arguments and exit 2 for unknown actions. These are distinct exit codes that calling scripts or CI/CD pipelines can check with $?. Exit code 0 means success. Any non-zero value signals a problem. This matters in production where your pipeline needs to know exactly what went wrong. Learn more about bash exit codes and error handling.

#04

CLI Argument Parsing with $1 - The DevOps Way

When you write automation scripts that other tools call, parsing arguments cleanly is essential. The shell script case statement real world example below shows how a deployment script might use $1 to decide what to do. This pattern is used constantly in CI/CD pipelines and Ansible wrapper scripts.

bash
LinuxTeck.com
#!/bin/bash
# deploy.sh -- DevOps CLI argument parser
ENV="$1"
VERSION="$2"

case "$ENV" in
dev | development)
echo "Deploying version $VERSION to DEV"
rsync -av ./build/ dev-server:/var/www/app/
;;
staging)
echo "Deploying version $VERSION to STAGING"
rsync -av ./build/ staging-server:/var/www/app/
;;
prod | production)
echo "WARNING: Deploying $VERSION to PRODUCTION"
read -p "Are you sure? [yes/no]: " CONFIRM
case "$CONFIRM" in
yes | YES)
rsync -av ./build/ prod-server:/var/www/app/
echo "Production deploy done."
;;
*)
echo "Deploy cancelled."
exit 1
;;
esac
;;
--help | -h)
echo "Usage: $0 {dev|staging|prod} VERSION"
exit 0
;;
*)
echo "Unknown environment: $ENV"
exit 1
;;
esac

For proper flag-style argument parsing (like -e prod or --version 1.2), combine case with a while loop and shift. This is the standard pattern used in real CLI tools:

bash
LinuxTeck.com
#!/bin/bash
# flag-parser.sh -- handles -e prod -v 1.2.0

while [[ $# -gt 0 ]]; do
case "$1" in
-e | --env)
ENV="$2"
shift 2
;;
-v | --version)
VERSION="$2"
shift 2
;;
-h | --help)
echo "Usage: $0 -e ENV -v VERSION"
exit 0
;;
*)
echo "Unknown flag: $1"
exit 1
;;
esac
done

echo "Deploying $VERSION to $ENV"

OUTPUT
$ ./flag-parser.sh -e prod -v 1.2.0
Deploying 1.2.0 to prod

$ ./flag-parser.sh --env staging --version 2.0.1
Deploying 2.0.1 to staging

This example also shows a nested case statement inside the production branch. Bash allows nesting without any issues. The outer case handles the environment, the inner case handles the confirmation prompt. For scripts that accept user input like this, the bash read command examples guide has everything you need to handle input safely.

#05

Fall-Through with ;& and ;;& (The Feature Nobody Explains)

Bash 4.0 introduced two extra terminators beyond the standard ;;. These give you fall-through behaviour, similar to how switch statements work in C or Java when you leave out a break.

Here is what each terminator does:

  • ;; - normal: run the matching block and jump to esac
  • ;& - fall-through: run the matching block AND automatically run the next block without testing its pattern
  • ;;& - continue matching: run the matching block AND keep testing remaining patterns for more matches

Bash Version Check:

Both ;& and ;;& require Bash 4.0 or newer. To check your version, run bash --version. Ubuntu 22.04/24.04 ships with Bash 5.1, Rocky Linux 8 ships with Bash 4.4, and Rocky Linux 9 ships with Bash 5.1. macOS still ships with Bash 3.2 by default, so avoid these terminators in cross-platform scripts without checking first.

You can also check the installed bash automation scripts setup on the shell scripting environment setup guide.

Ubuntu 22.04 - Bash 5.1
Ubuntu 24.04 - Bash 5.2
Rocky Linux 8 - Bash 4.4
Rocky Linux 9 - Bash 5.1
RHEL 8/9 - Bash 4.4 / 5.1
Bash 5.x - all features supported

Here is a working example of ;& fall-through, using a log severity classifier:

bash
LinuxTeck.com
#!/bin/bash
# Fall-through with ;&
# CRITICAL also triggers ERROR and WARN actions
SEVERITY="$1"

case "$SEVERITY" in
CRITICAL)
echo "[CRITICAL] Paging on-call engineer..."
;&
ERROR)
echo "[ERROR] Writing to error log..."
;&
WARN)
echo "[WARN] Sending Slack notification..."
;;
INFO)
echo "[INFO] Writing to access log only."
;;
*)
echo "Unknown severity: $SEVERITY"
;;
esac

OUTPUT
$ ./alert.sh CRITICAL
[CRITICAL] Paging on-call engineer...
[ERROR] Writing to error log...
[WARN] Sending Slack notification...

$ ./alert.sh WARN
[WARN] Sending Slack notification...

$ ./alert.sh INFO
[INFO] Writing to access log only.

Now here is ;;&, which is different. Instead of blindly running the next block, it keeps checking the remaining patterns:

bash
LinuxTeck.com
#!/bin/bash
# ;;& -- continue matching after a hit
INPUT="$1"

case "$INPUT" in
*[0-9]*)
echo "Input contains a digit"
;;&
*[a-z]*)
echo "Input contains a lowercase letter"
;;&
*[A-Z]*)
echo "Input contains an uppercase letter"
;;&
*[[:punct:]]*)
echo "Input contains a special character"
;;&
esac

OUTPUT
$ ./check.sh "Pass1!"
Input contains a digit
Input contains a lowercase letter
Input contains an uppercase letter
Input contains a special character

$ ./check.sh "hello"
Input contains a lowercase letter

#06

Case-Insensitive Matching with shopt -s nocasematch

By default, Bash pattern matching in case is case-sensitive. So Yes, YES, and yes are three different patterns. One way to handle this is the pipe trick: yes | Yes | YES). But when you have many patterns, this gets repetitive fast.

A cleaner approach is to enable the nocasematch shell option before the case block. This tells Bash to treat uppercase and lowercase letters as identical during pattern matching.

bash
LinuxTeck.com
#!/bin/bash
# nocasematch example -- user-friendly confirm prompt
shopt -s nocasematch

read -p "Do you want to continue? [yes/no]: " ANSWER

case "$ANSWER" in
yes | y)
echo "Continuing..."
;;
no | n)
echo "Aborting."
exit 0
;;
*)
echo "Please type yes or no."
exit 1
;;
esac

# Always turn it off when done to avoid side effects
shopt -u nocasematch

OUTPUT
Do you want to continue? [yes/no]: YES
Continuing...

Do you want to continue? [yes/no]: No
Aborting.

Alternative Without shopt:

If you do not want to use shopt, use bracket patterns instead. This catches mixed-case input like yEs or nO without listing every variant:

bash
LinuxTeck.com
read -p "Continue? [y/n]: " ANSWER

case "$ANSWER" in
[yY] | [yY][eE][sS])
echo "Proceeding..."
;;
[nN] | [nN][oO])
echo "Cancelled."
;;
*)
echo "Invalid input."
;;
esac

#07

Glob Patterns and POSIX Classes in Case Statements

Case patterns are not just plain strings. Bash supports glob-style wildcards inside case patterns, which makes them much more powerful. You can also use POSIX character classes, which are more portable than character ranges like [a-z].

Here is a practical file-type checker that uses both globs and POSIX classes:

bash
LinuxTeck.com
#!/bin/bash
# file-classifier.sh
for FILE in /var/log/*; do
case "$FILE" in
*.log)
echo "Log file: $FILE"
;;
*.gz | *.bz2 | *.xz)
echo "Compressed archive: $FILE"
;;
*.conf | *.cfg)
echo "Config file: $FILE"
;;
*.sh)
echo "Shell script: $FILE"
;;
*)
echo "Other file: $FILE"
;;
esac
done

For more scripting work involving file operations and loops, the find command in linux with examples pairs very well with this pattern.

#08

Troubleshooting: Common Mistakes and How to Fix Them

This section covers the errors that beginners and intermediate users run into most often. Each one is simple to fix once you know what to look for.

Mistake 1: Forgetting ;;:

Missing ;; at the end of a case block causes the next block to run unexpectedly. Bash will either show a syntax error or silently fall into the next pattern.

Wrong: start) echo "starting" with no ;; on the next line.

Fix: Always end every case block with ;; unless you deliberately want fall-through behaviour with ;& or ;;&.

Mistake 2: Pattern Quoting Issues:

Quoting patterns incorrectly breaks matching. Do not quote the pattern itself, only the variable being tested.

Wrong: case "$VAR" in "start") - this works but is unusual and confuses some people into thinking both sides need quotes.

Right: case "$VAR" in start) - quote the variable, not the literal pattern.

Exception: If the pattern itself contains spaces, then you do need to quote it: "start server").

Mistake 3: Wrong Placement of *) Catch-All:

The *) default block must come last. If you put it before other patterns, it will match everything and those other patterns will never be reached.

Fix: Always place *) as the final clause before esac.

Mistake 4: Variable Spacing Around [ ]:

This one is not specific to case but often shows up when mixing if guards with case blocks. Inside [ ] conditions, you always need spaces around operators and the variable.

Wrong: if [-z "$VAR"]

Fix: if [ -z "$VAR" ] with spaces after the opening bracket and before the closing one.

Mistake 5: Using ;& or ;;& on Old Bash:

If you use ;& or ;;& on Bash 3.x (like on default macOS), you get a syntax error. Add a version check at the top of any script that uses these features.

Fix: Add if (( BASH_VERSINFO[0] < 4 )); then echo "Bash 4+ required"; exit 1; fi near the top of the script.

For a broader look at writing scripts that handle errors properly and exit cleanly, the bash exit codes and error handling article is worth reading alongside this one.

#09

Interactive Menu Script - Practical Beginner Example

One of the most beginner-friendly uses of bash case statement with examples is building a simple interactive menu. This is a good starting script to learn from because you can run it, see the output immediately, and understand every line.

bash
LinuxTeck.com
#!/bin/bash
# system-menu.sh -- interactive system info menu
clear
echo "=============================="
echo " Linux System Info Menu "
echo "=============================="
echo "1) Show disk usage"
echo "2) Show memory usage"
echo "3) Show running processes"
echo "4) Show current uptime"
echo "5) Show network interfaces"
echo "6) Exit"
echo
read -p "Choose an option [1-6]: " CHOICE

case "$CHOICE" in
1)
echo
df -h
;;
2)
echo
free -h
;;
3)
echo
ps aux --sort=-%cpu | head -15
;;
4)
echo
uptime
;;
5)
echo
ip -brief addr show
;;
6)
echo "Goodbye!"
exit 0
;;
*)
echo "Invalid option: $CHOICE. Please choose 1-6."
exit 1
;;
esac

Save this as system-menu.sh, make it executable with chmod +x system-menu.sh, and run it with ./system-menu.sh. This works on Ubuntu, Rocky Linux, RHEL, and any system running Bash 4+. If you want to build on this with a looping menu so it re-shows after each selection, combine it with a while true loop. The interactive shell scripts guide walks through that pattern in detail.

#10

Bash 5.x Compatibility Notes (2025 / 2026 Update)

Bash 5.0 was released in early 2019 and Bash 5.2 in late 2022. As of 2026, most major Linux distributions have moved to Bash 5.x, which means you can rely on ;& and ;;& being available on any modern server.

Here is a quick version check you can add to any production script:

bash
LinuxTeck.com
#!/bin/bash
# Bash version guard -- add to scripts using ;& or ;;&
if (( BASH_VERSINFO[0] < 4 )); then
echo "Error: This script requires Bash 4.0 or higher."
echo "Your version: $BASH_VERSION"
exit 1
fi

echo "Bash $BASH_VERSION detected -- all features available."

OUTPUT
Bash 5.1.16(1)-release detected -- all features available.

For a full look at how bash automation scripts are evolving in modern Linux environments, the Linux bash scripting automation 2026 guide covers current best practices in depth.

FAQ

Frequently Asked Questions

What is the difference between case and if in Bash?

Both case and if are conditional structures, but they work differently. An if statement evaluates a boolean expression (true or false) and supports complex conditions using AND (&&), OR (||), and comparison operators like -gt, -lt.

A case statement matches a single variable or expression against a list of patterns. It cannot do numeric comparisons or test multiple variables at once. However, when you have one variable and five or more possible values, case is much cleaner and faster to read than a chain of elif blocks.

Rule of thumb: use if for conditions, use case for dispatch. Check the full bash if statement complete guide for comparison.

How do you use fall-through in a Bash case statement?

Bash 4.0 and later support two fall-through operators. The ;& terminator causes the script to run the next block automatically without testing its pattern. The ;;& terminator causes the script to keep testing the remaining patterns and run any that also match.

Both require Bash 4.0 or newer. Ubuntu 22.04+, Rocky Linux 9, and RHEL 9 all ship with Bash 5.x, so this is safe on any modern server. For cross-platform scripts, add a Bash version check at the top.

How do you match multiple values in one case block?

Use the pipe | character to separate values inside the same pattern. For example:

bash
LinuxTeck.com
case "$ANSWER" in
yes | Yes | YES | y | Y)
echo "Confirmed"
;;
no | No | NO | n | N)
echo "Cancelled"
;;
esac

Or use shopt -s nocasematch before the case block to avoid listing every capitalisation variant.

What does esac mean and why is it used?

esac is just the word "case" spelled backwards. Bash uses this convention for several block terminators: fi closes if, done closes loops, and esac closes case. It marks the absolute end of the case block and nothing after it belongs to the case statement. You must always have exactly one esac for every case...in that you open.

What exit code does a case statement return?

The exit code returned by a case statement is the exit code of the last command that ran inside the matched block. If no pattern matched, the case statement returns exit code 0. If you need to return a specific code from a case block, use an explicit exit N or return N (inside a function) at the end of that block. You can always check the result with echo $? right after the esac line.

Can you nest case statements inside each other?

Yes. Bash allows fully nested case statements. The outer case closes with its own esac and the inner case closes with its own esac. This is useful when you need a second level of branching within a matched block, such as the production deploy confirmation example in this guide. Keep nesting to two levels at most, or the script becomes hard to read and maintain.

Does a Bash case statement support regex patterns?

No. Bash case statements use glob patterns, not regular expressions. Globs support * (any characters), ? (single character), and character classes like [0-9] and [[:alpha:]]. If you need full regex matching, use an if statement with the =~ operator inside double brackets: if [[ "$VAR" =~ ^[0-9]+$ ]]; then. For everything else, glob patterns in case are usually enough. See Linux shell scripting interview questions for more patterns like this.

END

Summary

The bash case statement with examples in this guide covers everything from the basic case...esac syntax to production scripts used by real sysadmins and DevOps engineers. You have seen how bash case esac syntax compares to if/elif, how to parse CLI arguments with $1, how the fall-through operators ;& and ;;& work in Bash 4+, and how to handle common mistakes that break scripts silently.

For more depth on shell scripting interview topics, the Linux shell scripting interview questions guide is a good next step.

Related Articles

LinuxTeck - A Complete Linux Learning Blog
Learn step-by-step how to automate Linux tasks with real-world scripts and practical examples.


About Sharon J

Sharon J is a Linux System Administrator with strong expertise in server and system management. She turns real-world experience into practical Linux guides on Linux Teck.

View all posts by Sharon J →

Leave a Reply

Your email address will not be published.

L