Os seguintes comandos bash entram em um loop infinte:
$ echo hi > x
$ cat x >> x
Eu acho que isso cat
continua a ser lido x
depois que ele começou a escrever no stdout. O que é confuso, no entanto, é que minha própria implementação de teste de gato exibe um comportamento diferente:
// mycat.c
#include <stdio.h>
int main(int argc, char **argv) {
FILE *f = fopen(argv[1], "rb");
char buf[4096];
int num_read;
while ((num_read = fread(buf, 1, 4096, f))) {
fwrite(buf, 1, num_read, stdout);
fflush(stdout);
}
return 0;
}
Se eu correr:
$ make mycat
$ echo hi > x
$ ./mycat x >> x
Ele faz não loop. Dado o comportamento cat
e o fato de que eu estou liberando para o stdout
anterior fread
é chamado novamente, eu esperaria que esse código C continuasse lendo e gravando em um ciclo.
Como esses dois comportamentos são consistentes? Que mecanismo explica por que faz um cat
loop enquanto o código acima não funciona?
shell
files
io-redirection
cat
Tyler
fonte
fonte
cat x >> x
causa um erro; no entanto, esse comando é sugerido no livro Unix de Kernighan e Pike como um exercício.cat
provavelmente usa chamadas do sistema em vez do stdio. Com o stdio, seu programa pode estar armazenando em cache o EOFness. Se você começar com um arquivo maior que 4096 bytes, obtém um loop infinito?Respostas:
Em um sistema RHEL mais antigo que eu tenho, não
/bin/cat
faz loop para . dá a mensagem de erro "cat: x: arquivo de entrada é arquivo de saída". I pode enganar , fazendo isso: . Quando tento seu código acima, recebo o "loop" que você descreve. Também escrevi um "gato" baseado em chamada de sistema:cat x >> x
cat
/bin/cat
cat < x >> x
Isso dá laços também. O único buffer aqui (ao contrário do "mycat" baseado em stdio) é o que acontece no kernel.
Acho que o que está acontecendo é que o descritor de arquivo 3 (o resultado de
open(av[1])
) tem um deslocamento no arquivo de 0. O descritor arquivado 1 (stdout) tem um deslocamento de 3, porque o ">>" faz com que o shell de chamada faça umlseek()
no descritor de arquivo antes de entregá-lo aocat
processo filho.Executar
read()
qualquer tipo, seja em um buffer stdio ou em uma planilha,char buf[]
avança a posição do descritor de arquivo 3. Executar awrite()
avança a posição do descritor de arquivo 1. Esses dois deslocamentos são números diferentes. Por causa do ">>", o descritor de arquivo 1 sempre tem um deslocamento maior ou igual ao deslocamento do descritor de arquivo 3. Portanto, qualquer programa "semelhante a um gato" fará um loop, a menos que faça algum buffer interno. É possível, talvez até provável, que uma implementação stdio de aFILE *
(que é o tipo dos símbolosstdout
ef
no seu código) inclua seu próprio buffer.fread()
pode realmente fazer uma chamada do sistemaread()
para preencher o buffer interno fof
. Isso pode ou não mudar nada no interior destdout
. chamandofwrite()
emstdout
pode ou não alterar nada dentro def
. Portanto, um "gato" baseado em stdio pode não ser repetido. Ou pode. Difícil dizer sem ler muitos códigos libc feios e feios.Eu fiz uma
strace
no RHELcat
- ele só faz uma sucessão deread()
ewrite()
chamadas do sistema. Mas acat
não precisa funcionar dessa maneira. Seria possível parammap()
o arquivo de entrada, então façawrite(1, mapped_address, input_file_size)
. O kernel faria todo o trabalho. Ou você pode fazer umasendfile()
chamada do sistema entre os descritores de arquivo de entrada e saída nos sistemas Linux. Dizia-se que os antigos sistemas SunOS 4.x faziam o truque de mapeamento de memória, mas não sei se alguém já fez um gato baseado em arquivo de envio. Em ambos os casos, o "loop" não aconteceria, pois amboswrite()
esendfile()
requerem um parâmetro de comprimento para transferir.fonte
fread
chamada armazenou em cache um sinalizador EOF, como sugeriu Mark Plotnick. Evidência: [1] o gato de Darwin usa leitura, não medo; e [2] o medo de Darwin chama __srefill, que ocorrefp->_flags |= __SEOF;
em alguns casos. [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…cat
écat -u
- u para unbuffered .>>
deve ser implementado chamando open () com oO_APPEND
sinalizador, o que faz com que cada operação de gravação grave (atomicamente) no final atual do arquivo, independentemente da posição do descritor de arquivo antes da leitura. Esse comportamento é necessário parafoo >> logfile & bar >> logfile
funcionar corretamente, por exemplo - você não pode assumir que a posição após o final da sua última gravação ainda é o final do arquivo.Uma implementação moderna de gato (sunos-4.0 1988) usa mmap () para mapear o arquivo inteiro e depois chama 1x write () para esse espaço. Essa implementação não será executada enquanto a memória virtual permitir mapear o arquivo inteiro.
Para outras implementações, depende se o arquivo é maior que o buffer de E / S.
fonte
cat
implementações não armazenam em buffer sua saída (-u
implícita). Aqueles sempre serão repetidos.Conforme escrito nas armadilhas do Bash , você não pode ler um arquivo e gravá-lo no mesmo pipeline.
A solução é usar o editor de texto ou variável temporária.
fonte
Você tem algum tipo de condição de corrida entre os dois
x
. Algumas implementações decat
(por exemplo, coreutils 8.23) proíbem que:Se isso não for detectado, o comportamento obviamente dependerá da implementação (tamanho do buffer, etc.).
No seu código, você pode tentar adicionar um
clearerr(f);
após offlush
, caso o próximofread
retorne um erro se o indicador de fim de arquivo estiver definido.fonte
i = i++;
comportamento indefinido de C , daí a discrepância.cat
.