stdin
, stdout
e stderr
sono flussi associati ai descrittori di file 0, 1 e 2 rispettivamente di un processo.
Al prompt di una shell interattiva in un emulatore di terminale o terminale, tutti e 3 i descrittori di file farebbero riferimento alla stessa descrizione di file aperto che sarebbe stata ottenuta aprendo un file di dispositivo terminale o pseudo-terminale (qualcosa di simile /dev/pts/0
) in lettura + scrittura modalità.
Se da quella shell interattiva, avvii lo script senza utilizzare alcun reindirizzamento, lo script erediterà quei descrittori di file.
Su Linux, /dev/stdin
, /dev/stdout
, /dev/stderr
sono collegamenti simbolici a /proc/self/fd/0
, /proc/self/fd/1
, /proc/self/fd/2
rispettivamente stessi link simbolici speciali al file effettivo che è aperto su quei descrittori di file.
Non sono stdin, stdout, stderr, sono file speciali che identificano i file a cui vanno i file stdin, stdout, stderr (notare che è diverso in altri sistemi rispetto a Linux che hanno quei file speciali).
leggere qualcosa da stdin significa leggere dal descrittore di file 0 (che punterà da qualche parte all'interno del file a cui fa riferimento /dev/stdin
).
Ma in $(</dev/stdin)
, la shell non legge da stdin, apre un nuovo descrittore di file per la lettura sullo stesso file di quello aperto su stdin (quindi leggendo dall'inizio del file, non dove stdin attualmente punta).
Tranne nel caso speciale di dispositivi terminali aperti in modalità lettura + scrittura, stdout e stderr non sono normalmente aperti per la lettura. Sono pensati per essere flussi a cui scrivi . Quindi la lettura dal descrittore di file 1 generalmente non funzionerà. Su Linux, l'apertura /dev/stdout
o la /dev/stderr
lettura (come in $(</dev/stdout)
) funzionerebbe e ti permetterebbe di leggere dal file dove stdout va (e se stdout era una pipe, che leggerebbe dall'altra estremità della pipe, e se fosse un socket , fallirebbe poiché non è possibile aprire un socket).
Nel nostro caso lo script viene eseguito senza reindirizzamento al prompt di una shell interattiva in un terminale, tutti i file / dev / stdin, / dev / stdout e / dev / stderr saranno quel file / dev / pts / x terminal device.
La lettura da quei file speciali restituisce ciò che viene inviato dal terminale (ciò che si digita sulla tastiera). Scrivere su di essi invierà il testo al terminale (per la visualizzazione).
echo $(</dev/stdin)
echo $(</dev/stderr)
sarà lo stesso. Per espandere $(</dev/stdin)
, la shell aprirà quel / dev / pts / 0 e leggerà ciò che si digita fino a quando non si preme ^D
su una riga vuota. Passeranno quindi l'espansione (ciò che hai digitato spogliato delle nuove righe finali e soggetto a split + glob) a echo
cui poi lo emetterà su stdout (per la visualizzazione).
Tuttavia in:
echo $(</dev/stdout)
in bash
( e bash
solo ), è importante rendersi conto che dentro $(...)
, stdout è stato reindirizzato. Ora è una pipa. Nel caso di bash
, un processo di shell figlio sta leggendo il contenuto del file (qui /dev/stdout
) e lo sta scrivendo nella pipe, mentre il genitore legge dall'altra estremità per compensare l'espansione.
In questo caso, quando si apre quel processo bash figlio /dev/stdout
, si sta effettivamente aprendo l'estremità di lettura della pipe. Nulla verrà mai da quello, è una situazione di stallo.
Se volessi leggere dal file indicato dallo script stdout, dovresti aggirarlo con:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
Ciò duplicherebbe fd 1 su fd 3, quindi / dev / fd / 3 punta allo stesso file di / dev / stdout.
Con uno script come:
#! /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)"
Quando eseguito come:
echo bar > err
echo foo | myscript > out 2>> err
Vedresti in out
seguito:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
Se al contrario di lettura da /dev/stdin
, /dev/stdout
, /dev/stderr
, si voleva leggere da stdin, stdout e stderr (che renderebbe ancora meno senso), faresti:
#! /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)"
Se hai avviato di nuovo quel secondo script come:
echo bar > err
echo foo | myscript > out 2>> err
Vedresti in out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr:
e in err
:
bar
cat: -: Bad file descriptor
cat: -: Bad file descriptor
Per stdout e stderr, cat
fallisce perché i descrittori di file erano aperti solo per la scrittura , non per la lettura, l'espansione di $(cat <&3)
ed $(cat <&2)
è vuota.
Se lo hai chiamato come:
echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err
(dove si <>
apre in modalità lettura + scrittura senza troncamento), vedresti in out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
e in err
:
err
Noterai che nulla è stato letto da stdout, perché il precedente printf
aveva sovrascritto il contenuto di out
with what I read from stdin: foo\n
ed aveva lasciato la posizione stdout all'interno di quel file subito dopo. Se avessi innescato out
un testo più grande, come:
echo 'This is longer than "what I read from stdin": foo' > out
Quindi entrerai 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
Guarda come $(cat <&3)
ha letto ciò che era rimasto dopo il primo printf
e così facendo ha anche spostato la posizione stdout oltre in modo che il successivo printf
emetta ciò che è stato letto dopo.
echo x
non è lo stessoecho x > /dev/stdout
se stdout non va in una pipe o in alcuni dispositivi a caratteri come un dispositivo tty. Ad esempio, se stdout passa a un file normaleecho x > /dev/stdout
, troncerebbe il file e lo rimpiazzerebbex\n
invece di scriverex\n
nella posizione stdout corrente.