stdin
, stdout
, und stderr
sind Streams an die Dateideskriptoren 0, 1 bzw. 2 eines Prozesses angehängt.
An der Eingabeaufforderung einer interaktiven Shell in einem Terminal oder Terminalemulator würden alle diese 3 Dateideskriptoren auf dieselbe offene Dateibeschreibung verweisen, die durch Öffnen einer Terminal- oder Pseudo-Terminalgerätedatei erhalten worden wäre (etwas wie /dev/pts/0
) im Lese-/Schreibmodus.
Wenn Sie von dieser interaktiven Shell aus Ihr Skript starten, ohne eine Umleitung zu verwenden, erbt Ihr Skript diese Dateideskriptoren.
Unter Linux /dev/stdin
, /dev/stdout
, /dev/stderr
sind symbolische Links zu /proc/self/fd/0
, /proc/self/fd/1
, /proc/self/fd/2
jeweils selbst spezielle symbolische Links zu der eigentlichen Datei, die in diesen Dateideskriptoren geöffnet ist.
Sie sind nicht stdin, stdout, stderr, sondern spezielle Dateien, die angeben, zu welchen Dateien stdin, stdout, stderr gehören (beachten Sie, dass es in anderen Systemen als Linux, die diese speziellen Dateien haben, anders ist).
Etwas von stdin zu lesen bedeutet, von Dateideskriptor 0 zu lesen (der irgendwo innerhalb der Datei zeigt, auf die durch /dev/stdin
verwiesen wird ).
Aber in $(</dev/stdin)
, liest die Shell nicht von stdin, sie öffnet einen neuen Dateideskriptor zum Lesen in derselben Datei wie die, die in stdin geöffnet ist (es wird also vom Anfang der Datei gelesen, nicht dort, wo stdin derzeit zeigt).
Außer im Sonderfall von Endgeräten, die im Read+Write-Modus geöffnet sind, sind stdout und stderr normalerweise nicht zum Lesen geöffnet. Sie sind als Streams gedacht, in die Sie schreiben . Das Lesen aus dem Dateideskriptor 1 funktioniert also im Allgemeinen nicht. Öffnen Sie unter Linux /dev/stdout
oder /dev/stderr
zum Lesen (wie in $(</dev/stdout)
) würde funktionieren und Sie könnten aus der Datei lesen, wohin stdout geht (und wenn stdout eine Pipe wäre, würde das vom anderen Ende der Pipe lesen, und wenn es ein Socket wäre, würde es fehlschlagen, da Sie es nicht können öffnen eine Steckdose).
In unserem Fall, in dem das Skript ohne Umleitung an der Eingabeaufforderung einer interaktiven Shell in einem Terminal ausgeführt wird, sind alle /dev/stdin, /dev/stdout und /dev/stderr diese /dev/pts/x-Terminalgerätedatei.
Das Lesen aus diesen speziellen Dateien gibt zurück, was vom Terminal gesendet wird (was Sie auf der Tastatur eingeben). Wenn Sie an sie schreiben, wird der Text an das Terminal gesendet (zur Anzeige).
echo $(</dev/stdin)
echo $(</dev/stderr)
wird dasselbe sein. Um $(</dev/stdin)
zu erweitern , öffnet die Shell dieses /dev/pts/0 und liest, was Sie eingeben, bis Sie ^D
drücken auf einer leeren Zeile. Sie werden dann die Erweiterung (was Sie eingegeben haben, ohne die abschließenden Zeilenumbrüche und mit split+glob versehen) an echo
übergeben die es dann auf stdout (zur Anzeige) ausgibt.
Allerdings in:
echo $(</dev/stdout)
in bash
(und bash
nur), ist es wichtig, dies innerhalb von $(...)
zu erkennen , stdout wurde umgeleitet. Es ist jetzt ein Rohr. Im Fall von bash
liest ein untergeordneter Shell-Prozess den Inhalt der Datei (hier /dev/stdout
) und schreibt es in die Pipe, während der Elternteil vom anderen Ende liest, um die Erweiterung zu bilden.
In diesem Fall öffnet dieser untergeordnete Bash-Prozess /dev/stdout
, es öffnet tatsächlich das Leseende der Pfeife. Daraus wird nie etwas werden, es ist eine Sackgasse.
Wenn Sie aus der Datei lesen möchten, auf die die Skript-Stdout-Datei zeigt, würden Sie dies umgehen mit:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
Das würde fd 1 auf fd 3 duplizieren, also würde /dev/fd/3 auf dieselbe Datei wie /dev/stdout zeigen.
Mit einem Skript wie:
#! /bin/bash -
printf 'content of file on stdin: %s\n' "$(</dev/stdin)"
{ printf 'content of file on stdout: %s\n' "$(</dev/fd/3)"; } 3<&1
printf 'content of file on stderr: %s\n' "$(</dev/stderr)"
Bei Ausführung als:
echo bar > err
echo foo | myscript > out 2>> err
Sie würden in out
sehen danach:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
Im Gegensatz zum Lesen von /dev/stdin
, /dev/stdout
, /dev/stderr
, Sie wollten von stdin, stdout und stderr lesen (was noch weniger Sinn machen würde), würden Sie Folgendes tun:
#! /bin/sh -
printf 'what I read from stdin: %s\n' "$(cat)"
{ printf 'what I read from stdout: %s\n' "$(cat <&3)"; } 3<&1
printf 'what I read from stderr: %s\n' "$(cat <&2)"
Wenn Sie das zweite Skript erneut gestartet haben als:
echo bar > err
echo foo | myscript > out 2>> err
Sie würden in out
sehen :
what I read from stdin: foo
what I read from stdout:
what I read from stderr:
und in err
:
bar
cat: -: Bad file descriptor
cat: -: Bad file descriptor
Für stdout und stderr cat
schlägt fehl, weil die Dateideskriptoren zum Schreiben geöffnet waren nur, nicht lesend, die Erweiterung von $(cat <&3)
und $(cat <&2)
ist leer.
Wenn Sie es so genannt haben:
echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err
(wobei <>
öffnet sich im Lese-/Schreibmodus ohne Kürzung), würden Sie in out
sehen :
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
und in err
:
err
Sie werden feststellen, dass nichts von stdout gelesen wurde, weil der vorherige printf
hatte den Inhalt von out
überschrieben mit what I read from stdin: foo\n
und verließ die stdout-Position innerhalb dieser Datei gleich danach. Wenn Sie out
vorbereitet hatten mit etwas größerem Text, wie:
echo 'This is longer than "what I read from stdin": foo' > out
Dann kämen Sie auf out
:
what I read from stdin: foo
read from stdin": foo
what I read from stdout: read from stdin": foo
what I read from stderr: err
Sehen Sie, wie der $(cat <&3)
hat gelesen, was nach dem ersten printf
übrig geblieben ist und bewegte damit auch die stdout-Position daran vorbei damit der nächste printf
gibt aus, was danach gelesen wurde.
stdout
und stderr
sind Ausgänge, Sie können nicht von ihnen lesen, sondern nur darauf schreiben. Zum Beispiel:
echo "this is stdout" >/dev/stdout
echo "this is stderr" >/dev/stderr
Programme schreiben standardmäßig nach stdout, also ist der erste äquivalent zu
echo "this is stdout"
und Sie können stderr auf andere Weise umleiten, z. B.
echo "this is stderr" 1>&2