Los siguientes comandos bash entran en un bucle infinito:
$ echo hi > x
$ cat x >> x
Puedo adivinar que cat
continúa leyendo x
después de que comenzó a escribir en stdout. Sin embargo, lo que es confuso es que mi propia implementación de prueba de cat 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
Lo hace no lazo. Dado el comportamiento cat
y el hecho de que me estoy volviendo loco stdout
antes fread
, se espera que este código C continúe leyendo y escribiendo en un ciclo.
¿Cómo son consistentes estos dos comportamientos? ¿Qué mecanismo explica por qué se cat
repite mientras que el código anterior no lo hace?
shell
files
io-redirection
cat
Tyler
fuente
fuente
cat x >> x
causa un error; sin embargo, este comando se sugiere en Kernighan y el libro de Unike de Pike como ejercicio.cat
Lo más probable es que use llamadas del sistema en lugar de stdio. Con stdio, su programa puede estar almacenando en caché EOFness. Si comienza con un archivo de más de 4096 bytes, ¿obtiene un bucle infinito?Respuestas:
En un sistema más antiguo RHEL lo que tengo,
/bin/cat
lo hace no lazo paracat x >> x
.cat
da el mensaje de error "cat: x: el archivo de entrada es el archivo de salida". Puedo engañar/bin/cat
al hacer esto:cat < x >> x
. Cuando pruebo su código anterior, obtengo el "bucle" que describe. También escribí una llamada al sistema basada en "cat":Esto también se repite. El único búfer aquí (a diferencia de "mycat" basado en stdio) es lo que sucede en el núcleo.
Creo que lo que está sucediendo es que el descriptor de archivo 3 (el resultado de
open(av[1])
) tiene un desplazamiento dentro del fichero de 0. 1 Filed descriptor (stdout) cuenta con un desplazamiento de 3, debido a que el ">>" hace que el shell que invoca a hacer unalseek()
en el descriptor de archivo antes de entregarlo alcat
proceso secundario.Hacer una
read()
de cualquier tipo, ya sea en un búfer stdio o en un plano,char buf[]
avanza la posición del descriptor de archivo 3. Hacer unwrite()
avance de la posición del descriptor de archivo 1. Esos dos desplazamientos son números diferentes. Debido al ">>", el descriptor de archivo 1 siempre tiene un desplazamiento mayor o igual que el desplazamiento del descriptor de archivo 3. Por lo tanto, cualquier programa "similar a un gato" se repetirá, a menos que haga un búfer interno. Es posible, incluso probable, que una implementación estándar de unFILE *
(que es el tipo de símbolosstdout
yf
en su código) que incluya su propio búfer.fread()
en realidad puede hacer una llamadaread()
al sistema para llenar el búfer interno fof
. Esto puede o no cambiar nada en el interior destdout
. llamandofwrite()
enstdout
puede o no cambiar nada en el interior def
. Por lo tanto, un "gato" basado en stdio podría no repetirse. O que podría hacerlo. Difícil de decir sin leer un montón de código libc feo y feo.Hice una
strace
en la RHELcat
- sólo se hace una sucesión deread()
ywrite()
llamadas al sistema. Pero acat
no tiene que funcionar de esta manera. Sería posiblemmap()
ingresar el archivo, luego hacerwrite(1, mapped_address, input_file_size)
. El núcleo haría todo el trabajo. O puede hacer unasendfile()
llamada al sistema entre los descriptores de los archivos de entrada y salida en los sistemas Linux. Se rumoreaba que los viejos sistemas SunOS 4.x hacían el truco de mapeo de memoria, pero no sé si alguien había hecho un gato basado en sendfile. En cualquier caso, el "bucle" sería no ocurrir, ya que tantowrite()
ysendfile()
requerir un parámetro de longitud a transferencia.fuente
fread
llamada almacenó en caché una bandera EOF como sugirió Mark Plotnick. Evidencia: [1] El gato Darwin usa lectura, no fread; y [2] el fread de Darwin llama __srefill que se establecefp->_flags |= __SEOF;
en algunos casos. [1] src.gnu-darwin.org/src/bin/cat/cat.c [2] opensource.apple.com/source/Libc/Libc-167/stdio.subproj/…cat
escat -u
- U para no tamponada .>>
debe implementarse llamando a open () con elO_APPEND
indicador, lo que hace que cada operación de escritura escriba (atómicamente) en el final actual del archivo, sin importar la posición del descriptor del archivo antes de la lectura. Este comportamiento es necesario parafoo >> logfile & bar >> logfile
que funcione correctamente, por ejemplo: no puede darse el lujo de asumir que la posición después del final de su última escritura sigue siendo el final del archivo.Una implementación cat moderna (sunos-4.0 1988) usa mmap () para mapear todo el archivo y luego llama a 1x write () para este espacio. Dicha implementación no se repetirá mientras la memoria virtual permita mapear todo el archivo.
Para otras implementaciones, depende de si el archivo es más grande que el búfer de E / S.
fuente
cat
implementaciones no amortiguan su salida (-u
implícita). Esos siempre se repetirán.Como está escrito en las trampas de Bash , no puede leer un archivo y escribir en él en la misma tubería.
La solución es usar un editor de texto o una variable temporal.
fuente
Tienes algún tipo de condición de carrera entre ambos
x
. Algunas implementaciones decat
(por ejemplo, coreutils 8.23) prohíben que:Si esto no se detecta, el comportamiento obviamente dependerá de la implementación (tamaño del búfer, etc.).
En su código, puede intentar agregar un
clearerr(f);
después defflush
, en caso de que el siguientefread
devuelva un error si se establece el indicador de fin de archivo.fuente
i = i++;
comportamiento indefinido de C , de ahí la discrepancia.cat
.