extglob, globstar und nullglob praktisch einsetzen, statt an leeren Treffern zu scheitern
Bashs eingebautes Globbing kann weit mehr als das einfache Sternchen-Muster vieler Anfängerskripte: Mit shopt -s extglob stehen erweiterte Pattern wie Alternativen und Negation zur Verfügung, globstar aktiviert rekursives ** Matching über ganze Verzeichnisbäume, und nullglob verhindert den klassischen Fehler, bei dem ein Muster ohne Treffer wörtlich als Dateiname weitergereicht wird.
Inhaltsverzeichnis
- 1. Was Globbing ist und wo die Standard-Wildcards enden
- 2. extglob: erweiterte Pattern mit ?(...), @(...) und !(...)
- 3. globstar: rekursives ** Matching über Verzeichnisbäume
- 4. nullglob: der Fallstrick leerer Treffer ohne diese Option
- 5. Alle drei Optionen kombiniert: ein robustes Deployment-Skript
- 6. dotglob und failglob: zwei verwandte, oft übersehene Optionen
- 7. Sicherheit: Glob-Ergebnisse und nicht vertrauenswürdige Dateinamen
- 8. Performance: Globbing vs. find bei großen Verzeichnisbäumen
- 9. Wann welche Glob-Option zum Einsatz kommt
- 10. Zusammenfassung
- 11. FAQ
1. Was Globbing ist und wo die Standard-Wildcards enden
Globbing bezeichnet die Fähigkeit der Shell, Muster wie *.log oder file?.txt selbst zu expandieren, bevor ein Befehl überhaupt aufgerufen wird, im Unterschied zu regulären Ausdrücken, die von einzelnen Programmen wie grep interpretiert werden. Das Standard-Repertoire von Bash umfasst * für eine beliebige Zeichenfolge, ? für genau ein Zeichen und Zeichenklassen wie [abc] oder [0-9], was für die meisten alltäglichen Dateiauswahlen bereits ausreicht.
Sobald die Anforderungen komplexer werden, etwa mehrere alternative Endungen in einem einzigen Muster oder ein rekursiver Abstieg durch Unterverzeichnisse, stößt das Standard-Globbing an seine Grenzen. Genau hier setzen die drei shopt-Optionen extglob, globstar und nullglob an, die zusammen aus Bashs Globbing ein deutlich mächtigeres Werkzeug machen, ohne dass ein externes Programm wie find aufgerufen werden muss.
2. extglob: erweiterte Pattern mit ?(...), @(...) und !(...)
Mit shopt -s extglob aktiviert Bash eine Reihe erweiterter Musteroperatoren, die syntaktisch an reguläre Ausdrücke erinnern, aber weiterhin reines Globbing bleiben. @(muster1|muster2) matcht genau eine der angegebenen Alternativen, ?(muster) matcht das Muster null- oder einmal, *(muster) null- oder mehrmals, +(muster) ein- oder mehrmals, und !(muster) negiert das Muster, also alles, was gerade nicht darauf passt.
In der Praxis ist @(...) der mit Abstand meistgenutzte Operator, etwa um mehrere Dateiendungen in einem Rutsch zu erfassen, während !(...) sich anbietet, um gezielt alles außer einer bestimmten Datei oder einem bestimmten Muster zu selektieren, was ohne extglob nur mit einer Kombination aus find und grep -v möglich wäre.
#!/usr/bin/env bash
set -euo pipefail
shopt -s extglob
# Match multiple alternative extensions in one pattern
for f in *.@(jpg|jpeg|png|webp); do
echo "Image: $f"
done
# Match everything EXCEPT a specific file
for f in !(README.md); do
echo "Not the README: $f"
done
# Match one-or-more repetitions of a sub-pattern
for f in backup+([0-9]).tar.gz; do
echo "Numbered backup: $f"
done
3. globstar: rekursives ** Matching über Verzeichnisbäume
Standardmäßig matcht * in Bash keine Verzeichnistrenner, ein Muster wie src/*.php steigt also nicht automatisch in Unterordner ab. Mit shopt -s globstar ändert sich das Verhalten von zwei aufeinanderfolgenden Sternchen ** grundlegend: Ein Muster wie src/**/*.php matcht dann jede PHP-Datei in src und in beliebig tief verschachtelten Unterverzeichnissen, funktional vergleichbar mit find src -name '*.php', aber ohne einen externen Prozessaufruf.
Ein wichtiger Unterschied zu find ist, dass globstar bei sehr großen Verzeichnisbäumen alle passenden Pfade zunächst vollständig in den Speicher expandiert, bevor der Befehl überhaupt startet, während find die Ergebnisse als Stream liefert. Bei einigen hunderttausend Dateien kann das spürbar mehr Speicher und Zeit kosten, weshalb globstar sich vor allem für Projektverzeichnisse überschaubarer Größe eignet, nicht für das rekursive Durchsuchen ganzer Dateisysteme.
#!/usr/bin/env bash
set -euo pipefail
shopt -s globstar
# Match every .php file at any depth under src/
for f in src/**/*.php; do
echo "PHP file: $f"
done
# Combine globstar with extglob for recursive multi-extension matching
shopt -s extglob
for f in src/**/*.@(php|phtml); do
echo "PHP or phtml: $f"
done
4. nullglob: der Fallstrick leerer Treffer ohne diese Option
Ohne nullglob gibt Bash ein Muster, das keine einzige Datei matcht, wörtlich unverändert zurück, statt eine leere Liste zu liefern. In einer Schleife wie for f in *.bak; do ...; done führt das dazu, dass der Schleifenkörper trotzdem genau einmal ausgeführt wird, mit der buchstäblichen Zeichenkette *.bak als Wert von $f, was fast immer ein Programmierfehler ist und in Skripten mit unzureichender Prüfung zu verwirrenden Folgefehlern führt, etwa einem Löschbefehl, der auf eine nicht existierende Datei namens *.bak angewendet wird.
Mit shopt -s nullglob expandiert ein Muster ohne Treffer stattdessen zu einer leeren Liste, sodass die Schleife bei keinem Treffer korrekterweise gar nicht erst durchläuft. Diese Option gehört in praktisch jedes produktive Bash-Skript, das mit Glob-Mustern über Dateilisten iteriert, weil sie den häufigsten und gleichzeitig am schwersten zu debuggenden Globbing-Fehler von vornherein ausschließt.
#!/usr/bin/env bash
set -euo pipefail
cd /tmp/empty-dir-without-bak-files 2>/dev/null || mkdir -p /tmp/empty-dir-without-bak-files && cd "$_"
echo "Without nullglob:"
for f in *.bak; do
echo " Loop ran with: $f" # prints literally "*.bak" -- misleading!
done
shopt -s nullglob
echo "With nullglob:"
for f in *.bak; do
echo " Loop ran with: $f" # never prints, loop body is skipped entirely
done
echo "Loop correctly skipped when nothing matched"
5. Alle drei Optionen kombiniert: ein robustes Deployment-Skript
In der Praxis werden extglob, globstar und nullglob selten einzeln verwendet, sondern meist gemeinsam am Anfang eines Skripts aktiviert, weil sie sich gegenseitig ergänzen: globstar liefert die rekursive Tiefe, extglob die Ausdrucksstärke bei der Musterwahl, und nullglob die Sicherheit gegen leere Treffer. Ein typisches Einsatzbeispiel ist ein Deployment-Skript, das rekursiv alle Konfigurationsdateien mit bestimmten Endungen sucht und dabei Testdateien gezielt ausschließt.
Wichtig ist, alle shopt-Aufrufe möglichst am Skriptanfang zu bündeln und zu dokumentieren, welche Optionen aktiv sind, weil ein Leser, der mit den Standardeinstellungen von Bash vertraut ist, ein Muster wie !(*.test.php) ohne den Hinweis auf extglob gar nicht als gültiges Bash-Pattern erkennt.
#!/usr/bin/env bash
set -euo pipefail
shopt -s extglob globstar nullglob
readonly CONFIG_DIR="config"
# Recursively find all .yaml/.yml files, excluding anything with .test. in the name
for f in "$CONFIG_DIR"/**/!(*.test).@(yaml|yml); do
echo "Deploying config: $f"
done
6. dotglob und failglob: zwei verwandte, oft übersehene Optionen
Neben den drei Kernoptionen lohnt sich ein Blick auf zwei verwandte shopt-Schalter: dotglob sorgt dafür, dass Muster wie * auch versteckte Dateien erfassen, deren Name mit einem Punkt beginnt, was standardmäßig ausgeschlossen ist, weil sonst jedes *-Muster ungewollt . und .. mit einschließen würde. Wer Konfigurationsdateien wie .env oder .gitignore in ein Glob-Muster einbeziehen will, kommt an dotglob nicht vorbei.
failglob ist das genaue Gegenteil von nullglob: Statt bei keinem Treffer eine leere Liste zu liefern, bricht Bash mit einer Fehlermeldung ab, sobald ein Muster nicht matcht. Das eignet sich für Skripte, in denen ein fehlender Treffer tatsächlich ein harter Fehlerzustand sein soll, etwa wenn ein erwartetes Build-Artefakt zwingend vorhanden sein muss, bevor der nächste Schritt starten darf.
7. Sicherheit: Glob-Ergebnisse und nicht vertrauenswürdige Dateinamen
Ein oft übersehenes Risiko beim Globbing ist, dass ein expandiertes Muster Dateinamen liefern kann, die mit einem Bindestrich beginnen, etwa -rf. Wird ein solcher Name ungeschützt an einen Befehl wie rm $files weitergereicht, kann er versehentlich als Kommandozeilenoption statt als Dateiname interpretiert werden. Deshalb sollte jede Variable, die aus einem Glob-Ergebnis stammt, immer in Anführungszeichen verwendet werden, und bei Aufrufen wie rm ist der Präfix --, der alles Folgende zwingend als Dateiname statt als Option behandelt, eine zusätzliche Absicherung.
Ein zweites Risiko betrifft Dateinamen mit eingebetteten Leerzeichen oder Zeilenumbrüchen: Wird das Ergebnis eines Glob-Musters ohne Anführungszeichen in eine Schleifenvariable geschrieben und dann per Word-Splitting weiterverarbeitet, reißt Bash einen einzelnen Dateinamen mit Leerzeichen fälschlich in mehrere Wörter auseinander. Die sichere Variante iteriert direkt über das Array, das for f in *.log; do ... done ohnehin liefert, und verzichtet auf jede zusätzliche Zwischenspeicherung als einfacher, ungequoteter String.
8. Performance: Globbing vs. find bei großen Verzeichnisbäumen
Für kleine bis mittlere Projektverzeichnisse mit wenigen tausend Dateien ist globstar-basiertes Globbing in der Regel schnell genug und hat den Vorteil, komplett ohne externen Prozessaufruf auszukommen, was besonders in Schleifen mit vielen Wiederholungen spürbar ist. Bei Verzeichnisbäumen mit hunderttausenden Dateien oder tief verschachtelten Netzwerk-Dateisystemen zeigt find dagegen klare Vorteile, weil es Ergebnisse als Stream liefert und zusätzliche Filter wie Änderungsdatum oder Dateigröße direkt einbauen kann, ohne den kompletten Ergebnissatz vorher in den Speicher zu laden.
Eine bewährte Faustregel ist, globstar für Projektverzeichnisse und Deployment-Skripte zu verwenden, deren Größe man kennt und kontrolliert, und auf find umzusteigen, sobald ein Skript auf beliebig großen, fremdverwalteten Verzeichnisbäumen laufen soll, etwa bei einer generischen Backup- oder Aufräumroutine, die auf unterschiedlichen Servern mit unbekannter Dateianzahl läuft.
9. Wann welche Glob-Option zum Einsatz kommt
Die Wahl der richtigen Kombination hängt vom konkreten Anwendungsfall ab: einfache Musterlisten mit mehreren Endungen brauchen nur extglob, rekursive Suchen über Unterverzeichnisse brauchen zusätzlich globstar, und jede Schleife, die mit einem Glob-Muster über eine potenziell leere Ergebnismenge iteriert, sollte grundsätzlich nullglob aktivieren, um den klassischen Fehler mit dem wörtlichen Musterstring zu vermeiden.
| Option | Aktiviert mit | Effekt | Typischer Einsatz |
|---|---|---|---|
extglob |
shopt -s extglob |
Erweiterte Pattern: @(), ?(), !(), +() | Mehrere Endungen, Negation von Mustern |
globstar |
shopt -s globstar |
** matcht rekursiv über Verzeichnisse | Projektweite Dateisuche ohne find |
nullglob |
shopt -s nullglob |
Muster ohne Treffer wird zu leerer Liste | Sichere Schleifen über Glob-Ergebnisse |
dotglob |
shopt -s dotglob |
* erfasst auch versteckte Dateien | Konfigurationsdateien wie .env erfassen |
failglob |
shopt -s failglob |
Kein Treffer bricht mit Fehler ab | Erwartete Dateien zwingend voraussetzen |
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
Globbing in Bash: Das Wichtigste auf einen Blick
extglob
Aktiviert @(), ?(), !(), *() und +() für erweiterte Musteralternativen und Negation, syntaktisch nah an erweiterten regulären Ausdrücken.
globstar
Zwei Sternchen ** matchen rekursiv über beliebig tiefe Unterverzeichnisse, ohne einen externen find-Aufruf zu benötigen.
nullglob
Verhindert den klassischen Fehler, bei dem ein Muster ohne Treffer wörtlich als Dateiname in der Schleife landet.
Kombination
extglob, globstar und nullglob werden meist gemeinsam am Skriptanfang aktiviert und ergänzen sich gegenseitig.