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

31

Eu estava tentando redirecionar os arquivos stdout e stderr para um arquivo hoje e me deparei com isso:

<command> > file.txt 2>&1

Isso aparentemente redireciona stderr para stdout primeiro e, em seguida, o stdout resultante é redirecionado para file.txt .

No entanto, por que o pedido não é o <command> 2>&1 > file.txt ? Um naturalmente leria isso como (assumindo a execução da esquerda para a direita) o comando sendo executado primeiro, sendo o stderr redirecionado para stdout e então, o stdout resultante sendo gravado em file.txt . Mas o acima apenas redireciona stderr para a tela.

Como o shell interpreta os dois comandos?

    
por Train Heartnet 13.12.2016 / 09:20

6 respostas

41

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

Com <command> > file.txt 2>&1 stdout é primeiro redirecionado para o arquivo por > , então 2>&1 redireciona stderr para onde o stdout está indo, que é o arquivo.

Pode parecer contraintuitivo, mas quando você pensa nos redirecionamentos dessa maneira e lembra que eles são processados da esquerda para a direita, faz muito mais sentido.

    
por Arronical 13.12.2016 / 10:43
20

Pode fazer sentido se você 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 por seus números descritor de arquivo aqui: eles são descritores de arquivo 0, 1 e 2 respectivamente.

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

Então:

  1. > file.txt : fd/1 agora vai para file.txt . Com > , 1 é o descritor de arquivo implícito quando nada é especificado, então isso é 1>file.txt :

    fd/0 -> pts
    fd/1 -> file.txt
    fd/2 -> pts
    
  2. 2>&1 : fd/2 agora vai para onde quer que fd/1 atualmente :

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

Por outro lado, com 2>&1 > file.txt , a ordem está sendo revertida:

  1. 2>&1 : fd/2 agora vai para onde quer que fd/1 esteja, 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 mudanças futuras no descritor de arquivo de destino; só vai assumir o estado atual .

    
por muru 13.12.2016 / 15:25
11

Acho que é bom pensar que o shell irá configurar o redirecionamento à esquerda primeiro, e completá-lo antes de configurar o próximo redirecionamento.

A linha de comando do Linux por William Shotts diz

First we redirect standard output to the file, and then we redirect file descriptor 2 (standard error) to file descriptor one (standard output)

isso faz sentido, mas

Notice that the order of the redirections is significant. The redirection of standard error must always occur after redirecting standard output or it doesn't work

mas, na verdade, podemos redirecionar o stdout para o stderr depois de redirecionar o 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 envia stderr para stdout (que está sendo enviado para um arquivo). Considerando que, em command 2>&1 > file , o shell primeiro redireciona stderr para stdout (isto é, exibe-o no terminal para o qual o stdout normalmente passa) e, em seguida, redireciona o stdout para o arquivo. O TLCL é enganador ao dizer que devemos redirecionar o stdout primeiro: já que podemos redirecionar o stderr para um arquivo primeiro e então enviar stdout para ele. O que não podemos fazer, é redirecionar stdout para stderr ou vice-versa antes redirecionando para um arquivo. Outro exemplo

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

Poderíamos pensar que isso descartaria o stdout no mesmo lugar que o stderr, mas isso não acontece, ele redireciona stdout para stderr (a tela) primeiro, e então apenas redireciona stderr, como quando tentamos ao contrário ...

Espero que isso traga um pouco de luz ...

    
por Zanna 13.12.2016 / 10:43
10

Você já recebeu algumas respostas muito boas. Deixe-me salientar que há dois conceitos diferentes envolvidos aqui, cuja compreensão ajuda tremendamente:

Histórico: Descritor de arquivos vs. tabela de arquivos

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

Essa tabela de descritores de arquivos em si não contém qualquer informação sobre o que é o arquivo real. Em vez disso, 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 ou pipe, ou qualquer outro que o Linux possa endereçar através do mecanismo de arquivo) e mais informações (ou seja, se é para leitura ou gravação).

Então, quando você usa > ou < no seu shell, simplesmente substitua o ponteiro do respectivo descritor de arquivo para apontar para outra coisa. A sintaxe 2>&1 simplesmente aponta o descritor 2 para qualquer ponto 1. > file.txt simplesmente abre file.txt para gravação e deixa STDOUT (decsriptor 1 do arquivo) apontar para isso.

Existem outras vantagens, por ex. 2>(xxx) (isto é: crie um novo processo executando xxx , crie um canal, conecte o descritor de arquivo 0 do novo processo à extremidade de leitura do canal e conecte o descritor de arquivo 2 do processo original ao fim da escrita do tubo).

Esta é também a base para a "magia do manipulador de arquivos" em outro software que não o seu shell. Por exemplo, você poderia, em seu script Perl, dup licate o descritor de arquivo STDOUT em outro (temporário), em seguida, reabrir STDOUT para um arquivo temporário recém-criado. Deste ponto em diante, 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 voltar adup do seu STDOUT para o descritor temporário em que você o salvou, e pronto, tudo é como antes. Você pode até escrever nesse descritor temporário enquanto isso, enquanto a sua saída STDOUT atual vai para o arquivo temporário, você ainda pode realmente enviar o material para o real STDOUT (comumente, o usuário).

Resposta

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

In what order does the shell execute commands and stream redirection?

<command> > file.txt 2>&1

  1. fork de um novo processo.
  2. Abra file.txt e armazene seu ponteiro no descritor de arquivo 1 (STDOUT).
  3. Aponte STDERR (descritor de arquivo 2) para o que fd 1 apontar para agora (que novamente é o file.txt do curso).
  4. exec o <command>

This apparently redirects stderr to stdout first, and then the resulting stdout is redirected to file.txt.

Isso faria sentido se houvesse apenas uma tabela, mas como explicado acima, existem dois. Os descritores de arquivo não estão apontando uns para os outros de forma recursiva, não faz sentido pensar em "redirecionar STDERR para STDOUT". O pensamento correto é "ponto STDERR para onde quer que aponte o STDOUT". Se você alterar o STDOUT mais tarde, o STDERR permanecerá onde está, não passa magicamente com outras alterações no STDOUT.

    
por AnoE 13.12.2016 / 23:34
3

A ordem é da esquerda para a direita. O manual do Bash já cobriu o que você pede. Cite a partir da seção REDIRECTION 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 antes de qualquer comando ser executado! Veja link

    
por Sergiy Kolodyazhnyy 13.12.2016 / 23:47
3

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

Assim como na Matemática, fazemos da esquerda para a direita, exceto multiplicação e divisão antes da adição e subtração, exceto que operações dentro de parênteses (+ -) seriam feitas antes da multiplicação e divisão.

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

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

Então, é sempre da esquerda para a direita ... exceto quando ...

    
por WinEunuuchs2Unix 17.12.2016 / 04:18