Bash-Skripte mit getopts professionell parametrisieren
AI generated
$_
#!/
Bash · CLI-Tools · Optionen-Parsing · Skripting
Bash-Skripte mit getopts
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.

16 Min. Lesezeit getopts · OPTARG · OPTIND Bash 4.x · 5.x · POSIX-kompatibel

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.

11. FAQ: getopts in Bash: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen getopts und getopt?
getopts ist ein Bash-Builtin ohne Unterstützung für Langoptionen, überall verfügbar. getopt ist ein externes Kommando, dessen GNU-Variante Langoptionen unterstützt, aber nicht auf jedem System in dieser Form vorliegt, etwa auf macOS.
2Warum unterstützt getopts kein --verbose?
getopts wurde für einzelne Buchstaben nach einem einfachen Bindestrich entworfen und kennt das Konzept mehrbuchstabiger Optionsnamen nach zwei Bindestrichen schlicht nicht. Für Langoptionen ist eine manuelle case-Schleife nötig.
3Wofür steht OPTARG in getopts?
OPTARG enthält den Wert einer Option, die laut Optionsstring ein Argument erwartet, zum Beispiel bei o: den Wert nach -o. Bei reinen Schaltern ohne Doppelpunkt bleibt OPTARG leer.
4Warum muss ich nach der getopts-Schleife shift aufrufen?
OPTIND zeigt nach der Schleife auf die Position des ersten nicht optionalen Arguments. shift $((OPTIND - 1)) entfernt alle bereits verarbeiteten Optionen, damit $1, $2 und so weiter wieder die restlichen Positionsargumente referenzieren.
5Was bewirkt ein führender Doppelpunkt im Optionsstring?
Ein Doppelpunkt ganz am Anfang, wie in :vo:h, aktiviert den stillen Fehlermodus. getopts gibt dann keine eigene Fehlermeldung mehr aus, sondern signalisiert fehlende Argumente über opt gleich Doppelpunkt und unbekannte Optionen über opt gleich Fragezeichen.
6Kann ich getopts und eine manuelle Schleife im selben Skript mischen?
Technisch ja, aber praktisch führt das schnell zu Reihenfolge-Problemen, weil OPTIND nach dem ersten unbekannten Langargument nicht mehr zuverlässig ist. Robuster ist eine einzige manuelle case-Schleife, die beide Formen gleichzeitig behandelt.
7Welchen Exit-Code sollte ein Skript bei fehlenden Pflichtargumenten setzen?
Exit-Code 2 hat sich als Konvention für Aufruffehler etabliert, angelehnt an Standard-Tools wie grep. Exit-Code 1 signalisiert dagegen meist ein erwartetes, reguläres Fehlschlagen der eigentlichen Skriptlogik.
8Wohin soll der Hilfetext geschrieben werden, stdout oder stderr?
Explizit über -h oder --help angeforderter Hilfetext gehört auf stdout, damit er sich sauber umleiten lässt. Hilfetext, der im Fehlerfall ausgegeben wird, gehört auf stderr, damit er nicht in einer umgeleiteten Ausgabedatei landet.
9Wie parse ich --output=datei.txt in reinem Bash?
Mit der Parameter-Expansion ${1#*=}, die alles bis einschließlich des ersten Gleichheitszeichens entfernt und so den reinen Wert nach dem Gleichheitszeichen liefert, ohne cut oder sed aufzurufen.
10Muss ich für Langoptionen ein externes Tool wie getopt installieren?
Nein. Eine manuelle case-Schleife über die Positionsparameter mit shift deckt Langoptionen vollständig in reinem Bash ab und funktioniert identisch auf Linux, macOS und in Containern, ohne zusätzliche Abhängigkeit.