Das case-Statement ist Bashs Antwort auf lange if/elif-Ketten. Statt eine Variable mehrfach gegen verschiedene Werte zu vergleichen, schreibst du eine kompakte Pattern-Liste — mit Glob-Mustern, Multi-Pattern und optionalem Fall-Through. Wer CLI-Argumente parst, Datei-Endungen unterscheidet oder Ja/Nein-Prompts baut, kommt um case nicht herum.
Was case macht
case vergleicht eine Variable oder einen Ausdruck gegen eine Liste von Mustern und führt den Block aus, der zum ersten passenden Muster gehört. Anders als ein klassischer switch in C oder Java arbeitet Bash dabei nicht mit festen Werten, sondern mit Glob-Pattern — derselben Muster-Sprache, die auch beim Dateinamen-Matching in der Shell zum Einsatz kommt.
Der typische Anwendungsfall: Du hast eine Variable, die mehrere mögliche Werte annehmen kann, und willst pro Wert unterschiedliche Aktionen ausführen. Mit if/elif sieht das schnell unübersichtlich aus:
if [ "$mode" = "start" ]; then
starte_dienst
elif [ "$mode" = "stop" ]; then
stoppe_dienst
elif [ "$mode" = "restart" ]; then
stoppe_dienst
starte_dienst
else
echo "Unbekannter Modus: $mode"
fiMit case wird daraus eine flache Liste — jeder Pattern-Block ist auf einen Blick erkennbar, und neue Optionen lassen sich ohne Verschachtelung einfügen. case ist zudem ein Bash-Builtin, das beim Pattern-Matching meist schneller ist als eine Folge externer [-Aufrufe.
Syntax
Die Grundform ist immer gleich: ein Wert wird gegen eine Liste von Pattern-Blöcken geprüft, jeder Block endet mit ;;, das Ganze wird mit esac (case rückwärts) abgeschlossen.
case $variable in
PATTERN1)
# Befehle für Pattern 1
;;
PATTERN2)
# Befehle für Pattern 2
;;
*)
# Default-Fall
;;
esacKonkretes Beispiel mit einer Mode-Variable:
mode="start"
case "$mode" in
start)
echo "Dienst wird gestartet"
;;
stop)
echo "Dienst wird gestoppt"
;;
restart)
echo "Dienst wird neu gestartet"
;;
*)
echo "Unbekannter Modus: $mode"
;;
esacDie wichtigsten Bestandteile im Detail:
| Element | Bedeutung |
|---|---|
case $var in | Eröffnet das Statement. $var wird gegen die folgenden Pattern getestet. |
PATTERN) | Pattern, gefolgt von einer schliessenden Klammer. Leitet einen Block ein. |
;; | Beendet einen Pattern-Block und beendet das gesamte case. Pflicht. |
*) | Default-Pattern. Matcht alles und sollte am Ende stehen. |
esac | Schliesst das case-Statement (case rückwärts). |
case stoppt beim ersten passenden Pattern — alles danach wird ignoriert. Die Reihenfolge der Pattern ist deshalb wichtig: spezifische Pattern gehören vor allgemeine, der Default *) immer ans Ende.
Glob-Pattern in case
Pattern in case sind keine regulären Ausdrücke, sondern dieselben Glob-Muster, die die Shell auch beim Datei-Matching verwendet. Das ist ein häufiger Stolperstein für Programmierer, die von Sprachen mit Regex-Switches kommen.
| Pattern | Bedeutung | Matcht |
|---|---|---|
* | Beliebige Zeichenkette (auch leer) | alles |
? | Genau ein beliebiges Zeichen | a, 1, x |
[abc] | Genau ein Zeichen aus der Klasse | a, b, c |
[a-z] | Bereich von Zeichen | a bis z |
[!abc] | Negation: kein Zeichen aus der Klasse | alles ausser a, b, c |
*.txt | Suffix-Match | notes.txt, readme.txt |
start* | Prefix-Match | start, startup, start_app |
*log* | Substring-Match | logs, mylog, error.log.1 |
Beispiel mit Datei-Endungen:
datei="bericht.pdf"
case "$datei" in
*.txt)
echo "Textdatei"
;;
*.pdf)
echo "PDF-Dokument"
;;
*.[jp][pn]g)
echo "Bild (jpg oder png)"
;;
esacWer Regex-artige Pattern braucht — etwa mehrstellige Quantoren oder Alternativen — sollte stattdessen [[ $var =~ regex ]] verwenden. case deckt aber 90 Prozent der typischen Fälle ab.
Multi-Pattern mit |
Mehrere Pattern lassen sich in einem Block mit dem Pipe-Zeichen | kombinieren. Das ist die klassische Lösung für Eingaben, die in mehreren Schreibweisen akzeptiert werden sollen — Ja/Nein-Prompts sind das Standardbeispiel.
read -p "Wirklich löschen? [j/N] " antwort
case "$antwort" in
j|J|ja|JA|yes|y|Y)
echo "Lösche..."
;;
n|N|nein|NEIN|no|"")
echo "Abgebrochen"
;;
*)
echo "Bitte j oder n eingeben"
;;
esacDer |-Operator hat innerhalb von case nichts mit der Pipe der Shell zu tun — hier bedeutet er logisches ODER zwischen Pattern. Auch leere Eingaben lassen sich mit "" matchen, was den Default-Wert eines Prompts abbildet (z. B. wenn der Benutzer einfach Enter drückt).
Fall-Through mit ;& und ;;&
Standardmässig endet case nach dem ersten Match — egal, ob nachfolgende Pattern auch passen würden. Ab Bash 4 gibt es zwei Terminator-Varianten, die dieses Verhalten ändern und Fall-Through erlauben.
| Terminator | Verhalten |
|---|---|
;; | Standard. Beendet case sofort nach Ausführung des Blocks. |
;& | Fällt durch in den nächsten Block — ohne dessen Pattern erneut zu testen. |
;;& | Fällt durch und testet das nächste Pattern weiter. Mehrfach-Matches möglich. |
Beispiel für ;;& — eine Datei wird mehrfach klassifiziert:
datei="archiv.tar.gz"
case "$datei" in
*.gz)
echo "Gzip-komprimiert"
;;&
*.tar.gz)
echo "Tar-Archiv"
;;&
*.*)
echo "Hat eine Endung"
;;
esacGzip-komprimiert
Tar-Archiv
Hat eine EndungMit ;& springst du dagegen direkt in den nächsten Block, unabhängig davon, ob dessen Pattern matcht. Das ist nützlich für gestaffelte Defaults oder gemeinsam genutzte Cleanup-Schritte.
Praxis-Patterns
Im Alltag taucht case vor allem in fünf Konstellationen auf. Jedes Beispiel ist eigenständig nutzbar und zeigt eine typische Shell-Aufgabe.
Argument-Parsing für CLI-Skripte
Skripte mit mehreren Subkommandos (start, stop, status) verwenden case als zentrale Verteilung. Der erste positionelle Parameter $1 ist der Subkommando-Name, alles weitere wird an Funktionen delegiert.
case "$1" in
start)
starte_app
;;
stop)
stoppe_app
;;
status)
zeige_status
;;
-h|--help)
zeige_hilfe
;;
*)
echo "Verwendung: $0 {start|stop|status}"
exit 1
;;
esacDer *)-Default fängt unbekannte Aufrufe ab und gibt eine Usage-Meldung aus. exit 1 signalisiert dem Aufrufer einen Fehler — wichtig für Skripte, die in Pipelines oder CI-Systemen laufen.
Datei-Endungs-basierte Aktion
Statt mit if-Kaskaden auf Endungen zu prüfen, sortierst du mit case direkt nach Suffix. Das ist die typische Lösung für Konverter-Skripte oder Build-Helfer.
for datei in "$@"; do
case "$datei" in
*.md)
pandoc "$datei" -o "${datei%.md}.html"
;;
*.scss)
sass "$datei" "${datei%.scss}.css"
;;
*.ts)
tsc "$datei"
;;
*)
echo "Übersprungen: $datei"
;;
esac
doneDie Parameter-Expansion ${datei%.md} schneidet die Endung ab — typisches Bash-Pattern in Kombination mit case.
Yes/No-Prompt
Ein klassisches interaktives Muster — alle gängigen Schreibweisen werden akzeptiert, der Rest landet in einem Retry-Default.
read -p "Fortfahren? [j/N] " a
case "$a" in
[jJ]|[jJ][aA]|[yY]|[yY][eE][sS])
echo "weiter"
;;
*)
echo "abgebrochen"
exit 0
;;
esac[jJ] matcht ein einzelnes j oder J, [jJ][aA] matcht ja, Ja, jA, JA. So sparst du dir die Aufzählung aller Kombinationen mit |.
Default-Fallback mit *)
Der *)-Block ist der Joker — er matcht alles, was vorher nicht gegriffen hat. Pflicht für robuste Skripte, sonst geht ein unerwarteter Wert kommentarlos durch.
case "$ENV" in
dev|development)
LOG_LEVEL=debug
;;
prod|production)
LOG_LEVEL=warn
;;
*)
echo "Warnung: unbekannte Umgebung '$ENV', setze LOG_LEVEL=info"
LOG_LEVEL=info
;;
esacHier wird der Default nicht zum Fehlerausstieg, sondern zur sicheren Fallback-Konfiguration. Beide Varianten sind legitim — abhängig davon, ob ein unbekannter Wert kritisch ist oder nicht.
OS-Detection mit case "$(uname)"
Skripte, die auf Linux und macOS laufen sollen, müssen oft unterscheiden, welcher Kernel darunter liegt. uname liefert den Namen, case verzweigt darauf.
case "$(uname -s)" in
Linux*)
OPEN=xdg-open
;;
Darwin*)
OPEN=open
;;
CYGWIN*|MINGW*|MSYS*)
OPEN=start
;;
*)
echo "Unbekanntes OS: $(uname -s)"
exit 1
;;
esac
$OPEN "$1"Die Pattern enden auf *, weil uname Varianten wie Linux oder Linux-x86_64 ausgeben kann. Multi-Pattern fängt zusätzlich alle Windows-Bash-Varianten in einem Block ab.
Besonderheiten
Default-Pattern gehört ans Ende
case arbeitet sequenziell von oben nach unten und stoppt beim ersten Match. Ein *) an erster Stelle würde alles auffangen — die nachfolgenden Pattern wären toter Code. Spezifische Pattern stehen deshalb immer vor dem Default. Bei Pattern mit Überlappung (z. B. *.tar.gz und *.gz) gilt dasselbe: das spezifischere Pattern muss zuerst stehen, sonst greift das allgemeinere zu früh.
Glob-Pattern, nicht Regex
Pattern in case sind Shell-Globs, keine regulären Ausdrücke. Das überrascht oft Entwickler, die aus Sprachen wie Python, JavaScript oder Java kommen, wo switch mit Strings oder Regex arbeitet. * heisst hier „beliebige Zeichen”, nicht „null oder mehr Wiederholungen des vorigen Zeichens”. Wer echte Regex braucht, weicht auf if [[ $var =~ regex ]]; then ... fi aus.
Quotes um die Variable schützen vor leeren Werten
case "$var" in mit Anführungszeichen ist der sichere Default — auch wenn $var leer oder ungesetzt ist. Ohne Quotes kann eine leere Variable zu Syntaxfehlern führen, und Werte mit Leerzeichen oder Sonderzeichen werden falsch interpretiert. Innerhalb der Pattern selbst sind Quotes dagegen unnötig und deaktivieren teilweise die Glob-Funktion.
Ein Semikolon ist nicht zwei Semikola
Der Block-Terminator ist ;; — zwei Semikola ohne Leerzeichen. Ein einfaches ; ist ein Befehlstrennzeichen und führt zu einem Syntaxfehler im case-Kontext. Bash quittiert das mit syntax error near unexpected token ')' an einer Stelle, die mit dem eigentlichen Fehler nichts zu tun hat — typischer Stolperstein bei der ersten Begegnung mit case.
Fall-Through nur ab Bash 4
Die Terminatoren ;& und ;;& sind Bash-4-Erweiterungen. Auf macOS ist die mitgelieferte Bash 3.2 (Lizenzgründe) — Skripte, die dort laufen sollen, dürfen kein Fall-Through verwenden. Wer portabel schreiben will, prüft mit ${BASH_VERSINFO[0]} die Major-Version oder verzichtet ganz auf Fall-Through und löst den Fall mit Funktionen oder Variablen.
case ist meist schneller als if-elif-Ketten
case ist ein Bash-Builtin, das das Pattern-Matching direkt ohne Subprozess durchführt. Eine Kette aus if [ "$x" = "..." ] kann je nach Shell-Konfiguration externes [ aufrufen — pro Vergleich ein Fork. Bei einer Handvoll Werte ist der Unterschied unsichtbar; in Schleifen mit tausenden Iterationen kann case deutlich gewinnen. Lesbarkeit ist aber meist das stärkere Argument.
Pattern dürfen Variablen enthalten
Pattern müssen nicht statisch sein: case "$x" in $erlaubt) ... expandiert $erlaubt zur Pattern-Definition. Damit lässt sich eine Erlaubnis-Liste dynamisch zusammensetzen. Vorsicht: Der Inhalt der Variable wird als Glob interpretiert, nicht als Literal. Wer einen Wert eins-zu-eins matchen will, muss ihn explizit quoten oder Sonderzeichen escapen.
Weiterführende Ressourcen
Externe Quellen
- Bash Reference Manual: Conditional Constructs — formale Definition von
case, inklusive Fall-Through-Terminatoren - Bash-Manpage (man7.org) — Compound-Commands-Abschnitt mit
case-Syntax - Bash Hackers Wiki: case — praxisnahe Beispiele und Edge Cases
- ShellCheck — statische Analyse, warnt vor unsicheren
case-Pattern
Verwandte Artikel
- Bedingungen —
if,elif, Test-Ausdrücke und wanncasedie bessere Wahl ist - Schleifen —
forundwhileals Ergänzung zu Verzweigungen - Parameter —
$1,$@und Argument-Handling für CLI-Skripte - Globbing — die Pattern-Sprache hinter
case - Skript-Grundlagen — Shebang, Ausführbarkeit und Skript-Aufbau