stdin
, stdout
Und stderr
sind Ströme an Filedeskriptoren 0, 1 bzw. 2 eines Prozesses.
Bei der Aufforderung einer interaktiven Shell in einem Terminal oder Terminalemulator beziehen sich alle diese 3 Dateideskriptoren auf dieselbe offene Dateibeschreibung , die durch Öffnen einer Terminal- oder Pseudo-Terminal-Gerätedatei (so etwas wie /dev/pts/0
) in Lesen + Schreiben erhalten worden wäre Modus.
Wenn Sie Ihr Skript von dieser interaktiven Shell aus starten, ohne eine Umleitung zu verwenden, erbt Ihr Skript diese Dateideskriptoren.
Unter Linux /dev/stdin
, /dev/stdout
, /dev/stderr
sind symbolische Links auf /proc/self/fd/0
, /proc/self/fd/1
, /proc/self/fd/2
bzw. sich besondere symbolische Links auf die eigentliche Datei , die auf diesen Dateideskriptoren geöffnet ist.
Sie sind nicht stdin, stdout, stderr, sondern spezielle Dateien, die angeben, zu welchen Dateien stdin, stdout, stderr gehen (beachten Sie, dass dies in anderen Systemen als Linux mit diesen speziellen Dateien anders ist).
etwas von stdin zu lesen bedeutet, aus dem Dateideskriptor 0 zu lesen (der irgendwo in der Datei verweist, auf die verwiesen wird /dev/stdin
).
Da $(</dev/stdin)
die Shell jedoch nicht aus stdin liest, öffnet sie einen neuen Dateideskriptor zum Lesen derselben Datei wie die auf stdin geöffnete (also Lesen vom Anfang der Datei, nicht dort, wo stdin derzeit zeigt).
Außer im speziellen Fall von Endgeräten, die im Lese- / Schreibmodus geöffnet sind, sind stdout und stderr normalerweise nicht zum Lesen geöffnet. Sie sollen Streams sein, in die Sie schreiben . Das Lesen aus dem Dateideskriptor 1 funktioniert also im Allgemeinen nicht. Unter Linux würde das Öffnen /dev/stdout
oder /dev/stderr
Lesen (wie in $(</dev/stdout)
) funktionieren und Sie könnten aus der Datei lesen, in die stdout geht (und wenn stdout eine Pipe wäre, würde dies vom anderen Ende der Pipe lesen und wenn es ein Socket wäre würde es fehlschlagen, da Sie keinen Socket öffnen können ).
In unserem Fall, in dem das Skript ohne Umleitung an der Eingabeaufforderung einer interaktiven Shell in einem Terminal ausgeführt wird, sind alle Dateien / dev / stdin, / dev / stdout und / dev / stderr die / dev / pts / x-Endgerätedatei.
Das Lesen aus diesen speziellen Dateien gibt zurück, was vom Terminal gesendet wird (was Sie auf der Tastatur eingeben). Wenn Sie darauf schreiben, wird der Text an das Terminal gesendet (zur Anzeige).
echo $(</dev/stdin)
echo $(</dev/stderr)
wird dasselbe sein. Zum Erweitern $(</dev/stdin)
öffnet die Shell das / dev / pts / 0 und liest, was Sie eingeben, bis Sie ^D
auf eine leere Zeile drücken . Sie übergeben dann die Erweiterung (was Sie eingegeben haben, ohne die nachfolgenden Zeilenumbrüche und vorbehaltlich Split + Glob), an echo
die sie dann auf stdout (zur Anzeige) ausgegeben werden.
Jedoch in:
echo $(</dev/stdout)
in bash
( und bash
nur ) ist es wichtig zu erkennen, dass innen $(...)
stdout umgeleitet wurde. Es ist jetzt eine Pfeife. Im Fall von bash
liest ein untergeordneter Shell-Prozess den Inhalt der Datei (hier /dev/stdout
) und schreibt ihn in die Pipe, während das übergeordnete Element vom anderen Ende liest, um die Erweiterung zu erstellen .
In diesem Fall öffnet /dev/stdout
sich beim Öffnen dieses untergeordneten Bash-Prozesses tatsächlich das Leseende der Pipe. Daraus wird nie etwas werden, es ist eine Sackgasse.
Wenn Sie aus der Datei lesen möchten, auf die in den Skripten stdout verwiesen wird, können Sie Folgendes umgehen:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
Das würde die fd 1 auf die fd 3 duplizieren, also würde / dev / fd / 3 auf dieselbe Datei wie / dev / stdout verweisen.
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 out
später sehen:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
Wenn im Gegensatz zu Lesen aus /dev/stdin
, /dev/stdout
, /dev/stderr
, Sie von stdin, stdout und stderr (was machen würde noch weniger Sinn) lesen wollten, dann würden Sie 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 sehen in out
:
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 nur zum Schreiben geöffnet waren , nicht zum Lesen, die Erweiterung von $(cat <&3)
und $(cat <&2)
leer ist.
Wenn Sie es genannt haben als:
echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err
(wo <>
im Lese- / Schreibmodus ohne Kürzung geöffnet wird), sehen Sie in out
:
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, da der vorherige printf
den Inhalt von out
mit überschrieben what I read from stdin: foo\n
und die stdout-Position in dieser Datei unmittelbar danach belassen hat. Wenn Sie out
mit einem größeren Text grundiert hatten , wie:
echo 'This is longer than "what I read from stdin": foo' > out
Dann würden Sie einsteigen 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)
gelesen hat, was nach dem ersten übrig war, printf
und verschieben Sie dabei auch die Standardposition daran vorbei, so dass der nächste printf
ausgibt, was nach dem ersten gelesen wurde.
echo x
ist dies nicht dasselbe, alsecho x > /dev/stdout
ob stdout nicht zu einer Pipe oder einigen Zeichengeräten wie einem tty-Gerät geht. Wenn stdout beispielsweise zu einer regulären Datei wechseltecho x > /dev/stdout
, wird die Datei abgeschnitten und ihr Inhalt durch ersetzt,x\n
anstattx\n
an der aktuellen stdout-Position zu schreiben .