professionell parametrisieren, inklusive Langoptionen
Wer eigene Bash-Skripte mit Kommandozeilen-Optionen ausstattet, landet schnell bei getopts, dem eingebauten Bash-Werkzeug für Kurzoptionen. Es ist überall verfügbar, portabel und ohne externe Abhängigkeiten nutzbar, unterstützt aber keine Langoptionen wie --verbose. Wer beides braucht, kombiniert getopts mit einer manuellen case-Schleife und baut sich so ein Parameter-Parsing, das sich wie ein echtes CLI-Tool anfühlt.
Inhaltsverzeichnis
- 1. Warum professionelles Parameter-Parsing in Bash-Skripten zählt
- 2. Grundsyntax von getopts: Optionsstring, OPTARG und OPTIND
- 3. Pflichtargumente, optionale Flags und der führende Doppelpunkt
- 4. Die Grenzen von getopts: keine Langoptionen, keine --option=wert-Syntax
- 5. Langoptionen manuell nachrüsten: eine case-Schleife über die Positionsparameter
- 6. Kurz- und Langoptionen im selben Skript kombinieren
- 7. Hilfetext-Konventionen: usage()-Funktion, -h/--help und Exit-Codes
- 8. Fehlerbehandlung: unbekannte Optionen und fehlende Argumente sauber abfangen
- 9. getopts im Vergleich: wann welches Werkzeug für die Kommandozeile
- 10. Zusammenfassung
- 11. FAQ
1. Warum professionelles Parameter-Parsing in Bash-Skripten zählt
Ein Skript, das seine Argumente nur über $1, $2 und $3 ausliest, funktioniert genau so lange, wie niemand die Reihenfolge vertauscht, ein Flag weglässt oder eine zusätzliche Option braucht. Sobald ein Skript mehr als zwei oder drei feste Parameter bekommt, wird die positionsbasierte Auswertung unübersichtlich und fehleranfällig, weil jede neue Option die Positionen aller nachfolgenden Argumente verschiebt und bestehende Aufrufe bricht.
Ein professionelles Parameter-Parsing löst dieses Problem, indem Optionen über benannte Flags statt über feste Positionen erkannt werden. Der Nutzer kann Optionen in beliebiger Reihenfolge angeben, weglassen oder kombinieren, ohne dass sich am restlichen Skript etwas ändern muss. getopts ist dafür das naheliegende Werkzeug, weil es als Bash-Builtin ohne Installation auf jedem System mit Bash vorhanden ist, im Gegensatz zum externen Kommando getopt(1), das je nach System unterschiedliche Fähigkeiten hat.
2. Grundsyntax von getopts: Optionsstring, OPTARG und OPTIND
Die Grundform ist eine while-Schleife: while getopts "vo:h" opt; do case $opt in ... esac; done. Der Optionsstring "vo:h" definiert drei erlaubte Kurzoptionen: -v und -h sind reine Schalter ohne Wert, -o erwartet wegen des nachgestellten Doppelpunkts zwingend ein Argument. Bei jedem Schleifendurchlauf liefert getopts die nächste erkannte Option in der Variable opt, bis keine weiteren Optionen mehr übrig sind.
Der Wert einer Option mit Argument landet automatisch in der Variable OPTARG, während OPTIND intern mitzählt, an welcher Position in $@ die nächste Auswertung fortsetzt. Nach der Schleife steht in OPTIND die Position des ersten nicht-optionalen Arguments, weshalb ein abschließendes shift $((OPTIND - 1)) Pflicht ist, um die verbleibenden Positionsparameter für den Rest des Skripts freizulegen.
#!/usr/bin/env bash
set -euo pipefail
verbose=0
output=""
while getopts "vo:h" opt; do
case "$opt" in
v) verbose=1 ;;
o) output="$OPTARG" ;;
h) echo "Usage: $0 [-v] [-o file] [-h]"; exit 0 ;;
*) echo "Usage: $0 [-v] [-o file] [-h]" >&2; exit 2 ;;
esac
done
shift $((OPTIND - 1))
echo "verbose=$verbose output=$output remaining=$*"
3. Pflichtargumente, optionale Flags und der führende Doppelpunkt
Ob eine Option ein Argument braucht, steuert allein der Doppelpunkt im Optionsstring: o: verlangt ein Argument, v ohne Doppelpunkt ist ein reiner Schalter. Fehlt bei einer Pflichtoption das Argument, meldet getopts standardmäßig selbst einen Fehler auf stderr und setzt opt auf ein Fragezeichen, was in der case-Verzweigung als unbekannte oder fehlerhafte Option behandelt wird.
Ein führender Doppelpunkt am Anfang des gesamten Optionsstrings, etwa ":vo:h", schaltet den sogenannten stillen Fehlermodus ein: getopts gibt dann keine eigene Fehlermeldung mehr aus, sondern setzt opt bei fehlendem Argument auf einen Doppelpunkt und bei unbekannter Option auf ein Fragezeichen. So kann das Skript eigene, konsistent formatierte Fehlermeldungen ausgeben, statt sich auf den generischen Text von getopts zu verlassen.
#!/usr/bin/env bash
set -euo pipefail
while getopts ":o:h" opt; do
case "$opt" in
o) output="$OPTARG" ;;
h) echo "Usage: $0 [-o file] [-h]"; exit 0 ;;
\?) echo "Error: unknown option -$OPTARG" >&2; exit 2 ;;
:) echo "Error: option -$OPTARG requires an argument" >&2; exit 2 ;;
esac
done
4. Die Grenzen von getopts: keine Langoptionen, keine --option=wert-Syntax
So praktisch getopts für Kurzoptionen ist, so klar sind seine Grenzen: Es kennt ausschließlich einzelne Buchstaben nach einem einfachen Bindestrich. Eine Option wie --verbose oder die kombinierte Schreibweise --output=datei.txt versteht getopts nicht, weil das Bash-Builtin schlicht nicht dafür entworfen wurde, mehrbuchstabige Optionsnamen zu erkennen. Ein Aufruf mit --verbose wird von getopts als unbekannte Option mit dem Fragezeichen-Fall behandelt.
Das externe Kommando getopt(1), insbesondere die GNU-Variante, unterstützt zwar Langoptionen inklusive --option=wert, ist aber nicht überall gleich verfügbar. Auf macOS und vielen BSD-Systemen liegt standardmäßig nur die ältere, funktional eingeschränkte Variante vor, die keine Langoptionen kennt. Skripte, die auf mehreren Plattformen laufen sollen, verzichten deshalb häufig komplett auf das externe getopt und lösen Langoptionen selbst in reinem Bash.
5. Langoptionen manuell nachrüsten: eine case-Schleife über die Positionsparameter
Statt eines externen Tools reicht eine einfache while [[ $# -gt 0 ]]-Schleife, die jeden Positionsparameter selbst mit case auswertet und danach per shift weiterrückt. Diese Technik ist reines Bash, funktioniert überall gleich und erlaubt beliebig komplexe Optionsnamen, weil kein Optionsstring mit festen Buchstaben mehr existiert, sondern jede Verzweigung im case-Block explizit den vollen Optionsnamen prüft.
Für die Schreibweise --output=datei.txt hilft die Parameter-Expansion ${1#*=}, die alles bis zum ersten Gleichheitszeichen entfernt und so den reinen Wert liefert. Bei Optionen ohne Gleichheitszeichen, etwa --output datei.txt mit Leerzeichen, muss der Wert stattdessen aus dem nächsten Positionsparameter gelesen und ein zusätzliches shift eingefügt werden.
#!/usr/bin/env bash
set -euo pipefail
verbose=0
output=""
while [[ $# -gt 0 ]]; do
case "$1" in
--verbose) verbose=1; shift ;;
--output=*) output="${1#*=}"; shift ;;
--output) output="$2"; shift 2 ;;
--help) echo "Usage: $0 [--verbose] [--output=file]"; exit 0 ;;
--) shift; break ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) break ;;
esac
done
echo "verbose=$verbose output=$output remaining=$*"
6. Kurz- und Langoptionen im selben Skript kombinieren
Wer sowohl -v als auch --verbose anbieten will, verzichtet am besten vollständig auf getopts und wertet alle Optionen in einer einzigen manuellen case-Schleife aus. Der Versuch, getopts für die Kurzformen und danach eine zweite Schleife für die Langformen laufen zu lassen, führt schnell zu Reihenfolge-Problemen, weil getopts beim ersten nicht erkannten Langargument sofort abbricht und OPTIND nicht mehr zuverlässig auf die richtige Position zeigt.
Die robustere Lösung ist eine einzige case-Verzweigung, die für jede Funktion sowohl die Kurz- als auch die Langform mit demselben Verhalten verknüpft, etwa -v|--verbose). So bleibt die Logik an einer Stelle gebündelt, neue Optionen lassen sich einfach ergänzen, und es gibt nur einen einzigen Ort, an dem sich Kurz- und Langform inhaltlich unterscheiden könnten.
#!/usr/bin/env bash
set -euo pipefail
verbose=0
output=""
while [[ $# -gt 0 ]]; do
case "$1" in
-v|--verbose) verbose=1; shift ;;
-o|--output) output="$2"; shift 2 ;;
--output=*) output="${1#*=}"; shift ;;
-h|--help) echo "Usage: $0 [-v|--verbose] [-o|--output file]"; exit 0 ;;
--) shift; break ;;
-*) echo "Unknown option: $1" >&2; exit 2 ;;
*) break ;;
esac
done
7. Hilfetext-Konventionen: usage()-Funktion, -h/--help und Exit-Codes
Ein wiederverwendbarer Bash-Konventions-Baustein ist eine eigene usage()-Funktion, die einmal definiert und an mehreren Stellen im Skript aufgerufen wird: beim expliziten Hilfe-Aufruf über -h oder --help ebenso wie bei jedem Fehlerfall mit unbekannter oder fehlender Option. So bleibt der Hilfetext an einer Stelle gepflegt, statt in mehreren echo-Zeilen quer durchs Skript verstreut zu sein.
Wichtig ist die Unterscheidung, wohin der Text geschrieben wird: Ein explizit angeforderter Hilfetext gehört auf stdout, damit er sich problemlos in einen Pager wie less umleiten lässt, während ein Hilfetext im Fehlerfall auf stderr gehört, damit er nicht versehentlich in eine Ausgabedatei landet, wenn stdout umgeleitet wurde.
#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<EOF
Usage: $0 [-v|--verbose] [-o|--output FILE] [-h|--help]
-v, --verbose enable verbose logging
-o, --output FILE write results to FILE instead of stdout
-h, --help show this help text and exit
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
-h|--help) usage; exit 0 ;;
-*) echo "Unknown option: $1" >&2; usage >&2; exit 2 ;;
*) break ;;
esac
done
8. Fehlerbehandlung: unbekannte Optionen und fehlende Argumente sauber abfangen
Sowohl bei getopts als auch bei der manuellen Variante braucht jedes Skript einen klar definierten Fallback für den Fall einer unbekannten Option. Bei getopts ist das der Fall \?), bei der manuellen Schleife typischerweise das Muster -*) am Ende der case-Verzweigung, das jede noch nicht behandelte, mit einem Bindestrich beginnende Eingabe abfängt, bevor sie fälschlich als Positionsargument interpretiert wird.
Für fehlende Pflichtargumente hat sich exit 2 als gängige Konvention etabliert, weil viele Standard-Unix-Tools wie grep Exit-Code 2 für einen Aufruffehler reservieren, während 1 meist ein reguläres, erwartetes Fehlschlagen signalisiert. Ein konsistentes Exit-Code-Schema macht Skripte in größeren Automatisierungsketten vorhersagbar, weil aufrufende Prozesse den Grund eines Fehlers am Exit-Code unterscheiden können, ohne die Fehlermeldung selbst zu parsen.
9. getopts im Vergleich: wann welches Werkzeug für die Kommandozeile
Die Wahl zwischen getopts, einer reinen manuellen Schleife und dem externen getopt(1) hängt vor allem davon ab, ob Langoptionen gebraucht werden und wie portabel ein Skript sein muss. Für kleine, interne Helferskripte mit wenigen Kurzoptionen reicht getopts völlig aus und bleibt am einfachsten zu warten, während produktionsreife CLI-Tools mit einer erwarteten --flag-Syntax kaum an einer manuellen Lösung vorbeikommen.
| Ansatz | Langoptionen | Portabilität | Empfehlung |
|---|---|---|---|
getopts (Builtin) |
Nein | Überall, wo Bash läuft | Kleine Skripte mit reinen Kurzoptionen |
Manuelle case-Schleife |
Ja, beliebig | Überall, wo Bash läuft | CLI-Tools mit -x/--xyz-Syntax |
GNU getopt(1) |
Ja | Nur mit GNU-Variante (meist Linux) | Nur wenn GNU-Umgebung garantiert ist |
| Drittanbieter-Generatoren (z. B. argbash) | Ja | Abhängig vom generierten Code | Große CLI-Projekte mit vielen Optionen |
Mironsoft
Shell-Automatisierung, DevOps-Tooling und Deployment-Infrastruktur
Shell-Skripte, die in der Produktion zuverlässig laufen?
Wir analysieren bestehende Bash-Skripte, erkennen fragile Muster und ersetzen sie durch robuste Bash-Patterns: mit vollständiger Fehlerbehandlung, Logging und sicherer Parallelisierung für euren Deployment-Stack.
Code-Review
ShellCheck-Analyse und manuelle Prüfung auf kritische Bash-Pattern-Verstöße.
Refactoring
Fehlerbehandlung, Logging und sichere Dateioperationen nachrüsten.
CI-Integration
ShellCheck und BATS in Pipelines integrieren und Regressionstests aufbauen.
10. Zusammenfassung
getopts in Bash: Das Wichtigste auf einen Blick
getopts
Bash-Builtin für Kurzoptionen mit OPTARG und OPTIND, überall verfügbar, aber ohne Unterstützung für Langoptionen.
Langoptionen
Eine manuelle case-Schleife über die Positionsparameter mit shift ist der portable Weg zu --verbose und --output=wert.
Kombination
Kurz- und Langform gehören in dieselbe case-Verzweigung, z. B. -v|--verbose), statt zwei getrennte Parsing-Durchläufe.
Konventionen
usage()-Funktion für -h/--help, Fehlertexte auf stderr, Exit-Code 2 für Aufruffehler wie fehlende Pflichtargumente.