stdin
, stdout
, E stderr
são correntes ligados ao descritores de arquivo 0, 1 e 2, respectivamente de um processo.
No prompt de um shell interativo em um terminal ou emulador de terminal, todos esses 3 descritores de arquivo se referiam à mesma descrição de arquivo aberto que seria obtida pela abertura de um arquivo de dispositivo terminal ou pseudo-terminal (algo como /dev/pts/0
) em leitura + gravação modo.
Se a partir desse shell interativo, você iniciar o script sem usar nenhum redirecionamento, o script herdará esses descritores de arquivo.
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,-se links simbólicos especiais para o arquivo real que está aberta sobre os descritores de arquivos.
Eles não são stdin, stdout, stderr, são arquivos especiais que identificam para quais arquivos stdin, stdout, stderr vão (observe que é diferente em outros sistemas que o Linux que possuem esses arquivos especiais).
ler algo do stdin significa ler do descritor de arquivo 0 (que apontará para algum lugar do arquivo referenciado por /dev/stdin
).
Mas $(</dev/stdin)
, em , o shell não está lendo do stdin, ele abre um novo descritor de arquivo para leitura no mesmo arquivo que o aberto no stdin (portanto, lendo desde o início do arquivo, não para onde o stdin aponta atualmente).
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 devem ser fluxos nos quais você escreve . Portanto, a leitura do descritor de arquivo 1 geralmente não funcionará. No Linux, a abertura /dev/stdout
ou /dev/stderr
leitura (como em $(</dev/stdout)
) funcionaria e permitiria a leitura do arquivo para o qual o stdout vai (e se o stdout for um canal, ele lerá da outra extremidade do canal e se for um soquete , falharia porque você não pode abrir um soquete).
No nosso caso, o 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 arquivo de dispositivo do terminal / dev / pts / x.
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é pressionar ^D
uma linha vazia. Eles passarão a expansão (o que você digitou retirou das novas linhas à direita e está sujeito a split + glob), para a echo
qual a saída será exibida em stdout (para exibição).
No entanto, em:
echo $(</dev/stdout)
no bash
( e bash
somente ), é importante perceber que o $(...)
stdout foi redirecionado por dentro. Agora é um cano. No caso de bash
, um processo de shell filho está lendo o conteúdo do arquivo (aqui /dev/stdout
) e gravando no canal, enquanto o pai lê da outra extremidade para compensar a expansão.
Nesse caso, quando esse processo de bash filho é aberto /dev/stdout
, ele está realmente abrindo a extremidade de leitura do pipe. Nada nunca resultará disso, é uma situação de impasse.
Se você quiser ler o arquivo apontado pelos scripts stdout, contorná-lo-á com:
{ echo content of file on stdout: "$(</dev/fd/3)"; } 3<&1
Isso duplicaria o fd 1 no fd 3, portanto / 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ê veria 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 em oposição à leitura de /dev/stdin
, /dev/stdout
, /dev/stderr
, você queria ler de stdin, stdout e stderr (o que tornaria ainda 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 esse 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 arquivo foram abertos apenas para gravação , não para leitura, 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ê vê em out
:
what I read from stdin: foo
what I read from stdout:
what I read from stderr: err
e em err
:
err
Você vai notar que nada foi lida de stdout, porque o anterior printf
tinha substituído o conteúdo out
com what I read from stdin: foo\n
e deixou a posição stdout dentro desse arquivo logo após. Se você preparou out
um texto maior, como:
echo 'This is longer than "what I read from stdin": foo' > out
Então você entra 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 $(cat <&3)
ele leu o que restou após o primeiro printf
e, ao fazê-lo, também moveu a posição stdout além dela, para que as próximas printf
saídas produzam o que foi lido depois.
echo x
não é o mesmo queecho x > /dev/stdout
se o stdout não vá para um pipe ou para alguns dispositivos de caracteres como um dispositivo tty. Por exemplo, se stdout for para um arquivo normalecho x > /dev/stdout
, truncará o arquivo e substituirá seu conteúdo por, emx\n
vez de escrever,x\n
na posição atual de stdout.