Die drei Klammern [] {} () und die BASH – Ein Streifzug durch die syntaktische Vielfalt der Shell
Autor: DerSchneider
Einleitung
Die Bourne-Again-Shell, kurz Bash, ist eines der langlebigsten und zugleich mächtigsten Werkzeuge der UNIX-Welt. Wer täglich mit ihr arbeitet, kommt an einem Phänomen nicht vorbei, das Einsteiger regelmäßig zur Verzweiflung treibt: die Vielzahl der Klammerzeichen und ihre scheinbar willkürliche Verwendung. Runde Klammern (), geschweifte Klammern {} und eckige Klammern [] – drei Zeichen, drei Welten, unzählige Bedeutungen.
Die Idee zu diesem Artikel entstand bei einem Shell-Vortrag von Leyrer im Jahr 2023: Es gibt so viele verschiedene Klammer-Typen in der Shell, und so viele verschiedene Einsatzmöglichkeiten und Bedeutungen. Doch was auf den ersten Blick wie syntaktische Willkür wirkt, offenbart bei näherer Betrachtung eine durchdachte Systematik – ein Erbe aus den Anfängen der UNIX-Entwicklung, das bis heute die Arbeitsweise von Systemadministratoren und Entwicklern prägt.
Dieser Artikel beleuchtet die drei Klammerarten in ihrer ganzen Tiefe: ihre Funktionen, ihre Unterschiede, ihre historischen Hintergründe und die Fallstricke, die selbst erfahrene Anwender immer wieder übersehen.
Geschweifte Klammern {} – Die Kunst der Expansion
Die geschweiften Klammern sind das Werkzeug der Wahl, wenn es darum geht, Zeichenketten zu erzeugen, bevor die Shell überhaupt daran denkt, Dateien zu suchen oder Befehle auszuführen. Die sogenannte Brace Expansion ist ein Mechanismus zur Generierung beliebiger Zeichenketten mit gemeinsamem Präfix und Suffix.
Sequenzen und Listen
Die einfachste Form ist die Erzeugung von Zahlen- und Buchstabenfolgen:
bash
echo {1..10} # 1 2 3 4 5 6 7 8 9 10
echo {01..10} # 01 02 03 04 05 06 07 08 09 10
echo {A..H} # A B C D E F G H
echo {H..A} # H G F E D C B A
Die Bash erlaubt auch Inkremente:
bash
echo {0..15..2} # 0 2 4 6 8 10 12 14
echo {1..15..2} # 1 3 5 7 9 11 13 15
echo {H..A..2} # H F D B
Besonders nützlich ist die Kombination von Präfix und Suffix:
bash
touch file_{1..10}.txt # Erzeugt file_1.txt bis file_10.txt
cp file.txt{,.bak} # Kopiert file.txt nach file.txt.bak
rm /a/long/path/{foo,bar} # Entfernt beide Dateien[reference:2]
Die Oktalfalle
Eine der häufigsten Fehlerquellen betrifft die Behandlung von Zahlen mit führender Null. Während die Brace Expansion selbst führende Nullen problemlos verarbeitet, gilt das nicht für die arithmetische Expansion:
bash
echo $((010 + 1)) # 9, nicht 11!
Der Grund ist historisch: UNIX entstand auf PDP-Rechnern, die intern mit Oktalzahlen arbeiteten. Eine führende Null signalisiert bis heute ein oktales Zahlensystem – eine Konvention, die in der Bash-Arithmetik bis in die Gegenwart fortwirkt.
Die Reihenfolge der Expansion
Ein entscheidendes Detail: Die Brace Expansion findet vor allen anderen Expansionen statt, noch vor der Variablensubstitution. Das führt zu überraschenden Effekten:
bash
a=1
b=4
echo {$a..$b} # Funktioniert nicht wie erwartet
Die Variablen werden erst nach der Brace Expansion aufgelöst, sodass die Sequenz nicht gebildet werden kann. Wer dennoch variable Grenzen verwenden möchte, muss auf andere Konstrukte wie die seq– oder eval-Mechanismen zurückgreifen.
Eckige Klammern [] – Das Erbe des Globings
Während die geschweiften Klammern unabhängig vom Dateisystem arbeiten, sind die eckigen Klammern untrennbar mit der Welt der Dateinamen verbunden. Sie sind Teil des sogenannten Globbings – der Mustererkennung für Dateinamen, die neben * und ? die dritte Säule der Shell-Patterns bildet.
Zeichenklassen und Bereiche
Die Syntax ist einfach: Alles zwischen den eckigen Klammern repräsentiert eine Menge von Zeichen, von denen genau eines matchen muss:
bash
ls file[A-C] # Matcht fileA, fileB, fileC ls file[a-c] # Matcht filea, fileb, filec ls file[1-3] # Matcht file1, file2, file3 ls file[ACE] # Matcht fileA, fileC, fileE[reference:6]
Negation ist durch ^ oder ! möglich:
bash
ls file[^A-C] # Matcht alles außer fileA, fileB, fileC ls file[!A-C] # Gleiche Bedeutung[reference:7]
Die Kollationssequenz-Falle
Ein Thema, das in der Bash-Version 5.0 eine überraschende Wendung nahm, ist die Behandlung von Zeichenbereichen in Abhängigkeit von der System-Lokalisierung. Traditionell interpretierte die Bash [A-Z] als die ASCII-Zeichen von A bis Z – eine Erwartung, die sich in den Köpfen ganzer Generationen von Administratoren festgesetzt hatte.
Mit Bash 5.0 änderte sich dies. Die Option globasciiranges ist seitdem standardmäßig aktiviert. Das bedeutet: [A-Z] folgt nun der Kollationssequenz der aktuellen Locale. In manchen Locales können dadurch auch Kleinbuchstaben oder Umlaute in den Bereich fallen – mit potenziell verheerenden Folgen für Skripte, die auf das alte Verhalten angewiesen sind.
Die Lösung: Entweder die Option deaktivieren (shopt -u globasciiranges) oder die Locale temporär auf C setzen:
bash
( LC_ALL=C; echo [A-Z]* )
Glob versus Brace – Ein entscheidender Unterschied
Die eckigen Klammern operieren auf dem Dateisystem. Sie matchen existierende Dateien – sie erzeugen keine neuen. Die geschweiften Klammern hingegen generieren Zeichenketten, unabhängig davon, ob entsprechende Dateien existieren. Dieser Unterschied ist fundamental und erklärt viele der Missverständnisse, die Einsteiger (und manchmal auch Profis) in die Irre führen.
Runde Klammern () – Subshells und mehr
Die runden Klammern sind die vielseitigsten der drei Klammerarten. Sie treten in mehreren völlig unterschiedlichen Kontexten auf, die jeweils eigene Regeln und Eigenheiten mit sich bringen.
Kommandogruppierung in der Subshell
Die offensichtlichste Verwendung: Ein in runde Klammern gefasster Befehlsblock wird in einer eigenen Subshell ausgeführt:
bash
(cd /tmp && ls) # Wechselt in /tmp, listet auf, kehrt zurück
Der entscheidende Effekt: Alle Variablenzuweisungen innerhalb der Subshell bleiben ohne Wirkung auf die aufrufende Shell:
bash
a=1 (a=2) echo $a # Gibt 1 aus
Dieses Verhalten ist keine Schwäche, sondern eine bewusste Entscheidung. Die Subshell bietet eine isolierte Umgebung für temporäre Operationen – ein Konzept, das in der Shell-Programmierung von unschätzbarem Wert ist.
Arithmetische Expansion
Die doppelten runden Klammern ((...)) sind der moderne Weg für arithmetische Operationen:
bash
echo $((1 + 1)) # 2 echo $((010 + 1)) # 9 (oktale Falle!)
Die einfache runde Klammer in der Form $(...) dient der Befehlssubstitution – der Ausführung eines Befehls und der Ersetzung durch seine Ausgabe.
Die C-ähnliche Schleife
Eine weitere Sonderform sind die doppelten runden Klammern in Schleifenköpfen, die eine an C angelehnte Syntax ermöglichen:
bash
for ((i=0; i<10; i++)); do
echo $i
done
Diese Form ist deutlich flexibler als die klassische for i in {0..9}-Schleife, da sie variable Grenzen und komplexe Bedingungen erlaubt.
Geschweifte Klammern als Kommandogruppe {}
Neben der Brace Expansion kennen die geschweiften Klammern eine zweite, völlig eigenständige Verwendung: die Kommandogruppierung im aktuellen Shell-Kontext:
bash
{ sleep 10; cat; } | program
Im Gegensatz zur runden Klammer wird keine Subshell erzeugt. Variablenzuweisungen bleiben erhalten. Das hat Konsequenzen:
bash
a=1
{ a=2; }
echo $a # Gibt 2 aus
Wichtig: Die geschweiften Klammern sind Reserved Words, keine Operatoren. Sie müssen durch Leerzeichen oder andere Metazeichen vom umgebenden Text getrennt werden. Ein abschließendes Semikolon (oder ein Zeilenumbruch) ist zwingend erforderlich:
bash
{ echo foo; } # Korrekt
{echo foo;} # Falsch – Syntaxfehler
Der Unterschied zu den runden Klammern ist subtil, aber bedeutsam. Während (...) eine neue Umgebung schafft, bleibt {...} in der bestehenden – ein Unterschied, der bei Pipelines und Umleitungen entscheidend wird.
Prozesssubstitution <() und >() – Die versteckte Klammer
Eine der mächtigsten, aber auch am häufigsten übersehenen Funktionen der Bash ist die Prozesssubstitution. Sie ermöglicht es, die Ausgabe eines Befehls wie eine Datei zu behandeln:
bash
diff <(ls dir1) <(ls dir2)
Die Syntax < (list) oder > (list) erzeugt einen named pipe oder nutzt /dev/fd, um dem aufrufenden Befehl einen Dateinamen zu übergeben. Der listete Befehl läuft asynchron.
Ein klassisches Beispiel: der Vergleich zweier sortierter Dateien ohne temporäre Dateien:
bash
comm -3 <(sort a | uniq) <(sort b | uniq) # Zeigt einzigartige Zeilen[reference:22]
Warnung: Die Prozesssubstitution ist nicht POSIX-kompatibel. In Skripten, die strikte POSIX-Konformität erfordern, oder im POSIX-Modus der Bash (set -o posix) ist sie deaktiviert.
Ebenso kritisch: Zwischen < oder > und der öffnenden Klammer darf kein Leerzeichen stehen – sonst interpretiert die Shell den Ausdruck als Umleitung.
Die Geschichte der Ausrufezeichen – Historische Expansion !
Kein Artikel über die Bash-Syntax wäre vollständig ohne einen Blick auf die History Expansion. Ursprünglich aus der C-Shell (csh) übernommen, ermöglicht sie den Zugriff auf die Befehlshistorie:
bash
!! # Letzter Befehl !$ # Letztes Argument des letzten Befehls !* # Alle Argumente des letzten Befehls !^ # Erstes Argument !:2 # Zweites Argument[reference:29]
Die History Expansion wird nach dem Einlesen der Zeile, aber vor der Wortzerlegung durchgeführt. Sie ist in interaktiven Shells standardmäßig aktiviert.
Die Verbindung zu den Klammern? Die History Expansion verwendet das Ausrufezeichen – ein Zeichen, das in Kombination mit Klammern und anderen Sonderzeichen für zusätzliche Verwirrung sorgen kann. Die gute Nachricht: Sie kann mit set +H deaktiviert werden.
Die große Übersicht – Ein Vergleich
| Klammer | Hauptfunktion | Subshell? | Reihenfolge | Besonderheit |
|---|---|---|---|---|
{1..10} | Brace Expansion | Nein | Vor allen anderen | Generiert Zeichenketten |
{a,b,c} | Brace Expansion | Nein | Vor allen anderen | Beliebige Listen |
[a-z] | Globbing | Nein | Beim Dateinamen-Matching | Arbeitet auf dem Dateisystem |
$(cmd) | Befehlssubstitution | Ja | Vor der Ausführung | Ersetzt durch Ausgabe |
((...)) | Arithmetik | Nein | Vor der Ausführung | C-ähnliche Syntax |
(...) | Kommandogruppe | Ja | Zur Laufzeit | Eigene Umgebung |
{...} | Kommandogruppe | Nein | Zur Laufzeit | Aktuelle Umgebung |
<(cmd) | Prozesssubstitution | Ja (asynchron) | Parallel | Befehl als Datei |
Fazit und Ausblick
Die drei Klammern der Bash – [], {} und () – sind mehr als nur syntaktische Spielerei. Sie sind Ausdruck einer Design-Philosophie, die sich über fünf Jahrzehnte UNIX-Geschichte entwickelt hat. Jede Klammer hat ihre eigene Aufgabe, ihre eigene Geschichte und ihre eigenen Fallstricke.
Die geschweiften Klammern erzeugen Zeichenketten, bevor die Shell überhaupt an Dateien denkt. Die eckigen Klammern durchkämmen das Dateisystem nach Mustern. Die runden Klammern öffnen neue Umgebungen – sei es für temporäre Berechnungen, isolierte Befehlsausführungen oder die elegante Verkettung von Prozessen.
Wer diese Unterschiede versteht, wird nicht nur weniger Fehler machen, sondern auch die volle Kraft der Bash entfesseln können. Die Shell ist kein überholtes Relikt – sie ist ein lebendiges Zeugnis der Informatikgeschichte, das sich durch kluge Erweiterungen und behutsame Modernisierung bis heute behauptet.
Die Bash-Version 5.0 mit ihrer globasciiranges-Option ist ein Beispiel für diesen schmalen Grat zwischen Tradition und Fortschritt. Die Prozesssubstitution zeigt, wie selbst nicht-standardisierte Erweiterungen zu unverzichtbaren Werkzeugen werden können.
Die drei Klammern sind geblieben – und sie werden bleiben. Denn sie sind nicht nur Syntax, sie sind die DNA der Shell.
Quellen
- GNU Bash Reference Manual: Brace Expansion (Section 3.5.1)
- GNU Bash Reference Manual: Command Grouping (Section 3.2.5.3)
- GNU Bash Reference Manual: Process Substitution (Section 3.5.6)
- GNU Bash Reference Manual: History Interaction (Section 9.3)
- Linux Journal: Bash Brace Expansion (Mitch Frazier, 2008)
- Linux Journal: Bash Process Substitution (Mitch Frazier, 2008)
- Linux Handbook: Using Brace Expansion in Bash Shell (Abhishek Prakash, 2022)
- KodeKloud: Square – Globbing with Square Brackets
- GPN24: Die drei Klammern [] {} () und die BASH (Vortrag von Leyrer, Martin Schulte und Harald)
- Unix Stack Exchange: Diskussionen zu
globasciirangesund Collating Sequences
Kommentar abschicken