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?
ls > ../ls.out
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?
ls > ../ls.out
Respostas:
Ao avaliar o comando, o >
redirecionamento é resolvido primeiro: pelo tempo que ls
o 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.out
criaçã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)
sponge
grava no arquivo somente quando o restante do canal terminar de executar, portanto, ls
é executado antes da ls.out
criação ( sponge
é fornecido com o moreutils
pacote):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(funciona para ls > ls.out
o caso específico)
A expansão do nome do arquivo é realizada 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 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.
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 stdin
redirecionamento, 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?
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.out
como 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.out
e 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 sponge
e 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
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.out
possa 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");
}
}