In bash 4, what is the difference between ending a `case` clause with `;;`, with `;&`, and with `;;&`?
answer
- three terminators, not one
- stop is the default, unlike C
- fall through without retesting
- retest the remaining patterns
- bash 4.0 only, not macOS bash 3.2
basics
~20 sIn bash, ;; ends the case as soon as a clause runs. ;& falls through and runs the next clause's body without testing its pattern. ;;& keeps testing the remaining patterns and runs every later clause that also matches.
solid answer
~40 s`;;` is the normal terminator: the matched clause runs and control jumps past `esac`. `;&` is unconditional fallthrough — bash runs the *next* clause's command list without even looking at its pattern, which is the C `switch` behaviour people wrongly assume is the default. `;;&` is the interesting one: after the matched clause runs, bash carries on testing the remaining patterns and executes every later clause that also matches, so one word can pick up several independent labels. Both `;&` and `;;&` arrived in bash 4.0, so they are unavailable on the bash 3.2 that macOS ships, and I would not use them in a `#!/bin/sh` script. In review I treat `;&` with suspicion, because reordering clauses then silently changes behaviour.
code
bash · 9 lines#!/usr/bin/env bash
# requires bash 4.0+
level=3
case $level in
3) echo "install docs" ;&
2) echo "install headers" ;&
1) echo "install binary" ;;
*) echo "nothing to do" ;;
esacgo deeper
Know that a bash case clause stops at the double semicolon and does not fall through to the next clause the way C switch does. Recognising the plain ;; terminator is enough at this level.
Explain all three terminators precisely: which one skips the next pattern test and which one resumes testing, and give a concrete case where each is the right choice.
Bring the portability and maintenance angle: these are bash 4.0 features absent from macOS bash 3.2, and ;& makes clause order load-bearing in a way reviewers miss.
Have a position on whether these terminators belong in your team's shell style guide at all, given the readability cost and the interpreter floor they impose on every script.
## Three ways to end a clause A `case` clause is `pattern) command-list terminator`. Bash offers three terminators, and they differ only in what happens *after* the matched clause's commands have run. - `;;` — stop. Control leaves the `case` immediately; no further pattern is tested. - `;&` — fall through. The command list of the **next** clause runs unconditionally, without its pattern being tested at all. - `;;&` — retest. Bash resumes testing the remaining patterns from where it left off and runs the command list of every later clause that matches. Only the last clause before `esac` may omit its terminator; leaving one out between two clauses is a syntax error, not a fallthrough. ## `;;` is not C's switch The most common misconception is imported from C and Java, where a `case` label falls through unless you write `break`. Bash is the opposite: the default is to stop, and you must opt in to fallthrough. So this prints one line, not two: ```bash case a in a) echo "one" ;; *) echo "two" ;; esac ``` ## `;&` — the waterfall `;&` is for cumulative work where a higher level implies every lower one. Because the next pattern is never examined, the clause after a `;&` runs even if its pattern could not possibly match the word: ```bash level=3 case $level in 3) echo "install docs" ;& 2) echo "install headers" ;& 1) echo "install binary" ;; esac ``` With `level=3` all three lines print; with `level=1` only the last one does. That is genuinely useful, but note the coupling it creates: the clauses are now positionally dependent, so sorting them alphabetically — a change that looks cosmetic in review — silently rewrites the logic. This is why many style guides ban `;&` outright, and why any use of it deserves a comment. ## `;;&` — several labels for one word `;;&` keeps the clauses independent. Each pattern is still tested on its own merits, so the construct behaves like a chain of separate `if` statements over the same word, but written once and read top to bottom: ```bash case $file in *.tar.gz) echo "gzip-compressed" ;;& *.tar.*) echo "tar archive" ;;& *) echo "path: $file" ;; esac ``` For `backup.tar.gz` that prints all three lines. This shines for classification and validation — collecting every property of an input, or reporting every rule a value violates rather than only the first one. ## Portability Both `;&` and `;;&` are bash 4.0 additions. macOS still ships bash 3.2 as `/bin/bash`, so a script using either terminator is a syntax error there — and syntax errors in a shell script surface at parse time for the whole compound command, not when the branch is reached. If your script has to run on macOS's system bash, or under a plain `#!/bin/sh` interpreter, write independent `if` statements instead; there is no drop-in equivalent. ## How to choose Reach for `;;` by default. Reach for `;;&` when a word legitimately has several independent classifications and you want them all reported. Reach for `;&` rarely, only for a genuine waterfall, and comment it — the terminator is two characters long and a reader skimming the block will read it as `;;` unless something draws their eye.
- With `;&`, is the next clause's pattern evaluated at all before its body runs?No. `;&` transfers control straight into the next clause's command list without testing its pattern, so that body runs even if its pattern could never match the word. That is exactly what makes `;&` fragile: the clauses become positionally coupled, and reordering them changes behaviour without any pattern appearing to change.
- You need `;;&` behaviour but the script must run on macOS's bash 3.2. What do you do?Write independent `if` statements, one per rule, over the same word — there is no bash 3.2 equivalent of `;;&`, and using it there is a parse error rather than a runtime one. If the rule set is large, loop over pattern/label pairs and test each one, so adding a rule stays a data change rather than a code change.
- Why do reviewers treat `;&` as a smell in a long case block?Because it is visually one character away from `;;` and it makes clause order semantic. A reader skimming the block sees a normal case; a maintainer sorting or inserting clauses changes the executed set without touching a pattern. If you use it, comment the intent at the clause so the coupling is visible.
saying these in an interview costs you the question
- Says bash case falls through by default like C switch
- Thinks ;& and ;;& mean the same thing
- Assumes ;& retests the next pattern before running it
- Believes omitting ;; between clauses causes fallthrough
- Uses ;& or ;;& in a script that must run on macOS bash 3.2