¿Por qué cat x >> x loop?

Los siguientes commands bash entran en un bucle infinte:

$ echo hi > x $ cat x >> x 

Adivino que el cat continúa leyendo desde x después de que comenzó a escribir en stdout. Lo que es confuso, sin embargo, es que mi propia implementación de testing de gato exhibe un comportamiento 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; } 

Si corro:

 $ make mycat $ echo hi > x $ ./mycat x >> x 

No es un bucle. Dado el comportamiento de cat y el hecho de que estoy descargando a stdout antes de fread llamar a fread , esperaría que este código C continúe leyendo y escribiendo en un ciclo.

¿Cómo son estos dos comportamientos consistentes? ¿Qué mecanismo explica por qué cat loops mientras que el código anterior no?

En un sistema RHEL más antiguo que tengo, /bin/cat no realiza bucle para cat x >> x . cat da el post de error "cat: x: el file de input es el file de salida". Puedo engañar /bin/cat haciendo esto: cat < x >> x . Cuando pruebo tu código de arriba, obtengo el "bucle" que describes. También escribí un sistema basado en llamadas "cat":

 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int ac, char **av) { char buf[4906]; int fd, cc; fd = open(av[1], O_RDONLY); while ((cc = read(fd, buf, sizeof(buf))) > 0) if (cc > 0) write(1, buf, cc); close(fd); return 0; } 

Esto también. El único almacenamiento en búfer aquí (a diferencia de "mycat" basado en stdio) es lo que sucede en el kernel.

Creo que lo que está sucediendo es que el descriptor de file 3 (el resultado de open(av[1]) ) tiene un desplazamiento en el file de 0. El descriptor 1 (stdout) tiene un desplazamiento de 3, porque ">>" causa el invocando shell para hacer un lseek() en el descriptor de file antes de entregárselo al process hijo de cat .

Hacer una read() de cualquier tipo, ya sea en un búfer de stdio, o un char buf[] simple char buf[] avanza la position del descriptor de file 3. Hacer una write() avanza la position del descriptor de file 1. Esos dos desplazamientos son numbers diferentes. Debido a ">>", el descriptor de file 1 siempre tiene un desplazamiento mayor o igual que el desplazamiento del descriptor de file 3. Por lo tanto, cualquier progtwig "similar a un gato" hará loops, a less que tenga algún almacenamiento intermedio interno. Es posible, incluso probable, que una implementación stdio de un FILE * (que es el tipo de símbolos stdout f en su código) que incluye su propio búfer. fread() puede hacer una llamada al sistema read() para llenar el buffer interno fo f . Esto puede o no cambiar nada en el interior de stdout . Llamar a fwrite() en stdout puede o no cambiar algo dentro de f . Entonces un "gato" basado en stdio podría no funcionar. O podría ser. Es difícil de decir sin leer un feo y horrible código de libc.

Hice un strace en el cat RHEL – solo hace una sucesión de llamadas al sistema de read() y write() . Pero un cat no tiene que trabajar de esta manera. Sería posible mmap() el file de input, luego write(1, mapped_address, input_file_size) . El kernel haría todo el trabajo. O podría hacer una llamada al sistema sendfile() entre los descriptores de files de input y salida en los sistemas Linux. Se rumoreaba que los viejos sistemas SunOS 4.x hacían el truco de la asignación de memory, pero no sé si alguien alguna vez ha hecho un gato basado en sendfile. En cualquier caso, el "bucle" no ocurriría, ya que tanto write() como sendfile() requieren un parámetro de longitud a transferencia.

Una implementación moderna de cat (sunos-4.0 1988) usa mmap () para mapear todo el file y luego llama a 1x write () para este espacio. Tal implementación no se repetirá mientras la memory virtual permita mapear todo el file.

Para otras implementaciones, depende de si el file es más grande que el búfer de E / S.

Como está escrito en las trampas de Bash , no puede leer un file y escribir en la misma tubería.

Dependiendo de lo que haga su canalización, el file puede ser destruido (a 0 bytes, o posiblemente a un número de bytes igual al tamaño del búfer de canalización de su sistema operativo), o puede crecer hasta llenar el espacio disponible en el disco o alcanzar la limitación de tamaño de file de su sistema operativo, o su cuota, etc.

La solución es usar editor de text o variable temporal.

Tienes algún tipo de condición de carrera entre ambos x . Algunas implementaciones de cat (por ejemplo, coreutils 8.23) prohíben que:

 $ cat x >> x cat: x: input file is output file 

Si esto no se detecta, el comportamiento obviamente dependerá de la implementación (tamaño del búfer, etc.).

En tu código, podrías intentar agregar un clearerr(f); después de la fflush , en caso de que el siguiente fread devuelva un error si se establece el indicador de fin de file.