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.