Isso é um erro de digitação na seção de redirecionamento do manual do Bash?

13
Note that the order of redirections is significant.  For example, the command

          ls > dirlist 2>&1

   directs both standard output and standard error to the file dirlist, 
   while the command

          ls 2>&1 > dirlist

   directs  only  the  standard  output  to  file  dirlist,  because the 
   standard error was duplicated from the standard output before the standard
   output was redirected to dirlist.

Agora, essa última parte é confusa para mim. Nesse caso, qualquer erro padrão seria impresso no terminal e qualquer STDOUT iria para o arquivo dirlist. Isso é o que aconteceria, mas não é assim que eu entendo o manual.

Parece que deveria dizer "porque o erro padrão foi duplicado da saída padrão APÓS a saída padrão foi redirecionada para dirlist". Se STDERR foi enviado para STDOUT antes que STDOUT fosse direcionado para um arquivo, o arquivo não conteria STDOUT AND STDERR?

Alguém pode esclarecer isso para mim? É apenas pouca compreensão de leitura da minha parte? O uso da palavra duplicação me parece um pouco estranho nesse contexto. Talvez isso esteja me jogando.

Gregg Leventhal
fonte
1
Um caso clássico de misturar operações que são "por valor" vs "por referência". Quando você duplica um descritor de arquivo, é uma operação por valor . Na programação, depois que a = 1; b = a; a = 2você espera a == 2 && b == 1ser verdade. O redirecionamento 2>&1é semelhante à b = aatribuição - é por valor, não por referência. 2>&1não se encaixa no descritor de arquivo 2 para o descritor de arquivo 1 por toda a eternidade - eles ainda são 2 descritores de arquivo distintos, que apontam para o mesmo arquivo.
Jw013 30/07/2013

Respostas:

23

A duplicação é realmente a parte importante aqui.

Vamos ver para onde os descritores de arquivo estão indo antes do redirecionamento. Normalmente, este é o terminal atual, por exemplo:

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

Agora, se ligarmos ls -lsem redirecionamento, as mensagens de saída e de erro vão para o meu terminal em /dev/pts/1.

Se primeiro redirecionarmos STDOUTpara um arquivo ( ls -l > dirlist), será assim:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Quando , em seguida, redirecionar STDERRa um duplicado de STDOUTdescritor de arquivo 's ( ls -l > dirlist 2>&1), STDERRvai para uma cópia de /home/bon/dirlist:

STDOUT ---> /home/bon/dirlist
STDERR ---> /home/bon/dirlist

Se primeiro redirecionarmos STDERRpara uma duplicata do STDOUTdescritor de arquivo ( ls -l 2>&1):

STDOUT ---> /dev/pts/1
STDERR ---> /dev/pts/1

e depois STDOUT para um arquivo ( ls -l 2>&1 > dirlist), teríamos o seguinte:

STDOUT ---> /home/bon/dirlist
STDERR ---> /dev/pts/1

Aqui, STDERR ainda está indo para o terminal.

Veja bem, a ordem na página de manual está correta.


Redirecionando testes

Agora, você pode testar isso sozinho. Usando ls -l /proc/$$/fd/, você vê onde STDOUT(com fd 1) e STDERR(com fd 2) estão indo para o processo atual:

$ ls -l /proc/$$/fd/
total 0
lrwx------ 1 bon bon 64 Jul 24 18:19 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 07:41 2 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 18:19 255 -> /dev/pts/1

Vamos criar um pequeno script de shell que mostre para onde os descritores de arquivos estão apontados. Dessa forma, sempre obtemos o estado ao chamar ls, incluindo qualquer redirecionamento do shell de chamada.

$ cat > lookfd.sh
#!/bin/sh
ls -l /proc/$$/fd/
^D
$ chmod +x lookfd.sh

(Com CtrlD, você envia um final de arquivo e, portanto, interrompe a catleitura do comando STDIN.)

Agora, chame esse script com combinações variadas de redirecionamento:

$ ./lookfd.sh 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:08 0 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:08 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:08 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh 2>&1 > foo.out
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:10 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:10 1 -> /home/bon/foo.out
lrwx------ 1 bon bon 64 Jul 24 19:10 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:10 255 -> /home/bon/lookfd.sh
$ ./lookfd.sh > foo.out 2>&1
$ cat foo.out 
total 0
lrwx------ 1 bon bon 64 Jul 24 19:11 0 -> /dev/pts/1
l-wx------ 1 bon bon 64 Jul 24 19:11 1 -> /home/bon/foo.out
l-wx------ 1 bon bon 64 Jul 24 19:11 2 -> /home/bon/foo.out
lr-x------ 1 bon bon 64 Jul 24 19:11 255 -> /home/bon/lookfd.sh

Você pode ver que os descritores de arquivo 1 (para STDOUT) e 2 (para STDERR) variam. Por diversão, você também pode redirecionar STDINe ver o resultado:

$ ./lookfd.sh < /dev/zero
total 0
lr-x------ 1 bon bon 64 Jul 24 19:18 0 -> /dev/zero
lrwx------ 1 bon bon 64 Jul 24 19:18 1 -> /dev/pts/1
lrwx------ 1 bon bon 64 Jul 24 19:18 2 -> /dev/pts/1
lr-x------ 1 bon bon 64 Jul 24 19:18 255 -> /home/bon/lookfd.sh

(Pergunta deixada ao leitor: Onde o descritor de arquivos 255 aponta? ;-))

Dubu
fonte
+1 - excelente resposta. Exemplos extremamente bem escritos e ótimos. Obrigado!!!
Slm
Entendo, acho que meu mal-entendido foi que o redirecionamento seria persistente para todos os comandos a seguir, para que qualquer STDERR do restante da linha fosse para STDOUT.
precisa
2

Não, o manual está certo.

Se no começo 1 apontar para o terminal e 2 também para o terminal, então:

command  2>&1   1>somewhere

o redirecionamento de avaliação acontecerá da esquerda para a direita.

Portanto, ele PRIMEIRO avaliará 2>&1e, portanto, PRIMEIRO copiará o que fd 1costumava apontar (isto é, o descritor de arquivo the terminal, geralmente / dev / tty) para fd 2.

Então, nesse ponto, o fd 2agora aponta para onde o fd 1costumava apontar ( the terminal)

E então ele avalia a 1>somewhereparte e, assim, copia o descritor de arquivo de somewhereem fd 1(então, nesse ponto, fd 1agora aponta para somewheree fd2 ainda aponta para the terminal)

Por isso, imprime 1 em "algum lugar" e 2 no terminal, pois 2 foi duplicado de 1 ANTES de 1 ter sido alterado.

A outra ordem:

command  1>somewhere 2>&1

primeiro redirecionará fd 1para somewheree depois copiará a mesma referência para fd 2; portanto, no final 2 também aponte parasomewhere . Mas eles não estão "ligados" a partir de agora. Cada um ainda pode ser redirecionado separadamente.

ex:

command  1>somewhere 2>&1
exec 2>/dev/null

No final desse, fd 1aponta para somewhere, e fd 2é direcionado para/dev/null

Os nomes comuns para fd 1são STDOUT (saída padrão) e o nome usual para fd 2é STDERR (erro padrão, pois é comumente usado para exibir erros sem interferir no STDOUT)

Olivier Dulac
fonte
@ Michael-mrozek: obrigado pela edição, mas insisto em dizer "copiar" em vez de "duplicar" como "duplicar" pode levar a acreditar que a partir de agora ambos são "a mesma coisa", o que não é verdade. ex:: cmd 1>somewhere 2>&1 ; exec 2>/dev/nullapós o exec, apenas 2 foram redirecionados para / dev / null (1 ainda está indo para "algum lugar"). Eu preciso de ajuda para encontrar uma maneira de dizer "o que 1 aponta para" em vez de "o fd 1", no entanto ... como isso também é confuso ...
Olivier Dulac
1
Não tenho certeza do que você quer dizer; foi você quem mudou de "copiar" para "duplicar". Tudo que fiz foi capitalizar e formato de coisas, eu não alterar uma palavra
Michael Mrozek
doh ... ^^ desculpe. E editei novamente para reformular para tornar mais preciso o que é copiado para o que ^^
Olivier Dulac
1

Acho que a parte confusa aqui é a má compreensão de que o redirecionamento de stderr para stdout realmente conecta os dois fluxos.

Uma ideia perfeitamente razoável, mas o que acontece quando você escreve 2>&1 é stderr dá uma olhada no que stdout está escrevendo e escreve no mesmo local. Portanto, se você pedir ao stdout para escrever em outro lugar, isso não afetará o destino do stderr que já foi movido.

Eu acho que é um pouco contra-intuitivo, mas é assim que funciona. Configure onde você deseja escrever primeiro e depois diga a todos "me copie". Espero que esclareça ...

jrichemont
fonte
0

DUPLICAÇÃO...

é importante, mas no sentido de que é fonte de muita confusão . É realmente muito simples. Esta resposta é apenas uma ilustração "radical".

A resposta aceita é boa, mas longa demais e enfatiza a "duplicação".

O Q termina sabiamente com:

O uso da palavra duplicação me parece um pouco estranho nesse contexto. Talvez isso esteja me jogando.

Uso a notação bash e defino as variáveis ​​"um" e "dois" como identificadores de arquivo "1" e "2". O operador de redirecionamento (de saída) >é uma atribuição =. &e $significa "valor" de.

Os exemplos do man bash (com o padrão "1" adicionado)

ls 1>dirlist 2>&1      # both to dirlist
ls 2>&1 1>dirlist      # 1 to dirlist, 2 stays on tty/screen 

tornar-se:

one=dirlist  two=$one

e

two=$one   one=dirlist

E mesmo isso não é automático para mim, e para alguns outros, eu acho. A primeira linha deixa você com $onee$two ambos contendo "dirlist". Claro.

A segunda linha começa com uma atribuição inútil. Ambos começam por definição com "TTY" (um pouco simbólico) como direção ; nenhum valor é alterado por essa atribuição e, com variáveis ​​como com identificadores de arquivo, nada é vinculado magicamente. A variável twonão é afetada pelo seguinte one=dirlist. Claro que não.

Sombody aqui (há 6 anos) sugeriu "apontar para" em vez de "copiar" ou "duplicar" e depois percebeu: isso também seria confuso.

Essa duplicação ou semântica de ponteiros nem é necessária. Talvez seja o e comercial que precise de mais atenção. O "valor de" operador / token / qualquer coisa.

Se - e somente se - você estiver procurando uma maneira de obter um número surpreendente de tarefas no seu console , uma mensagem "concluído" e um bônus como um arquivo chamado "2" serão exibidos :

ls 1>2& 2>/dev/null

Lê naturalmente como " copiar" / "duplicar" 1 a 2 e, em seguida, ambos juntos para nulo . Mas a ideia está errada, e também a sintaxe. (mas nenhum erro de sintaxe, é válido)

A maneira correta de planejar é redirecionar qualquer um dos dois para null e, em seguida, redirecionar o OTHER para o MESMO local:

ls 1>/dev/null 2>&1
# or 
ls 2>/dev/null 1>&2

(o "1" principal pode ser deixado de fora)

(OK, o acesso A não é muito longo, mas é muito uma lista - ou: visualização muito boa, explicação não muito boa)

rastafile
fonte