Arrays sind in Bash ein vollwertiges Werkzeug — und zugleich eines der unterschätzten. Wer Listen von Dateinamen, Konfig-Werten oder Zähler-Mappings sauber halten will, kommt um Arrays nicht herum. Bash kennt zwei Sorten: indizierte Arrays mit numerischen Schlüsseln und assoziative Arrays mit beliebigen String-Schlüsseln. Beide haben eigene Syntax, eigene Stolperfallen und ein Quoting-Verhalten, das man einmal wirklich verstanden haben muss.
Indizierte Arrays
Ein indiziertes Array wird mit Klammern und Whitespace-getrennten Werten angelegt. Die Indizierung beginnt bei 0, wie in C oder Python.
arr=(alpha beta gamma)
echo "${arr[0]}"
echo "${arr[2]}"Einzelne Indizes lassen sich auch direkt setzen — auch mit Lücken, das Array bleibt gültig:
arr[0]="erstes"
arr[5]="sechstes"
echo "${arr[5]}"Wer alle Elemente will, schreibt ${arr[@]} oder ${arr[*]}. Der Unterschied liegt im Quoting:
| Ausdruck | Verhalten |
|---|---|
"${arr[@]}" | Jedes Element bleibt ein eigenes Wort — auch mit Leerzeichen drin |
"${arr[*]}" | Alle Elemente werden zu einem String, getrennt durch das erste Zeichen aus IFS |
${arr[@]} (unquoted) | Jedes Element wird Word-Splitting unterzogen — Spaces zerstören Strukturen |
${arr[*]} (unquoted) | Wie [@] unquoted — Word-Splitting auf dem zusammengebauten String |
Faustregel: Immer "${arr[@]}" mit Anführungszeichen, sobald du das Array an eine Schleife oder einen Befehl weitergibst. Alles andere ist die Eintrittskarte zu kaputten Dateinamen.
Assoziative Arrays
Assoziative Arrays bilden String-Schlüssel auf Werte ab — wie ein Dictionary in Python oder eine Map in Go. Sie müssen explizit deklariert werden, sonst behandelt Bash sie als indiziertes Array und konvertiert die Schlüssel still zu Zahlen.
declare -A user
user[name]="Michael"
user[shell]="bash"
user[home]="/home/michael"
echo "${user[name]}"Die Initialisierung in einem Rutsch ist ebenfalls möglich:
declare -A colors=(
[rot]="#ff0000"
[gruen]="#00ff00"
[blau]="#0000ff"
)
echo "${colors[gruen]}"Iteriert wird über die Schlüssel mit ${!map[@]}. Die Reihenfolge ist nicht garantiert — assoziative Arrays sind ungeordnete Hash-Maps:
for key in "${!user[@]}"; do
echo "$key = ${user[$key]}"
doneWichtig: Assoziative Arrays gibt es erst ab Bash 4. Auf macOS liegt unter /bin/bash immer noch Bash 3.2 — Skripte mit declare -A brauchen dort einen Shebang auf eine moderne Bash, etwa #!/usr/bin/env bash mit Homebrew-Bash im PATH. Mehr dazu in den Skript-Grundlagen.
Größe und Existenz
Die Anzahl der Elemente liefert ${#arr[@]}. Achtung: Das ist die Anzahl gesetzter Elemente, nicht der höchste Index — bei Lücken weichen die beiden Werte ab.
arr=(a b c)
arr[10]="z"
echo "${#arr[@]}"
echo "${arr[10]}"4
zDie Länge eines einzelnen Strings im Array fragst du mit ${#arr[i]} ab — also mit Index, ohne [@].
Existenz-Prüfung ist subtiler, als man denkt. Ein einfaches [ -z "${arr[5]}" ] unterscheidet nicht zwischen „existiert nicht” und „existiert, ist aber leer”. Der saubere Trick ist die ${var+x}-Erweiterung:
if [ "${arr[5]+gesetzt}" = "gesetzt" ]; then
echo "Index 5 existiert"
else
echo "Index 5 ist nicht gesetzt"
fi${arr[5]+gesetzt} expandiert zu gesetzt, wenn das Element existiert — egal ob leer oder nicht. Ist das Element nicht gesetzt, expandiert es zu nichts.
Ein leeres Array unterscheidet sich von einem nicht existierenden: arr=() ist deklariert mit Länge 0, unset arr löscht die Variable komplett. Beide haben ${#arr[@]} == 0, aber nur das zweite scheitert an set -u.
Iteration
Die einzig richtige Form, über ein Array zu iterieren, ist for x in "${arr[@]}". Mit Anführungszeichen, mit [@], ohne Tricks:
files=("report 2025.pdf" "config.yml" "notes.md")
for f in "${files[@]}"; do
echo "Datei: $f"
doneOhne die Anführungszeichen würde Bash report 2025.pdf an dem Leerzeichen splitten und zwei Iterationen mit report und 2025.pdf erzeugen.
Ein häufiger Anfänger-Fehler ist for x in $arr. Das tut nicht, was es suggeriert: $arr ohne Index ist eine Kurzform für ${arr[0]}. Die Schleife läuft also genau einmal, mit dem ersten Element — der Rest ist verloren.
Wenn du auch den Index brauchst, iterierst du über ${!arr[@]} (Schlüssel-Liste) und greifst pro Iteration auf den Wert zu:
arr=(rot gruen blau)
for i in "${!arr[@]}"; do
echo "$i: ${arr[$i]}"
doneDiese Form funktioniert auch bei Lücken — anders als ein C-Stil-Loop mit for ((i=0; i<${#arr[@]}; i++)), der bei dünn besetzten Arrays falsche Ergebnisse liefert.
Slicing und Substring
Bash kann Teilbereiche eines Arrays mit der Syntax ${arr[@]:start:laenge} herausschneiden. Das Ergebnis ist eine neue Wertfolge — kein referenzierendes Slice:
arr=(a b c d e f g)
echo "${arr[@]:1:3}"
echo "${arr[@]:4}"b c d
e f g${arr[@]:1:3} liefert drei Elemente ab Index 1. Lässt du den Längen-Teil weg (${arr[@]:4}), bekommst du alle ab dem Startindex.
Die gleiche Syntax funktioniert auch auf einzelnen String-Elementen — dort meint sie dann ein Substring:
arr=("Hallo Welt" "Bash Arrays")
echo "${arr[0]:6:4}"WeltMit [@] slicest du das Array, mit [0] (oder einem anderen Index) den String an dieser Position. Die Doppelbedeutung ist verwirrend — am besten merken: [@]: schneidet Array-Teile, [i]: schneidet Strings.
Hinzufügen und Entfernen
Elemente am Ende anhängen geht mit +=. Der Operator akzeptiert wieder eine Klammer-Liste, du kannst also mehrere Elemente in einem Schritt hinzufügen:
arr=(a b c)
arr+=(d)
arr+=(e f g)
echo "${arr[@]}"Entfernen geht mit unset — und hier wartet eine echte Falle: unset arr[2] entfernt das Element an Index 2, aber die übrigen Indizes bleiben unverändert. Es entsteht eine Lücke.
arr=(a b c d)
unset 'arr[1]'
echo "${!arr[@]}"
echo "${#arr[@]}"0 2 3
3Index 1 ist weg, aber 2 und 3 rutschen nicht nach. Wer eine kompakte Indizierung braucht, baut das Array neu auf:
arr=("${arr[@]}")
echo "${!arr[@]}"Die Zuweisung arr=("${arr[@]}") expandiert alle gesetzten Werte und schreibt sie in ein frisches Array — die Indizes laufen anschließend wieder lückenlos von 0.
Die einzelnen Anführungszeichen um 'arr[1]' bei unset sind übrigens nötig, damit Bash das [1] nicht als Glob-Pattern auf Dateinamen interpretiert, falls eine Datei wie arr1 oder arrA zufällig existiert.
Befehlsoutput in ein Array einlesen
Das naheliegende arr=( $(cmd) ) ist fast immer falsch. Bash unterzieht den Output Word-Splitting an IFS (Default: Space, Tab, Newline) und führt anschließend Glob-Expansion durch. Eine Ausgabezeile mit Leerzeichen wird zu zwei Elementen, ein * im Output matcht Dateinamen im Working Directory.
Die saubere Lösung ist mapfile (alias readarray), das zeilenweise in ein Array einliest — und nichts splitten muss:
mapfile -t lines < /etc/hostname
echo "Erste Zeile: ${lines[0]}"Das -t entfernt den abschließenden Newline jeder Zeile, sonst hängt am Ende jedes Elements ein \n.
Aus einem Befehl liest du mit Process Substitution ein:
readarray -t users < <(cut -d: -f1 /etc/passwd)
echo "Anzahl User: ${#users[@]}"
echo "Erster: ${users[0]}"mapfile und readarray sind identisch — verschiedene Namen für denselben Builtin. Auf modernen Bash-Versionen (4.0+) immer verfügbar. Für einzeilige Outputs (zum Beispiel ein einziger Wert) nutzt du natürlich keine Array-Form, sondern eine normale Zuweisung mit var=$(cmd).
Praxis-Patterns
Sechs Array-Patterns, die in fast jedem ernsthaften Skript vorkommen — jeweils mit kurzer Erläuterung.
Dateien aus einem Glob in ein Array — die Glob-Iteration übernimmt Quoting korrekt; mit nullglob ist auch ein leeres Verzeichnis sicher:
shopt -s nullglob
files=(/var/log/*.log)
echo "Gefunden: ${#files[@]} Logs"
for f in "${files[@]}"; do
echo "$f"
doneCounter pro Wert mit assoziativem Array — der Klassiker für Statistiken; jeder neue Schlüssel beginnt bei 0, wenn er in einer Arithmetik-Substitution gelesen wird:
declare -A count
for status in OK FAIL OK OK FAIL ERROR; do
count[$status]=$((${count[$status]:-0} + 1))
done
for k in "${!count[@]}"; do
echo "$k: ${count[$k]}"
doneWord-Frequency aus einer Datei — Tokenizen und in ein assoziatives Array zählen; klein, aber überraschend nützlich für Log-Analysen:
declare -A freq
while IFS= read -r word; do
freq[$word]=$((${freq[$word]:-0} + 1))
done < <(tr -s '[:space:]' '\n' < text.txt)
for w in "${!freq[@]}"; do
echo "${freq[$w]} $w"
done | sort -rn | headDefault-Liste mit Override — eine Standardauswahl, die per Argument überschrieben werden kann, ohne dass die Schleife sich ändert:
default_targets=(web db cache)
targets=("${@:-${default_targets[@]}}")
for t in "${targets[@]}"; do
echo "Deploy: $t"
doneZwei Arrays zusammenführen — die Konkatenation funktioniert per Expansion, nicht per Operator. Beide Quellen werden expandiert und in ein neues Array geschrieben:
a=(rot gruen)
b=(blau gelb)
c=("${a[@]}" "${b[@]}")
echo "${c[@]}"JSON-light aus assoziativem Array — schnell ein lesbares Pseudo-JSON für Logs oder Debug-Ausgaben bauen, ohne jq zu starten:
declare -A obj=([name]="Michael" [shell]="bash" [version]="5.2")
out="{"
sep=""
for k in "${!obj[@]}"; do
out+="$sep\"$k\":\"${obj[$k]}\""
sep=","
done
out+="}"
echo "$out"Stolperfallen mit Arrays
dollar-arr ist nur das erste Element
$arr ohne Index expandiert nicht zum gesamten Array, sondern zu ${arr[0]}. Das ist der absolute Klassiker: for x in $arr läuft genau einmal und meistens nicht so, wie der Autor dachte. Die korrekte Form ist immer "${arr[@]}" mit [@] und Anführungszeichen. Wer die Schreibweise konsequent verwendet, hat dieses Problem nie wieder.
Unquoted dollar-arr-at zerstört Whitespace in Elementen
Selbst mit [@] ist die Schlacht noch nicht gewonnen: ${arr[@]} ohne Anführungszeichen wird Word-Splitting unterzogen. Ein Element wie report 2025.pdf zerfällt in zwei Worte und die Schleife sieht plötzlich mehr Iterationen, als das Array Elemente hat. Die einzige sichere Form ist "${arr[@]}" — Anführungszeichen sind hier nicht optional, sondern Pflicht.
Assoziative Arrays brauchen declare -A
Ohne declare -A interpretiert Bash map[name]=wert als indiziertes Array und versucht, name zu einer Zahl zu casten — das Ergebnis ist meist 0. Es gibt keine Fehlermeldung, das Skript läuft scheinbar normal, aber alle Schlüssel landen am Index 0 und überschreiben sich gegenseitig. Wer ein Mapping mit String-Schlüsseln will, muss declare -A an erster Stelle haben.
unset hinterlässt Lücken statt Re-Indizierung
Nach unset arr[2] ist Element 2 weg, aber Index 3, 4 und so weiter rutschen nicht nach. Schleifen über for ((i=0; i<${#arr[@]}; i++)) laufen plötzlich falsch, weil die Länge nicht mehr zum höchsten Index passt. Wenn Indizes lückenlos bleiben sollen, baust du das Array nach dem unset mit arr=("${arr[@]}") neu auf.
macOS System-Bash kennt keine assoziativen Arrays
/bin/bash auf macOS ist seit Apple Bash 3.2 unter GPLv2 weiterhin friert — declare -A schlägt mit „bash: declare: -A: invalid option” fehl. Skripte, die assoziative Arrays brauchen, laufen auf macOS nur mit Homebrew-Bash 4+. Shebang #!/usr/bin/env bash greift dann die Bash aus dem PATH, und mit Homebrew zuerst im PATH landest du auf der modernen Version. Ohne diese Vorkehrung scheitert das Skript auf jedem frisch eingerichteten Mac.
IFS beeinflusst Array-Expansion stark
"${arr[*]}" (Stern statt Klammeraffe) konkateniert die Elemente zu einem String — und nutzt dabei das erste Zeichen aus IFS als Trenner. Wer im Skript IFS=$'\n\t' setzt (siehe robuster Header), bekommt Newlines statt Spaces. Das ist meist gewollt, kann aber überraschen, wenn man später echo "${arr[*]}" debuggt und plötzlich Zeilenumbrüche sieht. Für sichtbares Debugging hilft printf '%s\n' "${arr[@]}" — eine Zeile pro Element.
arr-gleich-cmd-output ist gefährlich
arr=( $(cmd) ) sieht harmlos aus, aber Bash splittet den Output an IFS und expandiert anschließend Globs. Eine Zeile mit Leerzeichen wird zu mehreren Elementen, ein * im Output matcht Dateinamen im aktuellen Verzeichnis. Die korrekte Form ist mapfile -t arr < <(cmd) für zeilenweises Lesen — kein Splitting, kein Globbing, jede Zeile ein Element. Pflichtwissen für jeden, der Befehlsoutput weiterverarbeitet.
Weiterführende Ressourcen
Externe Quellen
- Bash-Manpage: Arrays — Offizielle Referenz für indizierte und assoziative Arrays
- GNU Bash-Handbuch: Arrays — Vollständige Beschreibung mit allen Syntax-Varianten
- BashFAQ 005: How can I use array variables? — Praktischer Leitfaden zu Arrays in der Bash
- BashPitfalls — Sammlung typischer Array-Stolperfallen mit Erklärungen
Verwandte Artikel
- Schleifen —
for,whileunduntilüber Array-Elemente - Parameter — Positionsparameter
$@als Array verstehen - String-Manipulation — Substring und Pattern-Replacement auf Array-Elementen
- Variablen — Shell-Variablen und der Zusammenhang zu Arrays
- Skript-Grundlagen — Shebang und robuster Header für Bash-4-Features