eco ou imprima / dev / stdin / dev / stdout / dev / stderr

13

Quero imprimir o valor de / dev / stdin, / dev / stdout e / dev / stderr.

Aqui está o meu script simples:

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

eu uso os seguintes tubos:

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

única $(</dev/stdin)parece funcionar, eu também encontrado em alguns outros perguntas pessoas usando: "${1-/dev/stdin}"tentou fazê-lo, sem sucesso.

Omar BISTAMI
fonte

Respostas:

29

stdin, stdout, E stderrsã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/stderrsão links simbólicos para /proc/self/fd/0, /proc/self/fd/1, /proc/self/fd/2respectivamente,-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/stdoutou /dev/stderrleitura (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 ^Duma 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 echoqual a saída será exibida em stdout (para exibição).

No entanto, em:

echo $(</dev/stdout)

no bash( e bashsomente ), é 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 outdepois:

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, catfalha 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 printftinha substituído o conteúdo outcom what I read from stdin: foo\ne deixou a posição stdout dentro desse arquivo logo após. Se você preparou outum 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 printfsaídas produzam o que foi lido depois.

Stéphane Chazelas
fonte
2

stdoute stderrsão saídas, você não as lê, apenas pode escrever nelas. Por exemplo:

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

programas gravam no stdout por padrão, portanto o primeiro é equivalente a

echo "this is stdout"

e você pode redirecionar o stderr de outras maneiras, como

echo "this is stderr" 1>&2
Michael Daffin
fonte
1
No Linux, echo xnão é o mesmo que echo x > /dev/stdoutse 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 normal echo x > /dev/stdout, truncará o arquivo e substituirá seu conteúdo por, em x\nvez de escrever, x\nna posição atual de stdout.
Stéphane Chazelas
@ Michael-Daffin> Obrigado, então se eu quiser ler stdout e stderr, só posso usar gato? (como são arquivos), por exemplo, quero mostrar ao usuário o último erro que ocorreu echo this is your error $(cat /dev/stderr)?
Omar BISTAMI
@OmarBISTAMI Você não pode ler stdoute stderreles são saídas.
Kusalananda
1

myscript.sh:

#!/bin/bash
filan -s

Então corra:

$ ./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

Você provavelmente precisará instalar filancom o sudo apt install socatprimeiro.

wisbucky
fonte