grep no sale hasta EOF si se canaliza a través de cat

19

Dado este ejemplo mínimo

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; )

sale LINE 1y luego, después de un segundo, sale LINE 2, como se esperaba .


Si canalizamos esto a grep LINE

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE

El comportamiento es el mismo que en el caso anterior, como se esperaba .


Si, alternativamente, canalizamos esto a cat

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | cat

El comportamiento es nuevamente el mismo, como se esperaba .


Sin embargo , si nos dirigimos a grep LINE, y luego a cat,

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep LINE | cat

no hay salida hasta que pasa un segundo, y ambas líneas aparecen en la salida de inmediato, lo que no esperaba .


¿Por qué sucede esto y cómo puedo hacer que la última versión se comporte de la misma manera que los primeros tres comandos?

lisyarus
fuente
catconcatena archivos ¿Qué estás tratando de hacer entrando cat?
Douglas celebrada el
15
@DouglasHeld Cuando se llama sin argumentos, catsimplemente se lee stdiny se genera en stdout. Por supuesto, se me ocurrió esta pregunta con muchas cosas complejas en lugar de echoy cat, pero resultaron ser irrelevantes, ya que el problema aparece con ejemplos mucho más simples.
lisyarus
3
@DouglasHeld: La conexión al gato a menudo es útil para forzar que stdout no sea una terminal. Por ejemplo, esta es una manera fácil de obtener muchos comandos para no usar resultados coloreados.
wchargin
¡Juro que este es un duplicado de otra pregunta sobre Stack Overflow!
iBug
@wchargin muchas gracias, me has enseñado algo nuevo sobre posix que nunca supe.
Douglas celebrada el

Respuestas:

38

Cuando grepla salida de (al menos GNU) no es una terminal, almacena su salida, que es lo que causa el comportamiento que estás viendo. Puede deshabilitar esto usando grepla --line-bufferedopción de GNU :

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | grep --line-buffered LINE | cat

o la stdbufutilidad:

( echo "LINE 1" ; sleep 1 ; echo "LINE 2" ; ) | stdbuf -oL grep LINE | cat

Apagar el almacenamiento en búfer en la tubería tiene más información sobre este tema.

Stephen Kitt
fuente
26

Explicación simplificada

Al igual que muchas utilidades, esto no es algo peculiar de un programa, grepvaría su salida estándar entre estar en línea y completamente en memoria intermedia . En el primer caso, la biblioteca C almacena en memoria intermedia los datos de salida en la memoria hasta que se llena el búfer que contiene esos datos o se agrega un carácter de salto de línea (o el programa finaliza limpiamente), por lo que llama write()para escribir realmente el contenido del búfer. En el último caso, solo el búfer en memoria que se llena (o el programa termina limpiamente) activa el write().

Explicación más detallada

Esta es la explicación bien conocida, pero ligeramente incorrecta. De hecho, la salida estándar no es amortiguada línea, pero inteligente amortiguada en la biblioteca GNU C y C biblioteca de BSD. La salida estándar también se vacía cuando la lectura de la entrada estándar agota su búfer en memoria (de entrada previa a la lectura) y la biblioteca C tiene que llamar read()para obtener más entrada y está leyendo el comienzo de una nueva línea. (Una razón para esto es evitar el punto muerto cuando otro programa se conecta a ambos extremos de un filtro y espera poder operar línea por línea, alternando entre escribir en el filtro y leerlo; como "coprocesos" en GNU awkpor ejemplo.)

Influencia de la biblioteca C

grepy las otras utilidades hacen esto, o más estrictamente, las bibliotecas C que usan hacen esto, porque esta es una característica definida de programación en el lenguaje C, en función de lo que detectan que es su salida estándar. Si (y solo si) no es un dispositivo interactivo, eligen el almacenamiento en búfer completo, de lo contrario eligen el almacenamiento en búfer inteligente. Se considera que una tubería no es un dispositivo interactivo, porque la definición de ser un dispositivo interactivo, al menos en el mundo de Unix y Linux, es esencialmente la isatty()devolución de la llamada verdadera para el descriptor de archivo relevante.

Soluciones para deshabilitar el almacenamiento en búfer completo

Algunas utilidades como greptienen opciones idiosincrásicas como --line-bufferedque cambian esta decisión, que como puede ver está mal nombrada. Pero una fracción cada vez más pequeña de los programas de filtro que uno podría usar realmente tiene esa opción.

En términos más generales, se pueden usar herramientas que profundizan en los componentes internos específicos de la biblioteca C y cambian su toma de decisiones (que tienen problemas de seguridad si el programa que se va a modificar es set-UID, y también son específicos de bibliotecas C particulares, y de hecho son específico para programas escritos o en capas sobre el lenguaje C), o herramientas como esas ptybandageque no cambian las partes internas del programa sino que simplemente interponen un pseudo-terminal como salida estándar para que la decisión salga como "interactiva", para afectar esto

Otras lecturas

JdeBP
fuente
1
Si la frase "línea tamponada" es un nombre inapropiado, entonces no es realmente culpa de grep, sino de las llamadas de la biblioteca subyacente, setbuf/setvbuf . No conozco una referencia en línea confiable para el estándar C, pero, por ejemplo, las páginas de manual de Linux y FreeBSD junto con la descripción POSIX de lo setvbufllaman "buffer de línea". Incluso la constante simbólica es _IOLBF.
ilkkachu
Bueno, ahora has aprendido mejor. Esta estrategia de almacenamiento en búfer se describe en la biblioteca GNU C doco, aunque sea brevemente. Laurent Bercot es más directo al respecto. Yo también lo he mencionado.
JdeBP
No pensé que "Tu expectativa es incorrecta" fue un buen título para esta excelente explicación del almacenamiento en búfer de salida. Espero que no le importe que lo eliminé y agregué algunos encabezados descriptivos para cada sección de la respuesta.
Anthony G - justicia para Mónica
2
@ilkkachu El estándar C de hecho usa "línea tamponada". Según 7.21.3 Archivos , párrafo 3 : "Cuando una secuencia no tiene búfer, ... Cuando una secuencia está completamente almacenada, ... Cuando una secuencia está almacenada en línea, los caracteres están destinados a ser transmitidos hacia o desde el entorno host como se bloquea cuando se encuentra un carácter de nueva línea ... "De hecho, el Estándar C usa la frase exacta" línea almacenada "cinco veces. Entonces no es un nombre inapropiado.
Andrew Henle
1
Además, el enfoque descrito aquí como "almacenamiento en búfer inteligente", según tengo entendido, parece ser justo lo que el estándar C describe como "almacenamiento en línea". Específicamente, además de vaciar el búfer en las nuevas líneas, "Cuando una secuencia está almacenada en línea, los caracteres están destinados a ser transmitidos hacia o desde el entorno host como un bloque cuando se solicita [...] la entrada en una secuencia no almacenada, o cuando la entrada se solicita en una secuencia de línea almacenada en búfer que requiere la transmisión de caracteres desde el entorno host ". Entonces, esto no es una peculiaridad de GNU o BSD, sino más bien lo que requiere el lenguaje.
John Bollinger
7

Utilizar

grep --line-buffered

para hacer que grep no almacene más de una línea a la vez.

choroba
fuente