Chapter 04Lesson 05~65 minutes

case Patterns and Defensive Branching

Long chains of string comparisons often signal that a script needs a multi-way dispatch construct. Bash `case` matches one word against ordered shell patterns and gives each branch a clear block, making it ideal for environment routing, command dispatch, file handling, and defensive input validation.

BeginnerConditionsHands-on lab

Learning objectives

By the end of this lesson

  • Write case statements with exact and pattern branches.
  • Combine multiple alternatives in one pattern list.
  • Understand ;;, ;&, and ;;& termination behavior.
  • Use case for subcommand and environment dispatch.
  • Design explicit default branches that reject unknown states.

1. case performs ordered pattern dispatch

A case statement expands one word, then compares it against branch patterns in order. The first matching branch runs under normal ;; termination.

environment=${1:-}

case $environment in
  dev)
    printf 'development\n'
    ;;
  staging)
    printf 'staging\n'
    ;;
  prod)
    printf 'production\n'
    ;;
  *)
    printf 'unsupported environment: %s\n' "$environment" >&2
    exit 2
    ;;
esac
case dispatch
flowchart TB
  I["input word"] --> D{"first matching case pattern"}
  D --> A["branch actions"]
  A --> E["esac / continue script"]
  D --> X["default * branch if provided"]

2. Branches use shell patterns, not regular expressions

case patterns use shell pattern syntax. Wildcards such as * and ? are often enough for routing filenames and identifiers.

file="service-prod.yaml"

case $file in
  *.yaml|*.yml)
    printf 'YAML configuration\n'
    ;;
  *.json)
    printf 'JSON configuration\n'
    ;;
  *)
    printf 'unsupported configuration type\n' >&2
    ;;
esac

The vertical bar inside a pattern list means “any of these patterns.” It is not a data pipeline in this syntactic position.

3. Quote data, not the wildcard you intend to match

Patterns are syntax. Quoting wildcard characters makes them literal.

value="release-2026"

case $value in
  release-*)
    printf 'release identifier matched\n'
    ;;
esac

If part of a pattern comes from data, be explicit about whether that variable should contribute pattern metacharacters or literal text. Defensive scripts avoid letting untrusted input unexpectedly become pattern syntax.

4. Combine semantic aliases in one branch

Multiple patterns can route to the same implementation without duplicating code.

answer=${1:-}

case $answer in
  y|Y|yes|YES|Yes)
    printf 'confirmed\n'
    ;;
  n|N|no|NO|No)
    printf 'declined\n'
    ;;
  *)
    printf 'expected yes or no\n' >&2
    exit 64
    ;;
esac

For user-facing interfaces, you may instead normalize case first with parameter expansion and keep fewer alternatives.

5. A defensive * branch makes unsupported state explicit

If the accepted input set is closed—such as deployment environments, subcommands, or modes—include a default branch that rejects everything else.

case $mode in
  plan|apply|destroy)
    ;;
  *)
    printf 'invalid mode: %s\n' "$mode" >&2
    exit 64
    ;;
esac
Fail closed

For destructive or privileged operations, unknown modes should normally be rejected rather than silently mapped to a permissive default.

6. case is a natural CLI subcommand dispatcher

Many shell utilities grow from one action into several subcommands. A top-level case keeps routing obvious.

usage() {
  printf 'usage: %s {status|deploy|rollback} ...\n' "$0"
}

command_name=${1:-}
shift || true

case $command_name in
  status)
    show_status "$@"
    ;;
  deploy)
    deploy "$@"
    ;;
  rollback)
    rollback "$@"
    ;;
  help|-h|--help|'')
    usage
    ;;
  *)
    printf 'unknown command: %s\n' "$command_name" >&2
    usage >&2
    exit 64
    ;;
esac

Later lessons will cover argument parsing in more depth. The key design point here is separation between routing and implementation.

7. Bash offers three branch terminators

TerminatorBehaviorAdvice
;;Stop after the matching branchNormal and most common behavior
;&Execute the next branch's commands without testing its patternIntentional fall-through
;;&Continue testing later patternsAllows multiple matching branches

The nonstandard fall-through forms are Bash features and can surprise readers. Use them only when they make the logic clearer than explicit function calls or separate tests.

value="api-prod"

case $value in
  api-*)
    printf 'api family\n'
    ;;&
  *-prod)
    printf 'production suffix\n'
    ;;
esac

8. Extended patterns can increase power—and complexity

When Bash's extglob option is enabled, richer patterns such as @(...), +(...), and !(...) become available in relevant pattern contexts.

shopt -s extglob

case $environment in
  @(dev|staging|prod))
    printf 'known environment\n'
    ;;
  *)
    printf 'unknown environment\n'
    ;;
esac

For a small closed set, ordinary dev|staging|prod is simpler. Use extended patterns when they materially improve the rule rather than merely showcasing syntax.

9. case can model a small explicit state machine

Operational scripts sometimes need to branch on observed service state. A case statement can make allowed transitions explicit.

case $state in
  stopped)
    action="start"
    ;;
  running)
    action="verify"
    ;;
  degraded)
    action="investigate"
    ;;
  failed)
    action="recover"
    ;;
  *)
    printf 'unknown service state: %s\n' "$state" >&2
    exit 2
    ;;
esac

printf 'next action=%s\n' "$action"

Do not hide a complex distributed state machine inside one shell file. But for small local orchestration decisions, explicit states are often clearer than nested Boolean expressions.

10. Pattern dispatch is useful for artifact handlers

A packaging script may need different commands based on file suffix. case keeps those mappings in one place.

archive=${1:-}

case $archive in
  *.tar.gz|*.tgz)
    printf 'tar+gzip archive\n'
    ;;
  *.tar.xz)
    printf 'tar+xz archive\n'
    ;;
  *.zip)
    printf 'zip archive\n'
    ;;
  *)
    printf 'unsupported archive: %s\n' "$archive" >&2
    exit 65
    ;;
esac
Do not trust extensions as content validation

A filename suffix is routing metadata, not proof of file format or safety. Validate content where correctness or security requires it.

11. Hands-on lab: build a defensive service command router

Create a mini CLI with four supported subcommands and a closed set of environments. Unknown commands and environments must fail.

mkdir -p "$HOME/devops-academy/bash/chapter04/lesson05"
cd "$HOME/devops-academy/bash/chapter04/lesson05"

cat > svc.sh <<'EOF'
#!/usr/bin/env bash

subcommand=${1:-help}
environment=${2:-dev}

case $environment in
  dev|staging|prod)
    ;;
  *)
    printf 'invalid environment: %s\n' "$environment" >&2
    exit 64
    ;;
esac

case $subcommand in
  status)
    printf 'STATUS env=%s\n' "$environment"
    ;;
  plan)
    printf 'PLAN env=%s\n' "$environment"
    ;;
  deploy)
    if [[ $environment == prod ]]; then
      printf 'DEPLOY env=prod requires external approval check\n'
    else
      printf 'DEPLOY env=%s\n' "$environment"
    fi
    ;;
  rollback)
    printf 'ROLLBACK env=%s\n' "$environment"
    ;;
  help|-h|--help)
    printf 'usage: %s {status|plan|deploy|rollback} {dev|staging|prod}\n' "$0"
    ;;
  *)
    printf 'unknown subcommand: %s\n' "$subcommand" >&2
    exit 65
    ;;
esac
EOF

bash svc.sh status dev
bash svc.sh plan staging
bash svc.sh deploy prod
bash svc.sh unknown dev || true
bash svc.sh status qa || true

Verification checklist

12. Knowledge check

Question 1. Does case use regular expressions by default?

Question 2. What does * commonly represent as the final case pattern?

Question 3. What is the normal branch terminator?

Question 4. Why is an explicit default branch important for destructive command routing?

13. Summary

case is Bash's readable multi-way dispatcher. It matches ordered shell patterns, groups aliases naturally, and makes closed input sets easy to validate. Prefer ordinary ;; branches and explicit catch-all rejection for operational scripts; use fall-through and extended patterns only when they make the rule genuinely clearer.

14. Further reading

  • GNU Bash Reference Manual — Conditional Constructs: case.
  • GNU Bash Reference Manual — Pattern Matching and extglob.
  • POSIX Shell Command Language — case conditional construct.
  • ShellCheck documentation — pattern and case-statement diagnostics.
Next lesson

for Loops over Words, Files, and Sequences

Chapter 5 will move from branching into loops, iteration, and repeated automation workflows.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.