How to Use Case Statements in Bash

How to Use Case Statements in Bash

How to Use Case Statements in Bash

When I first started writing Bash scripts, I leaned on long chains of if/elif/else for everything. It worked, but once I had more than three or four conditions, my scripts turned into a wall of nested logic that was painful to read and even more painful to debug. Switching to case statements changed that completely. In this article I’ll walk through everything I’ve learned about using case in Bash, from the absolute basics to advanced pattern matching tricks I use in production scripts today.

What Is a Case Statement?

A case statement in Bash is a way to match a single value against a list of patterns and run different code depending on which pattern matches. It’s conceptually similar to switch statements in languages like C, Java, or JavaScript, but with Bash’s own glob-style pattern matching instead of strict equality checks.

The basic syntax looks like this:

case "$variable" in
    pattern1)
        # commands
        ;;
    pattern2)
        # commands
        ;;
    *)
        # default case
        ;;
esac

Every case block starts with case and ends with esac (which is just case spelled backward — a Bash convention shared with if/fi and done).

A Beginner Example

Let’s say I want a script that greets a user differently depending on the day of the week:

#!/usr/bin/env bash

day=$(date +%A)

case "$day" in
    Monday)
        echo "Ugh, Monday. Let's get through it."
        ;;
    Friday)
        echo "It's Friday! Almost the weekend."
        ;;
    Saturday|Sunday)
        echo "Weekend mode activated."
        ;;
    *)
        echo "Just another $day."
        ;;
esac

Running this on a Wednesday gives:

Just another Wednesday.

Notice the Saturday|Sunday) line — the pipe character lets me match multiple patterns against the same block of code, which is one of the reasons I prefer case over long if/elif chains for this kind of logic.

How It Works Internally

I always quote the variable being tested ("$variable") to avoid word-splitting issues if the value contains spaces.

Pattern Matching: Where Case Statements Really Shine

Unlike a simple equality check, case supports glob-style wildcards, which makes it powerful for parsing input, file extensions, or command-line arguments.

#!/usr/bin/env bash

filename="report.tar.gz"

case "$filename" in
    *.tar.gz|*.tgz)
        echo "This is a gzipped tarball."
        ;;
    *.zip)
        echo "This is a zip archive."
        ;;
    *.txt)
        echo "This is a plain text file."
        ;;
    *)
        echo "Unknown file type."
        ;;
esac

Here, *.tar.gz uses the * wildcard to match any filename ending in .tar.gz, regardless of what comes before it.

Real-World Use Case: A Command-Line Argument Parser

One of the most common places I use case is parsing arguments in scripts, often combined with a while loop:

#!/usr/bin/env bash

ACTION=""
VERBOSE=false

while [[ $# -gt 0 ]]; do
    case "$1" in
        start|stop|restart)
            ACTION="$1"
            shift
            ;;
        -v|--verbose)
            VERBOSE=true
            shift
            ;;
        -h|--help)
            echo "Usage: $0 [start|stop|restart] [-v]"
            exit 0
            ;;
        *)
            echo "Unknown argument: $1" >&2
            exit 1
            ;;
    esac
done

echo "Action: $ACTION, Verbose: $VERBOSE"

Running ./script.sh restart -v outputs:

Action: restart, Verbose: true

This pattern — a while loop combined with case and shift — is the backbone of nearly every serious command-line tool I write in Bash.

Another Real-World Use Case: A Service Control Script

case statements are the classic building block of System V init scripts, and I still use the same pattern for small custom service wrappers:

#!/usr/bin/env bash

SERVICE="myapp"

case "$1" in
    start)
        echo "Starting $SERVICE..."
        systemctl start "$SERVICE"
        ;;
    stop)
        echo "Stopping $SERVICE..."
        systemctl stop "$SERVICE"
        ;;
    restart)
        echo "Restarting $SERVICE..."
        systemctl restart "$SERVICE"
        ;;
    status)
        systemctl status "$SERVICE"
        ;;
    *)
        echo "Usage: $0 {start|stop|restart|status}"
        exit 1
        ;;
esac

Advanced Pattern Matching Techniques

Bash’s case supports several pattern types worth knowing:

Example using ;&:

case "$1" in
    1)
        echo "Case 1"
        ;&
    2)
        echo "Case 2 (falls through from 1)"
        ;;
esac

Running with 1 prints both “Case 1” and “Case 2”, because ;& deliberately falls through.

Best Practices I Follow

Security Considerations

case statements themselves are safe by nature since they only compare strings and don’t execute arbitrary code from the pattern. That said:

Optimization Tips

Troubleshooting

Common Mistakes to Avoid

FAQs

Can case compare numbers directly, like case "$n" in 5) ... ;;? Yes, for exact matches this works fine since it’s really just string comparison, but for ranges you need character classes or a different construct like if [[ "$n" -gt 5 ]].

Is case faster than if/elif? For a small number of conditions, the difference is negligible. For many conditions, case is usually more readable and can be marginally faster.

Can I use regular expressions in case? No, case uses glob patterns, not full regular expressions. For true regex matching, use [[ "$var" =~ pattern ]] instead.

Does case work in sh as well as Bash? Yes, case is part of the POSIX shell specification, so it works in sh, dash, and other POSIX-compliant shells, not just Bash.

Summary

The case statement is one of the most underused tools I see in beginner Bash scripts, yet it’s one of the cleanest ways to handle multi-branch logic, argument parsing, and pattern-based decisions. Once you get comfortable with wildcards, alternation, and fallthrough operators, you’ll find yourself reaching for case instead of long if/elif chains almost every time.

References

Exit mobile version