Appending funciona sem redirecionar a saída padrão e erro

4

Eu tenho um arquivo test.txt e não tenho nenhum arquivo chamado test . Quando eu tentei

ls test test.txt > new 2>new

Eu esperava que o new fosse sobrescrito, pois >> não é usado. Mas no arquivo de saída eu tenho os dois conteúdos anexados. Por que isso acontece?

    
por user3539 23.01.2015 / 16:44

2 respostas

5

O TL; DR bash abre e trunca todos os arquivos envolvidos antes que algo seja gravado neles. stdout e stderr são enviados para new porque bash já truncou o arquivo (duas vezes) quando ls começa a imprimir.

É assim que bash prepara / manipula o redirecionamento de E / S. Quando você solicita que um comando seja redirecionado ( > ) para um arquivo, bash basicamente abre esse arquivo, criando-o, se necessário. Se o arquivo já existir, será truncado. Isso é feito por meio da chamada do sistema open e de alguns sinalizadores, no seu caso:

open("new", O_WRONLY|O_CREAT|O_TRUNC, 0666)

O_CREAT cria o arquivo, se ele não existir, enquanto O_TRUNC o truncará quando o fizer. Esta chamada de sistema open faz parte da inicialização do bash para redirecionamento, o que significa que quando você usa várias operações de redirecionamento, como em ...

$ ls test test.txt > new 2>new

... bash começa abrindo todos os arquivos em questão. Portanto, antes de executar ls , ele abre new duas vezes, com os mesmos sinalizadores:

open("new", O_WRONLY|O_CREAT|O_TRUNC, 0666)
open("new", O_WRONLY|O_CREAT|O_TRUNC, 0666)

Isso significa que, basicamente, ao executar seu comando, bash faz o seguinte (nessa ordem):

  1. Abra new como saída padrão, crie / trunque o arquivo quando necessário.
  2. Abra new como erro padrão, crie / trunque o arquivo quando necessário.
  3. Executar ls : isso gravará o conteúdo em new .

Como você pode ver, bash trunca todos os arquivos envolvidos antes iniciando ls . Isso significa que ao executar algo com ... >new 2>new , new é basicamente truncado "duas vezes" e então , as saídas são redirecionadas para ele. O comportamento que você espera exigiria que bash capturasse stdout e stderr de ls independentemente, e os abrisse um após o outro, antes de escrever. Basicamente:

  1. Iniciar ls .
  2. Quando algo vem em stdout , abra new , trunque e escreva para ele.
  3. Quando algo aparecer em stderr , abra new novamente, truncá-lo e escreva para ele.

No entanto, as mensagens podem sair interweaven: o programa redirecionado pode muito bem escrever algo para stdout , então outra coisa para stderr , e então voltar para stdout ... Seria horrível gerenciar todos que (e isso pode levar a comportamentos indesejáveis (indefinidos?) ...) .

    
por 23.01.2015 / 17:15
2

Você não recebe os dois conteúdos anexados. Você receberá uma saída estranha:

$ ls testasdasd qtsingleapp-homecu-bcbf-3e8 >new 2>new
$ cat new
qtsingleapp-homecu-bcbf-3e8
: No such file or directory

Se você tiver os dois conteúdos, deverá ver:

$ ls testasdasd qtsingleapp-homecu-bcbf-3e8 >new 2>&1
$ cat new
ls: cannot access testasdasd: No such file or directory
qtsingleapp-homecu-bcbf-3e8

Então, vamos fazer um strace para ver o que aconteceu:

$ strace -f -e trace=open,close,write,fcntl,dup2 sh -c 'ls testasdsad qtsingleapp-homecu-bcbf-3e8 > new 2>new'
open("/etc/ld.so.cache", O_RDONLY)      = 3
close(3)                                = 0
open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY) = 3
close(3)                                = 0
open("new", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(1, F_DUPFD, 10)                   = 10
close(1)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0
open("new", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
fcntl(2, F_DUPFD, 10)                   = 11
close(2)                                = 0
fcntl(11, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 2)                              = 2
close(3)                                = 0
Process 7523 attached
....
[pid  7523] write(2, "ls: ", 4)         = 4
[pid  7523] write(2, "cannot access testasdsad", 24) = 24
[pid  7523] open("/usr/share/locale/en_US/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  7523] open("/usr/share/locale/en/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
[pid  7523] write(2, ": No such file or directory", 27) = 27
[pid  7523] write(2, "\n", 1)           = 1
[pid  7523] write(1, "qtsingleapp-homecu-bcbf-3e8\n", 28) = 28
[pid  7523] close(1)                    = 0
[pid  7523] close(2)                    = 0
Process 7523 detached
--- SIGCHLD (Child exited) @ 0 (0) ---
dup2(10, 1)                             = 1
close(10)                               = 0
dup2(11, 2)                             = 2
close(11)                               = 0

Veja as sequências de open , fcntl , dup2 que você pode ver:

  • Primeiro, o arquivo new é aberto e atribuído ao descritor de arquivo 3
  • Em seguida, o descritor de arquivo 1 é duplicado para o descritor de arquivo 10
  • Em seguida, o descritor de arquivo 3 (que é o arquivo new ) é duplicado para o descritor de arquivo 1 (que é 10 now).

Todas as coisas acima se referem a > new em seu comando. Em seguida, as mesmas sequências ocorreram, mas para o descritor de arquivo 2 , consulte 2>new em seu comando.

Depois disso, você tem dois descritores de arquivo 10 e 11 ambos apontam para o novo arquivo, e esses descritores se tornam stderr e stdout de ls . Quando ls em execução, ele gravou em stderr e stdout , que apontam para o mesmo arquivo new e parte da saída foi truncada, porque stdout substituiu stderr .

    
por 23.01.2015 / 17:36