Por que 'ls> ls.out' faz com que 'ls.out' seja incluído na lista de nomes?

26

Por que o $ ls > ls.out'ls.out' é incluído na lista de nomes de arquivos no diretório atual? Por que isso foi escolhido? Por que não o contrário?

Edward Torvalds
fonte
3
provavelmente porque ele primeiro cria um ls.out arquivo e, em seguida, escreve a saída para ele
Dimitri Podborski
1
Se você deseja evitar a inclusão, sempre pode armazenar o arquivo de saída em um diretório diferente. Por exemplo, você pode escolher o diretório principal (desde que não esteja na raiz do sistema de arquivos) com ls > ../ls.out
Elder Geek

Respostas:

36

Ao avaliar o comando, o >redirecionamento é resolvido primeiro: pelo tempo que lso arquivo de saída já foi criado.

Esse também é o motivo pelo qual a leitura e gravação no mesmo arquivo usando um >redirecionamento dentro do mesmo comando trunca o arquivo; no momento em que o comando é executado, o arquivo já está truncado:

$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$ 

Truques para evitar isso:

  • <<<"$(ls)" > ls.out (funciona para qualquer comando que precise ser executado antes que o redirecionamento seja resolvido)

    A substituição do comando é executada antes de o comando externo ser avaliado, e lsé executada antes da ls.outcriação:

    $ ls
    bar  foo
    $ <<<"$(ls)" > ls.out
    $ cat ls.out 
    bar
    foo
    
  • ls | sponge ls.out (funciona para qualquer comando que precise ser executado antes que o redirecionamento seja resolvido)

    spongegrava no arquivo somente quando o restante do canal terminar de executar, portanto, lsé executado antes da ls.outcriação ( spongeé fornecido com o moreutilspacote):

    $ ls
    bar  foo
    $ ls | sponge ls.out
    $ cat ls.out 
    bar
    foo
    
  • ls * > ls.out(funciona para ls > ls.outo caso específico)

    A expansão do nome do arquivo é realizada antes que o redirecionamento seja resolvido, portanto, lsserá executado em seus argumentos, que não conterão ls.out:

    $ ls
    bar  foo
    $ ls * > ls.out
    $ cat ls.out 
    bar
    foo
    $
    

Por que os redirecionamentos são resolvidos antes da execução do programa / script / o que quer que seja, não vejo um motivo específico pelo qual é obrigatório fazê-lo, mas vejo dois motivos pelos quais é melhor fazê-lo:

  • não redirecionar o STDIN antecipadamente tornaria o programa / script / o que quer que seja mantido até que o STDIN seja redirecionado;

  • não redirecionar o STDOUT de antemão deve necessariamente fazer com que o shell armazene a saída do programa / script / o que quer que seja até o redirecionamento do STDOUT;

Portanto, uma perda de tempo no primeiro caso e uma perda de tempo e memória no segundo caso.

É exatamente isso que me ocorre, não estou afirmando que essas são as razões reais; mas acho que, no fim das contas, se alguém tivesse uma escolha, eles iriam redirecionar antes pelas razões acima mencionadas.

kos
fonte
1
Observe que, durante o redirecionamento, o shell na verdade não toca nos dados (no redirecionamento de entrada ou no redirecionamento de saída). Ele apenas abre o arquivo e passa o descritor de arquivo para o programa.
Peter Green
11

De man bash:

REDIRECÇÃO

Antes de um comando ser executado, sua entrada e saída podem ser redirecionadas usando uma notação especial interpretada pelo shell. O redirecionamento permite que os identificadores de arquivo dos comandos sejam duplicados, abertos, fechados, feitos para se referir a arquivos diferentes e pode alterar os arquivos dos quais o comando lê e grava.

A primeira frase sugere que a saída é feita para ir para outro lugar que não seja o stdinredirecionamento, antes do comando ser executado. Portanto, para ser redirecionado para o arquivo, o arquivo deve primeiro ser criado pelo próprio shell.

Para evitar um arquivo, sugiro que você redirecione a saída para o pipe nomeado primeiro e depois para o arquivo. Observe o uso de &para retornar o controle do terminal ao usuário

DIR:/xieerqi
skolodya@ubuntu:$ mkfifo /tmp/namedPipe.fifo                                                                         

DIR:/xieerqi
skolodya@ubuntu:$ ls > /tmp/namedPipe.fifo &
[1] 14167

DIR:/xieerqi
skolodya@ubuntu:$ cat /tmp/namedPipe.fifo > ls.out

Mas por que?

Pense sobre isso - onde será a saída? Um programa tem funções como printf, sprintf, puts, que todos pelo movimento padrão para stdout, mas a sua saída pode ser ido para arquivo se o arquivo não existe, em primeiro lugar? É como a água. Você pode pegar um copo de água sem colocar o copo embaixo da torneira primeiro?

Sergiy Kolodyazhnyy
fonte
10

Não discordo das respostas atuais. O arquivo de saída deve ser aberto antes da execução do comando ou o comando não terá nenhum lugar para gravar sua saída.

Isso ocorre porque "tudo é um arquivo" em nosso mundo. A saída para a tela é SDOUT (também conhecido como descritor de arquivo 1). Para um aplicativo gravar no terminal, ele abre o fd1 e grava nele como um arquivo.

Quando você redireciona a saída de um aplicativo em um shell, está alterando o fd1 para que ele aponte para o arquivo. Quando você canaliza, altera o STDOUT de um aplicativo para se tornar o STDIN de outro (fd0).


Mas é legal dizer isso, mas você pode facilmente ver como isso funciona strace. É um material bastante pesado, mas este exemplo é bastante curto.

strace sh -c "ls > ls.out" 2> strace.out

Dentro strace.out, podemos ver os seguintes destaques:

open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3

Isso abre ls.outcomo fd3. Escreva apenas. Trunca (substitui) se existir, caso contrário, cria.

fcntl(1, F_DUPFD, 10)                   = 10
close(1)                                = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(3, 1)                              = 1
close(3)                                = 0

Isso é um pouco de malabarismo. Desviamos STDOUT (fd1) para fd10 e o fechamos. Isso ocorre porque não estamos produzindo nada para o STDOUT real com este comando. Ele termina duplicando a alça de gravação ls.oute fechando a alça original.

stat("/opt/wine-staging/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/home/oli/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls", 0x7ffc6bf028c0)    = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls", 0x7ffc6bf028c0)     = -1 ENOENT (No such file or directory)
stat("/sbin/ls", 0x7ffc6bf028c0)        = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0

É isso que procura pelo executável. Uma lição talvez para não ter um longo caminho;)

clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0961324a10) = 31933
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 31933
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31933, si_status=0, si_utime=0, si_stime=0} ---
rt_sigreturn()                          = 31933
dup2(10, 1)                             = 1
close(10)                               = 0

Em seguida, o comando é executado e o pai espera. Durante esta operação, qualquer STDOUT realmente será mapeado para o identificador de arquivo aberto ls.out. Quando o filho emite SIGCHLD, isso informa ao processo pai que está concluído e que pode ser retomado. Ele termina com um pouco mais de malabarismo e finalização ls.out.

Por que há tão muito malabarismo? Não, também não tenho certeza.


Claro que você pode mudar esse comportamento. Você pode armazenar em buffer a memória com algo parecido spongee isso será invisível a partir do comando proceder. Ainda estamos afetando os descritores de arquivo, mas não de uma maneira visível no sistema de arquivos.

ls | sponge ls.out
Oli
fonte
6

Também há um bom artigo sobre Implementação de operadores de redirecionamento e canal no shell . O que mostra como o redirecionamento pode ser implementado para que $ ls > ls.outpossa parecer:

main(){
    close(1); // Release fd no - 1
    open("ls.out", "w"); // Open a file with fd no = 1
    // Child process
    if (fork() == 0) {
        exec("ls"); 
    }
}
Dimitri Podborski
fonte