Was sind stdin, stdout und stderr unter Linux?

Beim Start eines Linux-Befehls werden drei Datenströme initialisiert: stdin, stdout und stderr. Diese Ströme sind entscheidend, um das Verhalten von Skripten bei Weiterleitung oder Umleitung zu verstehen. Wir zeigen Ihnen, wie das funktioniert.

Datenströme als Verbindungsglieder

Bei der Auseinandersetzung mit Linux und Unix-artigen Systemen begegnet man häufig den Begriffen stdin, stdout und stderr. Diese stellen drei Standarddatenströme dar, die bei der Ausführung eines Linux-Befehls entstehen. In der Informatik repräsentiert ein Datenstrom eine Möglichkeit zur Datenübertragung, wobei es sich in diesen Fällen um Textdaten handelt.

Datenströme, ähnlich wie Wasserläufe, haben zwei Endpunkte – eine Quelle und einen Abfluss. Jeder ausgeführte Linux-Befehl stellt ein Ende dieser Ströme dar. Das andere Ende wird von der Shell bestimmt, die den Befehl initiiert hat. Dieses Ende ist entweder mit dem Terminalfenster verbunden, durch eine Pipe mit einem anderen Befehl verknüpft oder zu einer Datei oder einem anderen Befehl umgeleitet.

Die Standardströme in Linux

In Linux ist stdin der Standardeingabestrom, der Text als Eingabe akzeptiert. Die normale Textausgabe eines Befehls an die Shell erfolgt über den Stream stdout (Standardausgabe). Fehlermeldungen des Befehls werden über den Stream stderr (Standardfehler) gesendet.

Es gibt also zwei Ausgabeströme (stdout und stderr) und einen Eingabestrom (stdin). Durch separate Kanäle für Fehlermeldungen und normale Ausgaben können diese unabhängig voneinander behandelt werden.

Datenströme als Dateien

In Linux werden Datenströme wie Dateien behandelt. Daten können aus einer Datei gelesen und in eine Datei geschrieben werden, wobei beide Aktionen Datenströme involvieren. Die Verarbeitung eines Datenstroms als Datei ist daher ein gängiges Konzept.

Jeder Datei, die einem Prozess zugeordnet ist, wird eine eindeutige Nummer (Dateideskriptor) zur Identifizierung zugewiesen. Bei Aktionen mit einer Datei wird der Dateideskriptor verwendet, um die entsprechende Datei zu referenzieren.

Folgende Werte werden für stdin, stdout und stderr verwendet:

0: stdin
1: stdout
2: stderr

Reaktion auf Pipes und Umleitungen

Oft wird zur Vereinfachung von Themen eine vereinfachte Darstellung verwendet. Bei Grammatikregeln heißt es beispielsweise „I vor E, außer nach C“. Es gibt jedoch mehr Ausnahmen von dieser Regel als Fälle, die sie befolgen.

Ähnlich verhält es sich bei der Betrachtung von stdin, stdout und stderr. Eine vereinfachte Annahme ist, dass ein Prozess nicht weiß oder sich nicht darum kümmert, wo seine Standardströme enden. Sollte ein Prozess berücksichtigen, ob seine Ausgabe im Terminal angezeigt oder in eine Datei umgeleitet wird? Kann er überhaupt erkennen, ob seine Eingabe von der Tastatur stammt oder von einem anderen Prozess kommt?

Tatsächlich kann ein Prozess dies erkennen – oder zumindest herausfinden, wenn er es prüfen möchte – und sein Verhalten entsprechend anpassen, sofern der Softwareentwickler diese Funktionalität implementiert.

Diese Verhaltensänderung lässt sich leicht beobachten. Führen Sie die folgenden Befehle aus:

ls

ls | cat

Der Befehl ls verhält sich anders, wenn seine Ausgabe (stdout) an einen anderen Befehl weitergeleitet wird. ls wechselt zu einer einspaltigen Ausgabe, dies ist keine Konvertierung durch cat. ls verhält sich auch so, wenn seine Ausgabe umgeleitet wird:

ls > capture.txt

cat capture.txt

Umleitung von stdout und stderr

Die Trennung von Fehlermeldungen durch einen eigenen Stream ist vorteilhaft. Dadurch kann die Ausgabe eines Befehls (stdout) in eine Datei umgeleitet werden, während Fehlermeldungen (stderr) weiterhin im Terminal angezeigt werden. Fehler können so schnell erkannt und behoben werden. Zudem wird vermieden, dass Fehlermeldungen die Datei verfälschen, in die stdout umgeleitet wurde.

Geben Sie den folgenden Text in einen Texteditor ein und speichern Sie ihn als error.sh:

#!/bin/bash

echo "Versuche auf eine nicht existierende Datei zuzugreifen"
cat bad-filename.txt

Machen Sie das Skript mit diesem Befehl ausführbar:

chmod +x error.sh

Die erste Zeile des Skripts gibt Text über den stdout-Stream an das Terminalfenster aus. Die zweite Zeile versucht, auf eine nicht existierende Datei zuzugreifen, was zu einer Fehlermeldung über stderr führt.

Führen Sie das Skript mit diesem Befehl aus:

./error.sh

Beide Ausgabeströme, stdout und stderr, werden im Terminalfenster angezeigt.

Versuchen wir, die Ausgabe in eine Datei umzuleiten:

./error.sh > capture.txt

Die Fehlermeldung (stderr) wird weiterhin im Terminal angezeigt. Der Inhalt der Datei zeigt, dass die stdout-Ausgabe in die Datei geleitet wurde.

cat capture.txt

Die stdout-Ausgabe wurde wie erwartet in die Datei umgeleitet.

Das Symbol > leitet standardmäßig stdout um. Numerische Dateideskriptoren können verwendet werden, um den umzuleitenden Ausgabestrom anzugeben.

Um stdout explizit umzuleiten, verwenden Sie folgende Anweisung:

1>

Um stderr explizit umzuleiten, verwenden Sie diese Anweisung:

2>

Wiederholen wir den Test, diesmal mit 2>:

./error.sh 2> capture.txt

Die Fehlermeldung wird umgeleitet, während die stdout-Nachricht im Terminalfenster erscheint:

Die stderr-Nachricht befindet sich wie erwartet in der Datei capture.txt.

Gleichzeitige Umleitung von stdout und stderr

Wenn stdout und stderr unabhängig voneinander in eine Datei umgeleitet werden können, sollten beide gleichzeitig in zwei verschiedene Dateien umgeleitet werden können?

Ja, das ist möglich. Dieser Befehl leitet stdout in die Datei capture.txt und stderr in die Datei error.txt:

./error.sh 1> capture.txt 2> error.txt

Da beide Ausgabeströme in Dateien umgeleitet werden, ist keine Ausgabe im Terminal sichtbar. Es kehrt zur Befehlseingabeaufforderung zurück, als ob nichts geschehen wäre.

Lassen Sie uns die Inhalte beider Dateien überprüfen:

cat capture.txt
cat error.txt

Umleitung von stdout und stderr in dieselbe Datei

Es ist also möglich, jeden Standardausgabestrom in eine separate Datei umzuleiten. Die einzig andere sinnvolle Kombination ist die Umleitung von stdout und stderr in dieselbe Datei.

Dies kann mit folgendem Befehl erreicht werden:

./error.sh > capture.txt 2>&1

Lassen Sie uns das aufschlüsseln:

./error.sh: Startet das Skript error.sh.
> capture.txt: Leitet den stdout-Stream in die Datei capture.txt um. > entspricht 1>.
2>&1: Verwendet die &>-Umleitungsanweisung. Damit wird der Shell mitgeteilt, dass ein Stream das gleiche Ziel wie ein anderer Stream haben soll. In diesem Fall bedeutet es: „Leite Stream 2, stderr, zum gleichen Ziel um, an das Stream 1, stdout, umgeleitet wird“.

Es gibt keine sichtbare Ausgabe, was erwartet wird.

Überprüfen wir die Datei capture.txt, um zu sehen, was sich darin befindet:

cat capture.txt

Sowohl stdout als auch stderr wurden in eine einzelne Zieldatei umgeleitet.

Um die Ausgabe eines Streams umzuleiten und sie stumm zu verwerfen, leiten Sie die Ausgabe nach /dev/null um.

Erkennung von Umleitungen in Skripten

Es wurde besprochen, wie ein Befehl erkennen kann, ob ein Stream umgeleitet wird, und sein Verhalten entsprechend anpassen kann. Kann dies auch in eigenen Skripten erreicht werden? Ja, und es ist eine einfache Technik.

Geben Sie den folgenden Text in einen Editor ein und speichern Sie ihn als input.sh:

#!/bin/bash

if [ -t 0 ]; then

  echo stdin kommt von der Tastatur
 
else

  echo stdin kommt von einer Pipe oder einer Datei
 
fi

Machen Sie es mit folgendem Befehl ausführbar:

chmod +x input.sh

Der wichtige Teil ist der Test innerhalb der eckigen Klammern. Die Option -t (Terminal) gibt true (0) zurück, wenn die Datei mit dem Dateideskriptor verknüpft ist und im Terminalfenster endet. Wir haben den Dateideskriptor 0 als Argument für den Test verwendet, der stdin repräsentiert.

Wenn stdin mit einem Terminalfenster verbunden ist, wird der Test wahr sein. Wenn stdin mit einer Datei oder einer Pipe verbunden ist, schlägt der Test fehl.

Jede Textdatei kann als Eingabe für das Skript verwendet werden. Hier verwenden wir eine Datei namens dummy.txt.

./input.sh < dummy.txt

Die Ausgabe zeigt, dass das Skript erkennt, dass die Eingabe nicht von einer Tastatur kommt, sondern von einer Datei. Das Skript kann sein Verhalten entsprechend anpassen.

Das war eine Datei-Umleitung, testen wir es mit einer Pipe:

cat dummy.txt | ./input.sh

Das Skript erkennt, dass seine Eingabe über eine Pipe kommt. Konkret wird erkannt, dass der stdin-Stream nicht mit einem Terminalfenster verbunden ist.

Führen wir das Skript ohne Pipes oder Umleitungen aus:

./input.sh

Der stdin-Stream ist mit dem Terminalfenster verbunden, und das Skript meldet dies entsprechend.

Um das gleiche mit dem Ausgabestream zu überprüfen, benötigen wir ein neues Skript. Geben Sie Folgendes in einen Editor ein und speichern Sie es als output.sh:

#!/bin/bash

if [ -t 1 ]; then

echo stdout geht ins Terminalfenster
 
else

echo stdout wird umgeleitet oder per Pipe weitergeleitet
 
fi

Machen Sie es mit folgendem Befehl ausführbar:

chmod +x input.sh

Die einzige relevante Änderung an diesem Skript ist der Test in den eckigen Klammern. Wir verwenden die Ziffer 1, die den Dateideskriptor für stdout darstellt.

Testen wir es. Wir leiten die Ausgabe über cat um:

./output.sh | cat

Das Skript erkennt, dass seine Ausgabe nicht direkt in ein Terminalfenster geht.

Das Skript kann auch durch Umleitung der Ausgabe in eine Datei getestet werden:

./output.sh > capture.txt

Es gibt keine Ausgabe im Terminalfenster, es kehrt still zur Eingabeaufforderung zurück.

Wir können den Inhalt der Datei capture.txt mit dem folgenden Befehl anzeigen:

cat capture.txt

Auch hier erkennt der einfache Test im Skript, dass der stdout-Stream nicht direkt an ein Terminalfenster gesendet wird.

Wenn das Skript ohne Pipes oder Umleitungen ausgeführt wird, sollte es erkennen, dass stdout direkt an das Terminal gesendet wird:

./output.sh

Und genau das ist der Fall.

Bewusstsein für Datenströme

Das Wissen darüber, ob Skripte mit dem Terminalfenster verbunden, per Pipe weitergeleitet oder umgeleitet werden, erlaubt es, ihr Verhalten entsprechend anzupassen.

Protokoll- und Diagnoseausgaben können je nachdem, ob sie auf dem Bildschirm oder in einer Datei ausgegeben werden, unterschiedlich detailliert sein. Fehlermeldungen können in einer separaten Datei als die normale Programmausgabe gespeichert werden.

Wie so oft, bringt mehr Wissen mehr Möglichkeiten.