stdin
, stdout
e stderr
sono stream allegato ai descrittori di file 0, 1 e 2 rispettivamente di un processo.
Al prompt di una shell interattiva in un terminale o in un emulatore di terminale, tutti quei 3 descrittori di file farebbero riferimento alla stessa descrizione di file aperto che sarebbe stata ottenuta aprendo un terminale o un file di dispositivo pseudo-terminale (qualcosa come /dev/pts/0
) in modalità lettura+scrittura.
Se da quella shell interattiva avvii il tuo script senza utilizzare alcun reindirizzamento, il tuo 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, essi stessi collegamenti simbolici speciali al file effettivo che è aperto su quei descrittori di file.
Non sono stdin, stdout, stderr, sono file speciali che identificano a quali file vanno stdin, stdout, stderr (nota che è diverso in altri sistemi diversi da 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 sta leggendo 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 punta attualmente stdin).
Ad eccezione del caso speciale di dispositivi terminali aperti in modalità lettura+scrittura, stdout e stderr di solito non sono aperti per la lettura. Sono pensati per essere stream su cui scrivi . Quindi la lettura dal descrittore di file 1 generalmente non funzionerà. Su Linux, aprendo /dev/stdout
o /dev/stderr
per la lettura (come in $(</dev/stdout)
) funzionerebbe e ti permetterebbe di leggere dal file in cui va stdout (e se stdout fosse una pipe, leggerebbe dall'altra estremità della pipe, e se fosse un socket, fallirebbe perché non puoi apri una presa).
Nel nostro caso dello script eseguito senza reindirizzamento al prompt di una shell interattiva in un terminale, tutto /dev/stdin, /dev/stdout e /dev/stderr sarà quel /dev/pts/x file del dispositivo terminale.
La lettura da quei file speciali restituisce ciò che viene inviato dal terminale (ciò che digiti sulla tastiera). Scrivendovi invierà il testo al terminale (per la visualizzazione).
echo $(</dev/stdin)
echo $(</dev/stderr)
sarà lo stesso. Per espandere $(</dev/stdin)
, la shell aprirà /dev/pts/0 e leggerà ciò che digiti finché non premi ^D
su una riga vuota. Quindi passeranno l'espansione (ciò che hai digitato privato delle nuove righe finali e soggetto a split+glob) a echo
che quindi lo emetterà su stdout (per la visualizzazione).
Tuttavia in:
echo $(</dev/stdout)
in bash
(e bash
solo), è importante rendersene conto all'interno di $(...)
, stdout è stato reindirizzato. Adesso è una pipa. Nel caso di bash
, un processo shell figlio sta leggendo il contenuto del file (qui /dev/stdout
) e scrivendolo nella pipe, mentre il genitore legge dall'altra parte per creare l'espansione.
In questo caso, quando quel processo bash figlio apre /dev/stdout
, in realtà sta aprendo l'estremità di lettura del tubo. Non ne verrà mai fuori nulla, è una situazione di stallo.
Se volessi leggere dal file puntato dagli 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 punterebbe 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
successivamente:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
Se invece di leggere da /dev/stdin
, /dev/stdout
, /dev/stderr
, volevi leggere da stdin, stdout e stderr (il che avrebbe 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 per la scrittura solo, non leggendo, l'espansione di $(cat <&3)
e $(cat <&2)
è vuoto.
Se l'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 non è stato letto nulla da stdout, perché il precedente printf
aveva sovrascritto il contenuto di out
con what I read from stdin: foo\n
e ha lasciato la posizione stdout all'interno di quel file subito dopo. Se avessi innescato out
con del testo più grande, come:
echo 'This is longer than "what I read from stdin": foo' > out
Quindi entreresti in 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 funziona il $(cat <&3)
ha letto ciò che è rimasto dopo il primo printf
e così facendo hai anche spostato la posizione stdout oltre in modo che il prossimo printf
restituisce ciò che è stato letto dopo.
stdout
e stderr
sono output, non puoi leggere da loro puoi solo scriverci. Ad esempio:
echo "this is stdout" >/dev/stdout
echo "this is stderr" >/dev/stderr
i programmi scrivono su stdout per impostazione predefinita, quindi il primo è equivalente a
echo "this is stdout"
e puoi reindirizzare stderr in altri modi come
echo "this is stderr" 1>&2