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

26

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

    
por edward torvalds 01.02.2016 / 18:10

4 respostas

36

Ao avaliar o comando, o redirecionamento > é resolvido primeiro: assim, no momento em que ls for executado, o arquivo de saída já foi criado.

Esta é também a razão pela qual ler e gravar no mesmo arquivo usando um redirecionamento > dentro do mesmo comando trunca o arquivo; no momento em que o comando é executado, o arquivo já foi 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, portanto, ls é executado antes de o ls.out ser criado:

    $ 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)

    sponge grava no arquivo somente quando o restante do pipe termina a execução, portanto ls é executado antes da criação do ls.out ( sponge é fornecido com o pacote moreutils ):

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

    A expansão de nome de arquivo é executada antes que o redirecionamento seja resolvido, portanto ls será 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 do programa / script / o que quer que seja executado, não vejo uma razão específica para que seja obrigatório , mas vejo dois motivos pelos quais ele é melhor para fazer isso:

  • não redireciona STDIN de antemão faria o programa / script / o que quer que seja mantido até STDIN ser redirecionado;

  • não redirecionar o STDOUT de antemão deve necessariamente fazer com que o buffer do shell seja o resultado do programa / script / whatever, até que o STDOUT seja redirecionado;

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

Isso é o que ocorre comigo, não estou afirmando que essas são as razões reais; mas acho que, apesar de tudo, se alguém tivesse uma escolha, eles iriam redirecionar antes de qualquer maneira pelas razões acima mencionadas.

    
por kos 01.02.2016 / 18:18
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. Redirecionamento permite     comandos dos arquivos dos comandos a serem duplicados, abertos, fechados, feitos para se referirem     arquivos diferentes, e pode alterar os arquivos que o comando lê e     escreve para.

Primeira frase, sugere que a saída seja feita para ir a algum lugar diferente de stdin com redirecionamento logo antes do comando ser executado. Assim, para ser redirecionado para o arquivo, o arquivo deve primeiro ser criado pelo próprio shell.

Para evitar ter 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 sobre o terminal para o 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 quê?

Pense nisso - onde será o resultado? Um programa tem funções como printf , sprintf , puts , que todas, por padrão, vão para stdout , mas a saída pode ser enviada para o arquivo se o arquivo não existir em primeiro lugar? É como a água. Você pode pegar um copo de água sem colocar o vidro embaixo da torneira primeiro?

    
por Sergiy Kolodyazhnyy 01.02.2016 / 18:17
10

Eu não discordo das respostas atuais. O arquivo de saída deve ser aberto antes de o comando ser executado ou o comando não terá onde gravar sua saída.

Isso ocorre porque "tudo é um arquivo" em nosso mundo. Saída para a tela é SDOUT (também conhecido como descritor de arquivo 1). Para uma aplicação escrever no terminal, abre fd1 e grava nela como um arquivo.

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

Mas é bom dizer isso, mas você pode facilmente ver como isso funciona com strace . É muito pesado, mas esse exemplo é bem curto.

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

Dentro de strace.out , podemos ver os seguintes destaques:

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

Isso abre ls.out as fd3 . Escreva apenas. Trunca (sobrescreve) 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. Nós desviaremos STDOUT (fd1) para fd10 e o fecharemos. Isso ocorre porque não estamos enviando nada para o STDOUT real com este comando. Ele termina duplicando o identificador de gravação para ls.out e fechando o 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 é procurar 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 aguarda. Durante esta operação, qualquer STDOUT terá realmente mapeado para o identificador de arquivo aberto em ls.out . Quando o filho emite SIGCHLD , isso informa ao processo pai que está concluído e que ele pode ser retomado. Termina com um pouco mais de malabarismo e um fechamento de ls.out .

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

Claro que você pode mudar esse comportamento. Você poderia armazenar em buffer a memória com algo como sponge e isso ficará invisível a partir do comando procedente. Ainda estamos afetando os descritores de arquivos, mas não de forma visível no sistema de arquivos.

ls | sponge ls.out
    
por Oli 02.02.2016 / 14:25
6

Há também um belo artigo sobre Implementação de redirecionamento e operadores de pipe no shell . O que mostra como o redirecionamento pode ser implementado para que $ ls > ls.out seja semelhante:

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"); 
    }
}
    
por incBrain 01.02.2016 / 18:40