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.

Bash Array anlegen und zugreifen
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:

Bash Indizes direkt setzen
arr[0]="erstes"
arr[5]="sechstes"
echo "${arr[5]}"

Wer alle Elemente will, schreibt ${arr[@]} oder ${arr[*]}. Der Unterschied liegt im Quoting:

AusdruckVerhalten
"${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.

Bash Assoziatives Array anlegen
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:

Bash Assoziatives Array initialisieren
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:

Bash Iteration über Keys
for key in "${!user[@]}"; do
    echo "$key = ${user[$key]}"
done

Wichtig: 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.

Bash Anzahl Elemente
arr=(a b c)
arr[10]="z"
echo "${#arr[@]}"
echo "${arr[10]}"
Output
4
z

Die 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:

Bash Existenz prüfen
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:

Bash Sichere Iteration
files=("report 2025.pdf" "config.yml" "notes.md")
for f in "${files[@]}"; do
    echo "Datei: $f"
done

Ohne 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:

Bash Index-basierte Iteration
arr=(rot gruen blau)
for i in "${!arr[@]}"; do
    echo "$i: ${arr[$i]}"
done

Diese 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:

Bash Array slicen
arr=(a b c d e f g)
echo "${arr[@]:1:3}"
echo "${arr[@]:4}"
Output
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:

Bash Substring auf Element
arr=("Hallo Welt" "Bash Arrays")
echo "${arr[0]:6:4}"
Output
Welt

Mit [@] 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:

Bash Elemente anhängen
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.

Bash unset hinterlässt Lücken
arr=(a b c d)
unset 'arr[1]'
echo "${!arr[@]}"
echo "${#arr[@]}"
Output
0 2 3
3

Index 1 ist weg, aber 2 und 3 rutschen nicht nach. Wer eine kompakte Indizierung braucht, baut das Array neu auf:

Bash Re-Indizierung
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:

Bash mapfile aus Datei
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:

Bash readarray mit Process Substitution
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:

Bash Files in Array
shopt -s nullglob
files=(/var/log/*.log)
echo "Gefunden: ${#files[@]} Logs"
for f in "${files[@]}"; do
    echo "$f"
done

Counter 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:

Bash Counter pro Wert
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]}"
done

Word-Frequency aus einer Datei — Tokenizen und in ein assoziatives Array zählen; klein, aber überraschend nützlich für Log-Analysen:

Bash Word-Frequency
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 | head

Default-Liste mit Override — eine Standardauswahl, die per Argument überschrieben werden kann, ohne dass die Schleife sich ändert:

Bash Default-Liste
default_targets=(web db cache)
targets=("${@:-${default_targets[@]}}")
for t in "${targets[@]}"; do
    echo "Deploy: $t"
done

Zwei Arrays zusammenführen — die Konkatenation funktioniert per Expansion, nicht per Operator. Beide Quellen werden expandiert und in ein neues Array geschrieben:

Bash Array-Concat
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:

Bash JSON-light
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

&#36;arr ohne Index expandiert nicht zum gesamten Array, sondern zu &#36;&#123;arr[0]&#125;. Das ist der absolute Klassiker: for x in &#36;arr läuft genau einmal und meistens nicht so, wie der Autor dachte. Die korrekte Form ist immer "&#36;&#123;arr[@]&#125;" 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: &#36;&#123;arr[@]&#125; 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 "&#36;&#123;arr[@]&#125;" — 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&lt;&#36;&#123;#arr[@]&#125;; 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=("&#36;&#123;arr[@]&#125;") 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

"&#36;&#123;arr[*]&#125;" (Stern statt Klammeraffe) konkateniert die Elemente zu einem String — und nutzt dabei das erste Zeichen aus IFS als Trenner. Wer im Skript IFS=&#36;'\n\t' setzt (siehe robuster Header), bekommt Newlines statt Spaces. Das ist meist gewollt, kann aber überraschen, wenn man später echo "&#36;&#123;arr[*]&#125;" debuggt und plötzlich Zeilenumbrüche sieht. Für sichtbares Debugging hilft printf '%s\n' "&#36;&#123;arr[@]&#125;" — eine Zeile pro Element.

arr-gleich-cmd-output ist gefährlich

arr=( &#36;(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 &#42; im Output matcht Dateinamen im aktuellen Verzeichnis. Die korrekte Form ist mapfile -t arr &lt; &lt;(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

  • Schleifenfor, while und until ü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
/ Weiter

Zurück zu Shell-Scripting

Zur Übersicht