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
-
fork
de um novo processo.
- Abra
file.txt
e armazene seu ponteiro no descritor de arquivo 1 (STDOUT).
- Aponte STDERR (descritor de arquivo 2) para o que fd 1 apontar para agora (que novamente é o
file.txt
do curso).
-
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.