stdin
, stdout
e stderr
são streams anexados a descritores de arquivos 0, 1 e 2 respectivamente de um processo.
No prompt de um shell interativo em um terminal ou emulador de terminal, todos esses três descritores de arquivo se refeririam ao mesmo descrição do arquivo aberto que teria sido obtida abrindo um arquivo de dispositivo terminal ou pseudo-terminal (algo como /dev/pts/0
) no modo leitura + gravação.
Se a partir desse shell interativo você iniciar o script sem usar nenhum redirecionamento, o script herdará esses descritores de arquivos.
No Linux, /dev/stdin
, /dev/stdout
, /dev/stderr
são links simbólicos para /proc/self/fd/0
, /proc/self/fd/1
, /proc/self/fd/2
, respectivamente, eles próprios links simbólicos para o arquivo real que está aberto nesses descritores de arquivo. / p>
Eles não são stdin, stdout, stderr, são arquivos especiais que identificam quais arquivos stdin, stdout, stderr vão (note que é diferente em outros sistemas que não o Linux que possuem esses arquivos especiais).
ler algo de stdin significa ler o descritor de arquivo 0 (que apontará para algum lugar dentro do arquivo referenciado por /dev/stdin
).
Mas em $(</dev/stdin)
, o shell não está lendo stdin, ele abre um novo descritor de arquivo para leitura no mesmo arquivo que o aberto em stdin (portanto, lendo desde o início do arquivo, não onde stdin atualmente aponta para).
Exceto no caso especial de dispositivos de terminal abertos no modo de leitura + gravação, stdout e stderr geralmente não estão abertos para leitura. Eles são feitos para serem fluxos que você escreve para . Portanto, a leitura do descritor de arquivo 1 geralmente não funcionará. No Linux, abrir /dev/stdout
ou /dev/stderr
para leitura (como em $(</dev/stdout)
) funcionaria e permitiria que você lesse o arquivo para o qual o stdout acessa (e se stdout fosse um canal, ele seria lido do outro lado o pipe, e se fosse um socket, ele falharia, pois você não pode abrir um soquete).
Em nosso caso do script executado sem redirecionamento no prompt de um shell interativo em um terminal, todos os / dev / stdin, / dev / stdout e / dev / stderr serão o dispositivo de terminal / dev / pts / x arquivo.
A leitura desses arquivos especiais retorna o que é enviado pelo terminal (o que você digita no teclado). Escrever para eles enviará o texto para o terminal (para exibição).
echo $(</dev/stdin)
echo $(</dev/stderr)
será o mesmo. Para expandir $(</dev/stdin)
, o shell abrirá / dev / pts / 0 e lerá o que você digitar até que você pressione ^D
em uma linha vazia. Eles então passarão a expansão (o que você digitou despojado das novas linhas à direita e sujeito a split + glob) para echo
, que irá então mostrar no stdout (para exibição).
No entanto, em:
echo $(</dev/stdout)
em bash
( e bash
only ), é importante perceber que dentro de $(...)
, o stdout foi redirecionado. Agora é um tubo. No caso de bash
, um processo de shell filho está lendo o conteúdo do arquivo (aqui /dev/stdout
) e gravando-o no canal, enquanto o pai lê do outro lado para compor a expansão.
Nesse caso, quando esse processo bash filho abre /dev/stdout
, ele está abrindo a extremidade de leitura do canal. Nada virá disso, é uma situação de impasse.
Se você quisesse ler o arquivo apontado pelo script stdout, você contornaria com:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
Isso duplicaria o fd 1 no fd 3, então / dev / fd / 3 apontaria para o mesmo arquivo que / dev / stdout.
Com um script como:
#! /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 executado como:
echo bar > err
echo foo | myscript > out 2>> err
Você verá em out
depois:
content of file on stdin: foo
content of file on stdout: content of file on stdin: foo
content of file on stderr: bar
Se ao contrário de ler /dev/stdin
, /dev/stdout
, /dev/stderr
, você quisesse ler stdin, stdout e stderr (o que faria menos sentido), você faria:
#! /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 você iniciou o segundo script novamente como:
echo bar > err
echo foo | myscript > out 2>> err
Você veria em out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr:
e em err
:
bar
cat: -: Bad file descriptor
cat: -: Bad file descriptor
Para stdout e stderr, cat
falha porque os descritores de arquivos estavam abertos para somente , não lendo, a expansão de $(cat <&3)
e $(cat <&2)
está vazia.
Se você o chamou como:
echo out > out
echo err > err
echo foo | myscript 1<> out 2<> err
(onde <>
é aberto no modo de leitura + gravação sem truncamento), você verá em out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
e em err
:
err
Você notará que nada foi lido no stdout, porque o printf
anterior substituiu o conteúdo de out
por what I read from stdin: foo\n
e deixou a posição stdout dentro desse arquivo logo após. Se você tivesse preparado out
com algum texto maior, como:
echo 'This is longer than "what I read from stdin": foo' > out
Então você entraria em 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
Veja como o $(cat <&3)
leu o que sobrou após o primeiro printf
e isso também moveu a posição stdout além dele para que o próximo printf
mostre o que foi lido depois.