Em que ordem o shell executa comandos e redireciona o fluxo?

32

Eu estava tentando redirecionar ambos stdoute stderrpara um arquivo hoje, e me deparei com isso:

<command> > file.txt 2>&1

Aparentemente, isso redireciona stderrpara stdoutprimeiro e, em seguida, o resultado stdouté redirecionado para file.txt.

No entanto, por que não é o pedido <command> 2>&1 > file.txt? Alguém poderia naturalmente ler isso como (assumindo a execução da esquerda para a direita) o comando sendo executado primeiro, o stderrredirecionado para stdoute, em seguida, o resultante stdoutsendo gravado file.txt. Mas o acima só redireciona stderrpara a tela.

Como o shell interpreta os dois comandos?

Trem Heartnet
fonte
7
O TLCL diz isso "Primeiro redirecionamos a saída padrão para o arquivo e, em seguida, redirecionamos o descritor de arquivo 2 (erro padrão) para o descritor de arquivo um (saída padrão)" e "Observe que a ordem dos redirecionamentos é significativa. O redirecionamento do erro padrão sempre deve ocorrer após o redirecionamento da saída padrão ou não funciona "
Zanna
@ Zanna: Sim, minha pergunta veio precisamente da leitura no TLCL! :) Estou interessado em saber por que não funcionaria, ou seja, como o shell interpreta os comandos em geral.
Train Heartnet
Bem, na sua pergunta, você diz o contrário "Isso aparentemente redireciona o stderr para o stdout primeiro ..." - o que eu entendo do TLCL, é que o shell envia stdout para o arquivo e depois stderr para stdout (ou seja, para o arquivo). Minha interpretação é que, se você enviar stderr para stdout, ele será exibido no terminal, e o redirecionamento subsequente do stdout não incluirá stderr (ou seja, o redirecionamento do stderr para a tela é concluído antes que o redirecionamento do stdout ocorra?)
Zanna
7
Eu sei que isso é algo antiquado de se dizer, mas o seu shell vem com um manual que explica essas coisas - por exemplo, redirecionamento no bashmanual . A propósito, redirecionamentos não são comandos.
Reinier Post
O comando não pode ser executado antes dos redirecionamentos serem configurados: quando o execvsyscall -family é chamado para realmente entregar o subprocesso para o comando que está sendo iniciado, o shell fica fora do loop (não há mais código em execução nesse processo) ) e não tem como controlar o que acontece a partir desse momento; redirecionamentos, portanto, todos devem ser realizados antes da execução começa, enquanto o shell tem uma cópia de si mesmo em execução no processo que fork()ed para executar o seu comando no.
Charles Duffy

Respostas:

41

Quando você executa o <command> 2>&1 > file.txtstderr, ele é redirecionado 2>&1para onde o stdout está atualmente, seu terminal. Depois disso, stdout é redirecionado para o arquivo >, mas stderr não é redirecionado com ele, portanto permanece como saída do terminal.

Com o <command> > file.txt 2>&1stdout, ele é redirecionado primeiro para o arquivo e >, em seguida, 2>&1redireciona o stderr para onde o stdout está indo, que é o arquivo.

Pode parecer contra-intuitivo começar, mas quando você pensa nos redirecionamentos dessa maneira e lembre-se de que eles são processados ​​da esquerda para a direita, faz muito mais sentido.

Arronical
fonte
Faz sentido se você pensar em termos de descritores de arquivos e chamadas "dup / fdreopen" sendo executadas na ordem da esquerda para a direita
Mark K Cowan
20

Pode fazer sentido se você o rastrear.

No começo, stderr e stdout vão para a mesma coisa (geralmente o terminal, que eu chamo aqui pts):

fd/0 -> pts
fd/1 -> pts
fd/2 -> pts

Estou me referindo a stdin, stdout e stderr pelos números de descritores de arquivos aqui: eles são descritores de arquivos 0, 1 e 2, respectivamente.

Agora, no primeiro conjunto de redirecionamentos, temos > file.txte 2>&1.

Tão:

  1. > file.txt: fd/1agora vai parafile.txt . With >, 1é o descritor de arquivo implícito quando nada é especificado, portanto, é 1>file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
  2. 2>&1: fd/2agora vai para ondefd/1 queatualmente :

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> file.txt

Por outro lado, com 2>&1 > file.txta ordem sendo revertida:

  1. 2>&1: fd/2 agora vai para onde quer fd/1que vá, o que significa que nada muda:

    fd/0 -> pts
    fd/1 -> pts
    fd/2 -> pts
  2. > file.txt: fd/1 agora vai para file.txt:

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts

O ponto importante é que o redirecionamento não significa que o descritor de arquivo redirecionado seguirá todas as alterações futuras no descritor de arquivo de destino; só vai assumir a corrente estado .

muru
fonte
Obrigado, esta parece ser uma explicação mais natural! :) Você fez um pequeno erro de digitação na 2.segunda parte; fd/1 -> file.txte não fd/2 -> file.txt.
Train Heartnet
11

Eu acho que ajuda pensar que o shell configurará o redirecionamento à esquerda primeiro e concluirá antes de configurar o próximo redirecionamento.

A linha de comando do Linux, de William Shotts, diz

Primeiro, redirecionamos a saída padrão para o arquivo e, em seguida, redirecionamos o descritor de arquivo 2 (erro padrão) para o descritor de arquivo um (saída padrão)

isso faz sentido, mas então

Observe que a ordem dos redirecionamentos é significativa. O redirecionamento do erro padrão sempre deve ocorrer após o redirecionamento da saída padrão ou não funciona

mas, na verdade, podemos redirecionar stdout para stderr após redirecionar stderr para um arquivo com o mesmo efeito

$ uname -r 2>/dev/null 1>&2
$ 

Portanto, em command > file 2>&1, o shell envia stdout para um arquivo e, em seguida, envia stderr para stdout (que está sendo enviado para um arquivo). Enquanto no command 2>&1 > fileshell primeiro redireciona o stderr para o stdout (ou seja, exibe-o no terminal onde o stdout normalmente vai) e depois, redireciona o stdout para o arquivo. O TLCL é enganoso ao dizer que devemos redirecionar o stdout primeiro: já que podemos redirecionar o stderr para um arquivo primeiro e depois enviar o stdout para ele. O que não podemos fazer é redirecionar o stdout para o stderr ou vice-versa antes do redirecionamento para um arquivo. Outro exemplo

$ strace uname -r 1>&2 2> /dev/null 
4.8.0-30-generic

Podemos pensar que isso descartaria o stdout para o mesmo local que o stderr, mas não redireciona o stdout para o stderr (a tela) primeiro e depois redireciona o stderr, como quando tentamos o contrário ...

Espero que isso traga um pouco de luz ...

Zanna
fonte
Muito mais eloquente!
Arronical 13/12
Ah, agora eu entendo! Muito obrigado, @Zanna e @Arronical! Apenas começando minha jornada na linha de comando. :)
Train Heartnet
@TrainHeartnet é um prazer! Espero que você goste tanto quanto eu: D
Zanna
@ Zanna: De fato, eu sou! : D
Train Heartnet
2
@TrainHeartnet não se preocupe, um mundo de frustração e alegria espera por você!
Arronical 13/12
10

Você já tem algumas respostas muito boas. Quero enfatizar, porém, que existem dois conceitos diferentes envolvidos aqui, cuja compreensão ajuda tremendamente:

Segundo plano: descritor de arquivo x tabela de arquivos

Seu descritor de arquivo é apenas um número 0 ... n, que é o índice na tabela de descritores de arquivos em seu processo. Por convenção, STDIN = 0, STDOUT = 1, STDERR = 2 (observe que os termos STDINetc. aqui são apenas símbolos / macros usados ​​pela convenção em algumas linguagens de programação e páginas de manual, não há um "objeto" real chamado STDIN; o objetivo desta discussão, STDIN é 0, etc.).

Essa tabela de descritor de arquivo em si não contém nenhuma informação sobre qual é o arquivo real. Em vez disso, ele contém um ponteiro para uma tabela de arquivos diferente; o último contém informações sobre um arquivo físico real (ou dispositivo de bloco, canal ou qualquer outra coisa que o Linux possa endereçar através do mecanismo de arquivo) e mais informações (isto é, para leitura ou gravação).

Portanto, quando você usa >ou <no seu shell, basta substituir o ponteiro do respectivo descritor de arquivo para apontar para outra coisa. A sintaxe 2>&1simplesmente aponta o descritor 2 para onde quer que seja 1. > file.txtsimplesmente abre file.txtpara escrever e permite que STDOUT (decsriptor de arquivo 1) aponte para isso.

Existem outras vantagens, por exemplo 2>(xxx) ( por exemplo : criar um novo processo em execuçãoxxx , criar um canal, conectar o descritor de arquivo 0 do novo processo à extremidade de leitura do canal e conectar o descritor de arquivo 2 do processo original à extremidade de gravação do tubo).

Essa também é a base para a "manipulação de arquivos mágicos" em outro software que não o seu shell. Por exemplo, você pode, em seu script Perl, duplicenciar o descritor de arquivo STDOUT para outro (temporário) e depois reabrir o STDOUT para um arquivo temporário recém-criado. A partir deste ponto, toda a saída STDOUT do seu próprio script Perl e todas as system()chamadas desse script terminarão nesse arquivo temporário. Quando terminar, você pode redirecionar dupseu STDOUT para o descritor temporário em que o salvou e pronto, tudo é como antes. Você pode até escrever nesse descritor temporário enquanto isso, enquanto sua saída STDOUT real for para o arquivo temporário, você ainda poderá realmente enviar coisas para o STDOUT real (geralmente, o usuário).

Responda

Para aplicar as informações básicas fornecidas acima à sua pergunta:

Em que ordem o shell executa comandos e redireciona o fluxo?

Esquerda para a direita.

<command> > file.txt 2>&1

  1. fork fora de um novo processo.
  2. Abra file.txte armazene seu ponteiro no descritor de arquivo 1 (STDOUT).
  3. Aponte STDERR (descritor de arquivo 2) para o que o fd 1 apontar agora (que novamente já está aberto, é file.txtclaro).
  4. exec a <command>

Aparentemente, isso redireciona o stderr para o stdout primeiro e, em seguida, o stdout resultante é redirecionado para o arquivo.txt.

Isso faria sentido se houvesse apenas uma tabela, mas, como explicado acima, há duas. Os descritores de arquivo não estão apontando recursivamente um para o outro; não faz sentido pensar em "redirecionar STDERR para STDOUT". O pensamento correto é "apontar STDERR para onde quer que STDOUT aponte". Se você alterar STDOUT mais tarde, o STDERR permanecerá onde está, não será magicamente acompanhado de outras alterações no STDOUT.

AnoE
fonte
Upvoting para a "mágica identificador de arquivo" mordeu - apesar de não responder diretamente a pergunta que eu aprendi algo novo hoje ...
Floris
3

O pedido é da esquerda para a direita. O manual do Bash já cobriu o que você pediu. Citação da REDIRECTIONseção do manual:

   Redirections  are  processed  in  the
   order they appear, from left to right.

e algumas linhas depois:

   Note that the order of redirections is signifi
   cant.  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  dupli
   cated from the standard output before the stan
   dard output was redirected to dirlist.

É importante observar que o redirecionamento é resolvido primeiro antes da execução de qualquer comando! Consulte https://askubuntu.com/a/728386/295286

Sergiy Kolodyazhnyy
fonte
3

É sempre da esquerda para a direita ... exceto quando

Assim como na matemática, fazemos da esquerda para a direita, exceto a multiplicação e a divisão antes da adição e subtração, exceto as operações entre parênteses (+ -) antes da multiplicação e divisão.

De acordo com o guia para iniciantes do Bash aqui ( Guia para iniciantes do Bash ), existem 8 ordens de hierarquia do que vem primeiro (antes da esquerda para a direita):

  1. Expansão de cinta "{}"
  2. Expansão til "~"
  3. Parâmetro do shell e expressão variável "$"
  4. Substituição de comando "$ (command)"
  5. Expressão aritmética "$ ((EXPRESSION))"
  6. Substituição de processo do que estamos falando aqui "<(LIST)" ou "> (LIST)"
  7. Divisão de palavras "'<espaço> <tab> <nova linha>'"
  8. Expansão do nome do arquivo "*", "?" Etc.

Portanto, é sempre da esquerda para a direita ... exceto quando ...

WinEunuuchs2Unix
fonte
1
Para ser claro: a substituição e o redirecionamento de processos são duas operações independentes. Você pode fazer um sem o outro. Tendo substituição processo não significa que o bash decide alterar a ordem em que se processa o redirecionamento
Muru