case statements and glob patterns

Bash · Practical · 10 min

case is the clean way to dispatch on file types, subcommands and options.

Glob patterns match strings and file names: * any run of characters, ? exactly one character, [abc] or [a-z] one character from a set, [!0-9] one not in the set. When used unquoted as arguments, globs expand to matching file names, sorted; hidden files (starting with .) only match if the pattern starts with a dot.

The same patterns drive the case statement, which compares one word against a list of patterns and runs the first match:

case $word in
  pat1|pat2) commands ;;
  *) default ;;
esac

| separates alternatives, ;; ends a branch, and *) is the catch-all. case is cleaner than a chain of elif [[ $x == ... ]] and it is POSIX, so it also works in sh.

Useful shell options via shopt -s:

  • nullglob: unmatched globs expand to nothing instead of themselves,
  • globstar: ** matches across directories, as in **/*.py,
  • dotglob: include hidden files,
  • extglob: extended patterns !(pat), @(a|b), +(pat), *(pat), ?(pat).

Globs are not regular expressions: * alone means "anything", there is no . wildcard, and you need [[ =~ ]] for real regexes.

Example

cd "$(mktemp -d)"
for name in app.tgz main.c 2024.log --help notes.md; do
  case $name in
    *.tar.gz|*.tgz) kind="tarball" ;;
    *.[ch])         kind="C source" ;;
    [0-9]*)         kind="starts with a digit" ;;
    -h|--help)      kind="help flag" ;;
    *)              kind="other" ;;
  esac
  echo "$name: $kind"
done
# app.tgz: tarball / main.c: C source / 2024.log: starts with a digit
# --help: help flag / notes.md: other
shopt -s nullglob extglob globstar
mkdir -p src/lib && touch a.log b.log c.txt src/x.py src/lib/y.py
logs=(*.log); echo "${#logs[@]} logs"   # 2 logs
echo !(*.log|src)                       # c.txt
echo **/*.py                            # src/lib/y.py src/x.py
none=(*.zip); echo "${#none[@]}"        # 0  (thanks to nullglob)

Remember

  • * any string, ? one char, [a-z] one char from a set
  • case word in pat|pat) ... ;; esac picks the first matching pattern
  • shopt -s nullglob, globstar and extglob unlock safer, richer globs

Watch out for

  • Treating globs as regexes, e.g. expecting .* to mean 'anything'.

Check yourself

  1. Which case pattern matches both 'y' and 'yes' but not 'yellow'?
  2. What does `shopt -s nullglob` change?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: www.gnu.org/software/bash/manual/bash.html#Pattern-Matching