Por que o comportamento de `command 1> file.txt 2> file.txt` é diferente de` command 1> file.txt 2> & 1`?

20

Quando você deseja redirecionar stdout e stderr para o mesmo arquivo, pode fazê-lo usando command 1>file.txt 2>&1, ou command &>file.txt. Mas por que o comportamento é command 1>file.txt 2>file.txtdiferente dos dois comandos acima?

A seguir, é um comando de verificação.

$ cat redirect.sh
#!/bin/bash

{ echo -e "output\noutput" && echo -e "error" 1>&2; } 1>file.txt 2>&1
{ echo -e "output\noutput" && echo -e "error" 1>&2; } 1>file1.txt 2>file1.txt
{ echo -e "error" 1>&2 && echo -e "output\noutput"; } 1>file2.txt 2>file2.txt
{ echo -e "output" && echo -e "error\nerror" 1>&2; } 1>file3.txt 2>file3.txt
{ echo -e "error\nerror" 1>&2 && echo -e "output"; } 1>file4.txt 2>file4.txt

$ ./redirect.sh

$ echo "---file.txt---"; cat file.txt;\
echo "---file1.txt---"; cat file1.txt; \
echo "---file2.txt---"; cat file2.txt; \
echo "---file3.txt---"; cat file3.txt; \
echo "---file4.txt----"; cat file4.txt;
 ---file.txt---
output
output
error
---file1.txt---
error

output
---file2.txt---
output
output
---file3.txt---
error
error
---file4.txt----
output
rror

Tanto quanto os resultados são vistos, parece que a segunda string de eco substitui a primeira quando você executa command 1>file.txt 2>file.txt, mas não sei por que. (Existe uma referência em algum lugar?)

fhiyo
fonte

Respostas:

43

Você precisa saber duas coisas:

  • Um descritor de arquivo aberto conhecido no lado do modo de aplicativo de um processo faz referência a um objeto interno do kernel conhecido como descrição do arquivo , que é uma instância de um arquivo aberto. Pode haver várias descrições de arquivo por arquivo e vários descritores de arquivo compartilhando uma descrição de arquivo.
  • A posição atual do arquivo é um atributo de uma descrição do arquivo . Portanto, se vários descritores de arquivo forem mapeados para uma única descrição de arquivo, todos compartilharão a mesma posição atual do arquivo, e uma alteração na posição do arquivo promulgada usando um desses descritores de arquivos afeta todos os outros descritores de arquivos.

    Tais mudanças são promulgadas por processos chamando os read()/ readv(), write()/ writev(), lseek()chamadas, e do sistema afins. O echocomando chama write()/ é writev()claro.

Então, o que acontece é o seguinte:

  • command 1>file.txt 2>&1cria apenas uma descrição do arquivo, porque o shell abre um arquivo apenas uma vez. As marcas shell tanto a saída padrão e descritores de arquivo de erro padrão mapear para essa única descrição de arquivo. Ele duplica a saída padrão para o erro padrão. Portanto, uma gravação via descritor de arquivo moverá a posição atual compartilhada do arquivo: cada gravação será posterior à gravação anterior, a descrição comum do arquivo. E como você pode ver, os resultados dos echocomandos não se substituem.
  • command 1>file.txt 2>file.txtcria duas descrições de arquivo, porque o shell abre o mesmo arquivo duas vezes, em resposta aos dois redirecionamentos explícitos. A saída padrão e os descritores de arquivo de erro padrão são mapeados para duas descrições de arquivo diferentes, que por sua vez são mapeadas para o mesmo arquivo único. As duas descrições de arquivo têm posições atuais totalmente independentes e cada gravação passa imediatamente para a gravação anterior na mesma descrição do arquivo. E como você pode ver, o resultado é que o que é escrito por um pode substituir o que é escrito por outro, de várias maneiras diferentes, de acordo com a ordem em que você executa as gravações.

Leitura adicional

JdeBP
fonte
11
Deve ser a descrição do arquivo aberto em vez da descrição do arquivo . É mais sobre o registro de como o arquivo foi aberto mais do que o próprio arquivo. Essa é a terminologia usada pela documentação POSIX, Linux, Solaris e GNU pelo menos.
Stéphane Chazelas
16

Usar >indica para substituir o arquivo. Como você tem stdout e stderr gravando no arquivo em duas operações diferentes, a última a gravar substituirá a primeira.

Você pode fazer:

command 1>>file.txt 2>>file.txt

ou

command &>file.txt Apenas bash v4 e acima.

>> diz para anexar o arquivo para que não substitua a saída das operações anteriores.

&> é apenas uma maneira mais fácil de escrever 2>&1

Jesse_b
fonte
2
por que ls 1>&0e ls 0>&0ainda mostra a saída de ls?
Yvain
Estou surpreso que usando >>obras. Por que isso não tem o problema de duas descrições de arquivo com compensações independentes? @JdeBP, você sabe? Eu pensei que abrir um arquivo no modo de acréscimo era equivalente a abrir no modo de gravação, procurar a posição final e, em seguida, impedir a busca adicional.
Jol
4
@ jlmg: arquivos em modo de acréscimo são procuráveis, mas cada gravação é prefixada com uma busca implícita até o final. Se essa busca implícita é atômica é menos claro para mim.
Kevin
11
Isso depende inteiramente da pergunta subsequente. Pessoas assim estão relacionadas indicam que a resposta está incompleta. E é improvável que as pessoas pesquisem as perguntas subsequentes sem também querer saber a resposta completa. Portanto, é bem provável que essas respostas tenham respostas que duplicam as informações e, portanto, sejam fechadas como duplicatas.
trlkly
11
@ Kevin, em sistemas de arquivos totalmente compatíveis com POSIX, a busca implícita O_APPEND é atômica. Dito isto, nem todo sistema de arquivos implementa a semântica relevante corretamente - por exemplo, o NFS (pelo menos v3 e anterior - não estou claro na v4) não tem o suporte relevante incorporado no protocolo de conexão, portanto o servidor possui não há como saber se um cliente abriu um arquivo com O_APPEND.
Charles Duffy