MWCodebymw.de ↗
Terminal

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/b

Mit <(...) 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

#Bash#Shell#Terminal#Pipes#Automatisierung

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 →