Warum deine Zählvariable nach der Schleife wieder 0 ist – Prozess-Substitution in Bash
Ein Klassiker, der jeden einmal erwischt: Du zählst in einer while-Schleife hoch, und hinterher steht die Variable trotzdem auf 0. Schuld ist eine Subshell hinter der Pipe. Mit Prozess-Substitution behältst du das Ergebnis – und kannst nebenbei zwei Befehle direkt vergleichen.
Dieser Fehler hat mich früher eine gute halbe Stunde gekostet, und ich wette, dich auch schon mal. Du schreibst ein Skript, das Zeilen durchgeht und dabei mitzählt – und am Ende ist der Zähler seelenruhig auf 0, obwohl die Schleife offensichtlich gelaufen ist. Kein Fehler, keine Warnung. Nur eine Zahl, die nicht stimmt.
Der Klassiker, der jeden erwischt
count=0
grep -c "" datei.txt | echo # nur zur Deko
printf 'a\nb\nc\n' | while read -r zeile; do
count=$((count + 1))
done
echo "Zeilen: $count" # -> Zeilen: 0 😖Die Schleife läuft dreimal, count wird brav hochgezählt – und trotzdem steht am Ende 0. Der Grund: Alles rechts von der Pipe | läuft in einer Subshell, also in einer eigenen Kopie der aktuellen Shell. Diese Kopie zählt fleißig hoch, aber wenn die Pipe endet, verschwindet sie samt ihrem count. Die äußere Shell hat davon nie etwas gesehen.
Die Lösung: Prozess-Substitution
Statt den Befehl in die Schleife zu pipen, hängst du ihn mit < <(...) als Eingabe an. Damit läuft die Schleife in deiner eigentlichen Shell weiter, und die Variable überlebt:
count=0
while read -r zeile; do
count=$((count + 1))
done < <(printf 'a\nb\nc\n')
echo "Zeilen: $count" # -> Zeilen: 3 ✅Das <(...) verwandelt die Ausgabe eines Befehls in etwas, das sich wie eine Datei liest – Bash legt dafür intern einen Datei-Deskriptor an. Das äußere < reicht diese „Datei" als Standard-Eingabe in die Schleife. Wichtig ist die Lücke zwischen den beiden Zeichen: < <( – zusammengeschrieben (<<) wäre es ein Here-Document und etwas völlig anderes.
Der zweite Grund, das zu kennen: Befehle direkt vergleichen
Prozess-Substitution glänzt noch woanders. Willst du zwei Kommando-Ausgaben vergleichen, brauchst du normalerweise zwei temporäre Dateien:
sort datei_a.txt > /tmp/a
sort datei_b.txt > /tmp/b
diff /tmp/a /tmp/b
rm /tmp/a /tmp/bMit <(...) fällt das ganze Aufräumen weg – die „Dateien" existieren nur, solange der Befehl läuft:
diff <(sort datei_a.txt) <(sort datei_b.txt)Ich benutze das ständig, etwa um zu prüfen, ob zwei Server dieselben installierten Pakete haben, oder ob sich eine generierte Konfigurationsdatei gegenüber dem letzten Stand verändert hat. Kein Temp-Datei-Chaos, nichts, das man zu löschen vergisst.
> Kleiner Haken: Prozess-Substitution gibt es in Bash und Zsh, aber nicht in der reinen POSIX-sh (etwa dash, unter dem auf vielen Systemen /bin/sh läuft). Wenn dein Skript portabel bleiben muss, schreib bewusst #!/usr/bin/env bash in die erste Zeile – dann weißt du, worauf du dich verlässt.
Solche kleinen Terminal-Kniffe sind genau das, was am Ende den Unterschied zwischen einem Skript macht, das „irgendwie läuft", und einem, dem man vertrauen kann. Ich baue Websites und Android-Apps und automatisiere den ganzen Kram drumherum am liebsten sauber – wenn du dabei Unterstützung suchst, findest du mich auf bymw.de.
Quellen
Du brauchst mehr als ein Snippet?
Ich entwickle Android-Apps in Kotlin und moderne Websites für Selbstständige und kleine Unternehmen — von der ersten Idee bis zum Release.
Projekt anfragen →Verwandte Snippets
Pfade und Namen zerlegen — ganz ohne basename, dirname und sed
Die Shell kann Zeichenketten selbst zerschneiden. Kein Aufruf eines externen Programms, keine Pipe – nur geschweifte Klammern. Wenn du das einmal kennst, werden deine Skripte kürzer und deutlich schneller.
cron – Aufgaben, die von selbst laufen (und warum sie es oft nicht tun)
Nächtliches Backup, wöchentlicher Bericht, stündliche Bereinigung: Fünf Zahlen und ein Befehl reichen. Der häufigste Fehler ist dabei nicht die Zeitangabe, sondern eine Umgebung, die ganz anders aussieht als dein Terminal.
getopts – Skript-Parameter, die sich wie richtige Befehle anfühlen
$1, $2, $3 funktioniert genau so lange, bis man einen Parameter weglassen will. Mit getopts bekommt dein Skript benannte Optionen, eine Hilfe und eine klare Fehlermeldung – in fünfzehn Zeilen.