echo oder print / dev / stdin / dev / stdout / dev / stderr

13

Ich möchte den Wert von / dev / stdin, / dev / stdout und / dev / stderr drucken.

Hier ist mein einfaches Skript:

#!/bin/bash
echo your stdin is : $(</dev/stdin)
echo your stdout is : $(</dev/stdout)
echo your stderr is : $(</dev/stderr)

Ich benutze die folgenden Rohre:

[root@localhost home]# ls | ./myscript.sh
[root@localhost home]# testerr | ./myscript.sh

$(</dev/stdin)scheint nur zu funktionieren, ich habe auch bei einigen anderen Fragen Leute gefunden: "${1-/dev/stdin}"habe es ohne Erfolg versucht.

Omar BISTAMI
quelle

Antworten:

29

stdin, stdoutUnd stderrsind 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/stderrsind symbolische Links auf /proc/self/fd/0, /proc/self/fd/1, /proc/self/fd/2bzw. 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/stdoutoder /dev/stderrLesen (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 ^Dauf eine leere Zeile drücken . Sie übergeben dann die Erweiterung (was Sie eingegeben haben, ohne die nachfolgenden Zeilenumbrüche und vorbehaltlich Split + Glob), an echodie sie dann auf stdout (zur Anzeige) ausgegeben werden.

Jedoch in:

echo $(</dev/stdout)

in bash( und bashnur ) ist es wichtig zu erkennen, dass innen $(...)stdout umgeleitet wurde. Es ist jetzt eine Pfeife. Im Fall von bashliest 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/stdoutsich 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 outspä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 catschlä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 printfden Inhalt von outmit überschrieben what I read from stdin: foo\nund die stdout-Position in dieser Datei unmittelbar danach belassen hat. Wenn Sie outmit 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 printfausgibt, was nach dem ersten gelesen wurde.

Stéphane Chazelas
quelle
2

stdoutund stderrsind Ausgaben, Sie lesen nicht von ihnen, Sie können nur auf sie schreiben. Beispielsweise:

echo "this is stdout" >/dev/stdout
echo "this is stderr" >/dev/stderr

Programme schreiben standardmäßig in stdout, sodass das erste äquivalent zu ist

echo "this is stdout"

und Sie können stderr auf andere Arten umleiten, wie z

echo "this is stderr" 1>&2
Michael Daffin
quelle
1
Unter Linux echo xist dies nicht dasselbe, als echo x > /dev/stdoutob stdout nicht zu einer Pipe oder einigen Zeichengeräten wie einem tty-Gerät geht. Wenn stdout beispielsweise zu einer regulären Datei wechselt echo x > /dev/stdout, wird die Datei abgeschnitten und ihr Inhalt durch ersetzt, x\nanstatt x\nan der aktuellen stdout-Position zu schreiben .
Stéphane Chazelas
@ Michael-Daffin> Danke, wenn ich also stdout und stderr lesen möchte, kann ich nur cat verwenden? (da es sich um Dateien handelt), möchte ich dem Benutzer beispielsweise den letzten aufgetretenen Fehler anzeigen echo this is your error $(cat /dev/stderr)?
Omar BISTAMI
@OmarBISTAMI Sie können nicht von stdoutund lesen stderr, sie sind Ausgänge.
Kusalananda
1

myscript.sh:

#!/bin/bash
filan -s

Dann renne:

$ ./myscript.sh
    0 tty /dev/pts/1
    1 tty /dev/pts/1
    2 tty /dev/pts/1

$ ls | ./myscript.sh
    0 pipe 
    1 tty /dev/pts/1
    2 tty /dev/pts/1

$ ls | ./myscript.sh > out.txt
$ cat out.txt
    0 pipe 
    1 file /tmp/out.txt
    2 tty /dev/pts/1

Sie werden wahrscheinlich installieren müssen filanmit sudo apt install socatersten.

wisbucky
quelle