¿Por qué 'sed q' funciona de manera diferente cuando se lee desde una tubería?

25

Creé un archivo de prueba llamado 'prueba' que contiene lo siguiente:

xxx
yyy
zzz

Ejecuté el comando:

(sed '/y/ q'; echo aaa; cat) < test

y obtuve:

xxx
yyy
aaa
zzz

Entonces corrí:

cat test | (sed '/y/ q'; echo aaa; cat)

y consiguió:

xxx
yyy
aaa

Pregunta

sedlee e imprime hasta que encuentra una línea con 'y', luego se detiene. En el primer caso, pero no en el segundo, el gato lee e imprime el resto.

¿Alguien puede explicar qué fenómeno está detrás de esta diferencia de comportamiento?

También noté que funciona de esta manera en Ubuntu 16.04 y Centos 6, pero en Centos 7 ninguno de los comandos imprime 'zzz'.

Antti Kuusela
fuente
Supongo que cat(en el sub shell) puede reutilizar el descriptor de archivo en el primer caso, porque stdin está vinculado a un archivo real. En el segundo caso, stdin es de una tubería y no de un archivo real. Tenga en cuenta que (sed '/y/ q'; echo aaa; cat) < <(cat test)tampoco se imprime zzz.
Martin Nyolt
1
Un ejemplo más simple: (head -n1; head -n1) < testycat test | (head -n1; head -n1)
Martin Nyolt

Respuestas:

22

Cuando el archivo de entrada se puede buscar (como leer desde un archivo normal) o no se puede buscar (como leer desde una tubería), sed(y otras utilidades estándar) se comportarán de manera diferente (lea la INPUT FILESsección en este enlace ).

Cita del documento:

Cuando una utilidad estándar lee un archivo de entrada que se puede buscar y finaliza sin un error antes de que llegue al final del archivo, la utilidad se asegurará de que el desplazamiento del archivo en la descripción del archivo abierto esté colocado correctamente justo después del último byte procesado por la utilidad.

Entonces en:

(sed '/y/ q'; echo aaa; cat) < test

sedrealizó el qcomando uit antes de llegar a EOF, por lo que dejó el desplazamiento del archivo al comienzo de la zzzlínea, por lo quecat puede continuar imprimiendo las líneas (GNU sed no es compatible con POSIX en alguna condición, consulte a continuación).

Y continuando desde el documento:

Para los archivos que no son buscables, el estado del desplazamiento del archivo en la descripción del archivo abierto para ese archivo no está especificado

En este caso, el comportamiento no está especificado. La mayoría de las herramientas estándar, include sed, consumirán la entrada tanto como sea posible. Leyó pasar la yyylínea y quit sin restaurar el desplazamiento del archivo, por lo que no queda nada paracat .


GNU sedno cumple con el estándar, depende de la implementación estándar del sistema y la versión de glibc:

$ (gsed '/y/ q'; echo aaa; cat) < test
xxx
yyy
aaa

Aquí, el resultado se obtuvo de Mac OSX 10.11.6, máquinas virtuales Centos 7.2 - glibc 2.17, Ubuntu 14.04 - glibc 2.19, que se ejecutan en Openstack con backend CEPH.

En esos sistemas, puede usar la -uopción para lograr el comportamiento estándar:

(gsed -u '/y/ q'; echo aaa; cat) </tmp/test

y para pipa:

$ cat test | (gsed -u '/y/ q'; echo aaa; cat)
xxx
yyy
aaa
zzz

lo que conduce a un rendimiento terriblemente ineficiente, porque sedtiene que leer un byte a la vez. Una salida parcial de strace:

$ strace -fe read sh -c '{ sed -u "/y/q"; echo aaa; cat; } <test'
...
[pid  5248] read(3, "", 4096)           = 0
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "x", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
xxx
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "y", 1)             = 1
[pid  5248] read(0, "\n", 1)            = 1
yyy
...
Cuonglm
fuente
1
Para GNU sed, eso depende de la implementación estándar del sistema. En los sistemas GNU (con GNU libc), GNU sedserá compatible exit()y buscará archivos administrados por stdio.
Stéphane Chazelas
@ StéphaneChazelas: ¿Cómo verificarlo? Con mi Centos 7.2, Ubuntu 14.04 VM, sedno es compatible, mi portátil manjaro sí, todos tienen la misma sed versión 4.2.2
Cuonglm
@ StéphaneChazelas: Parece que algo sucedió debajo del capó. En mis máquinas virtuales, strace -f sh -c '{ sed "/y/q"; echo aaa; cat; } <test'demuestro que no lseek()se realizó nada , mientras que en mi manjaro lseek()se llamó antes exit_group().
Cuonglm
Supongo que eso depende de la versión de GNU libc. Puedes probar con un main() { char buf[999]; gets(buf); }'programa.
Stéphane Chazelas
1
@ StéphaneChazelas: Confirmado. Mis dos máquinas virtuales tienen 2.17 y 2.19, mientras que la de mi manjaro es 2.23. ¿Esto se considera un error glibc? ¿Tiene alguna información sobre el cambio entre las versiones de glibc
Cuonglm