Streams und Pipes sind das Bindeglied zwischen den vielen kleinen Werkzeugen, aus denen Linux besteht. Wer stdin, stdout und stderr versteht und weiß, wie sich Datei-Deskriptoren umleiten und verketten lassen, kombiniert Befehle zu mächtigen Pipelines, in denen jeder Schritt nur eine Aufgabe übernimmt. Dieser Artikel führt dich vom Grundkonzept bis zu Process Substitution und den Stolperfallen, die selbst erfahrene Anwender regelmäßig in die Falle laufen lassen.

Drei Streams: stdin, stdout, stderr

Jeder Prozess unter Linux startet mit drei vorgegebenen Datenkanälen, die als Streams bezeichnet werden. Sie sind nichts anderes als offene Datei-Deskriptoren — kleine Ganzzahlen, mit denen der Kernel I/O-Quellen und -Ziele identifiziert. Im Terminal sind alle drei standardmäßig mit deinem Bildschirm und deiner Tastatur verbunden, lassen sich aber unabhängig voneinander umlenken.

FDNameZweckStandard-Ziel
0stdinEingabe lesenTastatur
1stdoutReguläre AusgabeTerminal
2stderrFehler- und DiagnoseausgabenTerminal

Die strikte Trennung zwischen stdout und stderr ist eine der besten Designentscheidungen von Unix. Sie erlaubt dir, das eigentliche Ergebnis eines Befehls in eine Datei oder eine Pipe zu schicken, während Fehlermeldungen weiterhin sichtbar bleiben — oder umgekehrt. Genau das macht Skripte robust: Du kannst Daten verarbeiten und Fehler getrennt protokollieren, ohne dass beides durcheinander gerät.

Bash Streams in Aktion
ls /etc /nichtvorhanden
Output
ls: cannot access '/nichtvorhanden': No such file or directory
/etc:
hostname  passwd  resolv.conf  ...

Optisch erscheinen beide Ausgaben gemischt — tatsächlich kommt die erste Zeile aus stderr (FD 2), die folgenden aus stdout (FD 1). Lenkt man einen der beiden um, bleibt der andere im Terminal stehen.

Ausgabe umleiten (>, >>)

Mit > schickst du stdout in eine Datei statt ins Terminal. Existiert die Datei bereits, wird sie kommentarlos überschrieben — ein häufiger Grund für verlorene Daten. Mit >> hängst du stattdessen an, was bestehende Inhalte unangetastet lässt und sich besonders für Logs eignet.

Bash Überschreiben vs. anhängen
echo "Erste Zeile" > log.txt        # erzeugt/überschreibt
echo "Zweite Zeile" >> log.txt      # hängt an
echo "Dritte Zeile" >> log.txt

Wer sich vor dem versehentlichen Überschreiben schützen möchte, aktiviert in Bash die Shell-Option noclobber. Danach verweigert die Shell jede >-Umleitung auf eine bestehende Datei. Soll ausnahmsweise doch überschrieben werden, erzwingst du das mit >|.

Bash noclobber als Schutz
set -o noclobber
echo "neu" > log.txt
echo "doch überschreiben" >| log.txt
Output
bash: log.txt: cannot overwrite existing file

Technisch ist > eine Kurzform von 1> — es bedeutet ausdrücklich „Datei-Deskriptor 1 (stdout) auf diese Datei umlenken”. Die explizite Schreibweise wird wichtig, sobald mehrere Streams gleichzeitig im Spiel sind.

Eingabe umleiten (<)

Symmetrisch dazu liest < den Inhalt einer Datei in stdin. Viele Werkzeuge akzeptieren Dateinamen direkt als Argument, andere aber nur über stdin — und manchmal ist die Umleitung schlicht semantisch klarer.

Bash Datei als stdin
wc -l < datei.txt

Der Unterschied zu wc -l datei.txt ist subtil: Bei der Umleitung sieht wc keinen Dateinamen und gibt nur die Zeilenzahl ohne Pfad aus. Skripte nutzen das gerne, um saubere, parsebare Ausgaben zu erzeugen.

Für mehrzeilige Eingaben, die direkt im Skript stehen sollen, bietet Bash Heredocs:

Bash Heredoc
cat <<EOF
Hallo Welt
Zweite Zeile
EOF

Variante <<-EOF entfernt führende Tabs aus jeder Zeile, was sauber eingerückte Skripte erlaubt. Wer die Expansion innerhalb des Heredocs unterdrücken will, setzt das Schlüsselwort in Anführungszeichen: <<'EOF'.

Für einzelne Zeichenketten gibt es die noch kürzere Form Herestring mit <<<:

Bash Herestring
wc -w <<< "drei kleine worte"
Output
3

stderr trennen oder kombinieren

Da stderr ein eigener Datei-Deskriptor ist, hat er eigene Operatoren: 2> leitet ihn in eine Datei um, 2>> hängt an. Die typische Übung ist, normale Ausgabe und Fehler getrennt zu protokollieren.

Bash stdout und stderr getrennt
skript.sh > out.log 2> err.log

Sollen beide Streams in dieselbe Datei laufen, schreibt man in POSIX-konformer Schreibweise zuerst die stdout-Umleitung und kopiert dann FD 2 auf das gleiche Ziel wie FD 1:

Bash Beides in eine Datei (POSIX)
skript.sh > kombiniert.log 2>&1

Bash bietet zusätzlich die Kurzform &>, die exakt dasselbe tut und sich gut liest, aber nicht POSIX-kompatibel ist und in dash oder sh-Skripten nicht funktioniert.

Bash Bash-Kurzform
skript.sh &> kombiniert.log
skript.sh &>> kombiniert.log    # anhängen

Das vielleicht häufigste Idiom überhaupt ist das stille Verwerfen von Fehlern nach /dev/null — etwa um störende „Permission denied”-Meldungen aus find zu unterdrücken:

Bash Fehler unterdrücken
find / -name "*.conf" 2>/dev/null

Pipes (|)

Eine Pipe verbindet die stdout eines Befehls direkt mit der stdin des nächsten. Statt Zwischendateien anzulegen, fließen die Daten Byte für Byte durch einen Kernel-Puffer von einem Prozess zum anderen. Genau das macht Pipes so schnell — und genau deshalb laufen die Stufen einer Pipe parallel, nicht hintereinander.

Bash Klassische Pipeline
ls -l | grep ".mdx" | wc -l
Output
12

Schritt für Schritt:

  1. ls -l listet das aktuelle Verzeichnis auf.
  2. grep ".mdx" filtert nur Zeilen mit .mdx.
  3. wc -l zählt die übrig gebliebenen Zeilen.

Alle drei Prozesse starten gleichzeitig. Sobald ls die ersten Zeilen geschrieben hat, beginnt grep zu lesen, und sobald grep etwas durchgelassen hat, zählt wc mit. Das hat zwei wichtige Konsequenzen: Pipelines können auf endlose Datenströme angewendet werden (z. B. tail -f log | grep ERROR), und ein langsames Glied bremst die ganze Kette aus, weil der Kernel-Pipe-Puffer (typischerweise 64 KiB) sonst volläuft und der Schreiber blockiert.

Wichtig: Pipes übertragen ausschließlich stdout. Will man auch stderr durch eine Pipe schicken, muss man es zuerst auf FD 1 zusammenführen — entweder mit dem klassischen 2>&1 | oder mit der Bash-Kurzform |&.

Bash stderr mit pipen
skript.sh 2>&1 | tee alles.log
skript.sh |& tee alles.log        # Bash-Kurzform

tee — abzweigen

tee ist der „T-Verteiler” der Shell: Es liest von stdin, schreibt gleichzeitig in eine oder mehrere Dateien und reicht den Stream unverändert auf stdout weiter. So kannst du eine Pipeline beobachten oder protokollieren, ohne sie zu unterbrechen.

Bash Pipeline mitschreiben
make 2>&1 | tee build.log | grep -E "warning|error"

Die komplette Build-Ausgabe landet in build.log, gleichzeitig sieht der Nutzer im Terminal nur Warnungen und Fehler. Mit -a wird angehängt statt überschrieben:

Bash An eine Logdatei anhängen
./run.sh | tee -a runs.log

Ein praktischer Sonderfall ist das Schreiben in privilegierte Dateien. Die Umleitung sudo echo x > /etc/datei schlägt fehl, weil die Shell die Umleitung vor sudo öffnet — also unprivilegiert. tee als separat aufgerufenes Programm umgeht das Problem:

Bash In Systemdatei schreiben
echo "neue zeile" | sudo tee -a /etc/hosts > /dev/null

Das > /dev/null am Ende verhindert, dass tee die Zeile noch einmal ins Terminal echot.

Process Substitution <(...), >(...)

Manchmal erwartet ein Befehl einen Dateinamen als Argument, du hast aber nur die Ausgabe eines anderen Befehls zur Hand. Genau dafür gibt es Process Substitution. Die Syntax <(cmd) führt cmd aus und ersetzt den Ausdruck durch den Pfad zu einem speziellen Datei-Deskriptor (in Linux-Bash typischerweise /dev/fd/63), aus dem die Ausgabe von cmd gelesen werden kann.

Bash Zwei Verzeichnisse vergleichen
diff <(ls dir1) <(ls dir2)

Intern legt die Shell für jede Substitution eine named pipe (FIFO) oder einen /dev/fd/-Eintrag an. diff öffnet diese Pseudo-Dateien wie reguläre Dateien — sieht aber nicht, dass dahinter laufende Prozesse stecken.

Die Schreibrichtung >(cmd) funktioniert spiegelbildlich: Was in den Pseudo-Pfad geschrieben wird, wandert auf die stdin von cmd. Damit lassen sich Streams gleichzeitig an mehrere Verbraucher schicken:

Bash Mehrere Konsumenten
cat groß.log | tee >(gzip > log.gz) >(grep ERROR > errors.txt) > /dev/null

Achtung: Process Substitution funktioniert nur dort, wo Konsumenten sequenziell lesen. Befehle, die seek() oder mmap() auf der Eingabedatei brauchen (z. B. einige Versionen von grep -m, viele Mediaplayer), scheitern, weil eine Pipe weder zurückspulen noch springen kann. Außerdem ist die Syntax bash- und zsh-spezifisch — dash und sh kennen sie nicht.

/dev/null, /dev/stdin, /dev/stdout

Linux stellt einige Pseudo-Dateien bereit, die sich wie reguläre Dateien verhalten, aber besondere Bedeutung haben.

PfadVerhaltenTypische Verwendung
/dev/nullNimmt alles entgegen, verwirft es; liefert beim Lesen sofort EOFUnerwünschte Ausgaben verwerfen
/dev/stdinVerweis auf FD 0 des ProzessesWerkzeuge fordern Datei, du willst stdin geben
/dev/stdoutVerweis auf FD 1Explizite Stdout-Adresse, z. B. für tar -f - Alternativen
/dev/stderrVerweis auf FD 2Diagnoseausgabe in Skripten erzwingen

/dev/null ist mit Abstand am häufigsten zu sehen — als „Mülltonne” der Shell:

Bash Typische /dev/null-Muster
cmd > /dev/null              # nur stderr behalten
cmd 2> /dev/null             # nur stdout behalten
cmd &> /dev/null             # alles verwerfen
cmd </dev/null               # leere Eingabe erzwingen

</dev/null ist besonders nützlich, wenn Hintergrundprozesse oder über SSH gestartete Befehle nicht auf eine Tastatureingabe warten sollen, die nie kommt.

Häufige Stolperfallen

`>` überschreibt Dateien stillschweigend

Der Operator > zerstört bestehenden Inhalt ohne Rückfrage — selbst wenn der ausführende Befehl gar nichts schreibt. cmd > wichtig.txt öffnet die Datei zum Schreiben, bevor cmd startet, und kürzt sie sofort auf null Bytes. Stürzt cmd danach ab, ist die Datei trotzdem leer. Schutz: set -o noclobber aktivieren und für Erzwingen >| schreiben. Für Logs grundsätzlich >> verwenden.

Reihenfolge bei `> file 2>&1` ist entscheidend

Die Shell wertet Umleitungen von links nach rechts aus. cmd > out.log 2>&1 lenkt zuerst FD 1 auf die Datei, dann FD 2 auf das, was FD 1 jetzt zeigt — also ebenfalls in die Datei. Schreibst du dagegen cmd 2>&1 > out.log, geht FD 2 zuerst aufs Terminal (denn dort zeigte FD 1 zu diesem Zeitpunkt) und nur FD 1 landet in der Datei. Resultat: Fehler stehen weiter auf dem Bildschirm. Wenn beides in eine Datei soll, immer erst die Zieldatei, dann 2>&1.

Pipes verbergen Exit-Codes

In cmd1 | cmd2 liefert die Shell standardmäßig nur den Exit-Code des letzten Befehls. Schlägt cmd1 fehl, cmd2 aber durch, gilt die Pipeline als erfolgreich — if, && und set -e werden getäuscht. Mit set -o pipefail setzt Bash den Exit-Code auf den ersten von links, der mit Fehler endet. Alternativ liefert ${PIPESTATUS[@]} ein Array mit allen Einzel-Codes. In ernsthaften Skripten gehört set -euo pipefail an den Anfang.

`sudo cmd > /etc/datei` schreibt nicht als root

Die Umleitung wird von deiner aktuellen Shell geöffnet, nicht von sudo. Ohne Schreibrechte auf die Zieldatei brichst du mit „Permission denied” ab, obwohl cmd selbst privilegiert läuft. Korrekt ist die Pipe an sudo tee: cmd | sudo tee /etc/datei > /dev/null. Soll angehängt werden, tee -a benutzen. Alternativ funktioniert auch sudo sh -c 'cmd > /etc/datei', weil dort die Umleitung erst innerhalb der privilegierten Subshell stattfindet.

Buffering bricht Live-Ausgaben in Pipes

Wenn stdout keine Konsole, sondern eine Pipe ist, schalten viele Programme automatisch auf Block-Buffering (typischerweise 4 KiB) um. Aus tail -f log | grep ERROR wird so eine ruckelnde Ausgabe, die erst nach Tausenden von Zeilen reagiert. Lösungen: grep --line-buffered, awk mit fflush(), python -u, oder generisch stdbuf -oL cmd | ... und unbuffer cmd | ... aus dem expect-Paket. Wer das nicht weiß, glaubt fälschlich, das Tool sei eingefroren.

`tee` schreibt selbst, wenn die Pipe-Senke nicht liest

Schließt der nachgelagerte Befehl in cmd | tee log | grep ... seine Eingabe vorzeitig (z. B. weil head -n 1 nach einer Zeile aufgibt), erhält tee ein SIGPIPE und beendet sich — die Logdatei kann unvollständig sein, obwohl cmd weitergelaufen wäre. Wer wirklich alles mitschreiben will, hängt tee an das Pipeline-Ende oder nutzt tee -p/Process Substitution: cmd > >(tee log) | grep .... Bei sehr großen Streams sollte man außerdem an Plattenplatz denken — tee puffert nicht, schreibt aber kontinuierlich.

`&>` ist Bash, nicht POSIX

Die hübsche Kurzform &> file ist eine Bash-Erweiterung, die in dash, ash oder klassischem sh schweigend falsch interpretiert wird — typisch als „starte Prozess im Hintergrund (&) und leite stdout (>) um”. Das Skript läuft scheinbar, fängt Fehler aber nicht ab. Für portable Skripte mit #!/bin/sh immer die ausgeschriebene Form > file 2>&1 verwenden. Auch |& und Process Substitution sind nicht POSIX.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Grundlagen

Zur Übersicht