Tuberías, ¿cómo fluyen los datos en una tubería?

22

No entiendo cómo fluyen los datos en la tubería y espero que alguien pueda aclarar lo que está sucediendo allí.

Pensé que una tubería de comandos procesa archivos (texto, matrices de cadenas) en línea por línea. (Si cada comando funciona línea por línea). Cada línea de texto pasa a través de la tubería, los comandos no esperan a que el anterior termine de procesar toda la entrada.

Pero parece que no es así.

Aquí hay un ejemplo de prueba. Hay algunas líneas de texto. Las mayúsculas y repito cada línea dos veces. Lo hago con cat text | tr '[:lower:]' '[:upper:]' | sed 'p'.

Para seguir el proceso podemos ejecutarlo "interactivamente": omita el nombre de archivo de entrada cat. Cada parte de la tubería corre línea por línea:

$ cat | tr '[:lower:]' '[:upper:]'
alkjsd
ALKJSD
sdkj
SDKJ
$ cat | sed 'p'
line1
line1
line1
line 2
line 2
line 2

Pero la tubería completa espera a que termine la entrada con EOFy solo luego imprime el resultado:

$ cat | tr '[:lower:]' '[:upper:]' | sed 'p'
I am writing...
keep writing...
now ctrl-D
I AM WRITING...
I AM WRITING...
KEEP WRITING...
KEEP WRITING...
NOW CTRL-D
NOW CTRL-D

¿Se supone que es así? ¿Por qué no es línea por línea?

xealits
fuente
No es la tubería, está catalmacenando en búfer hasta que se cierra la entrada estándar.
Ricitos
pero try sedhacer líneas de proceso catantes de que cierre stdin
xealits
Los valores predeterminados utilizados por stdio (que creo que usan todos los programas mencionados) es que stderr no tiene búfer, y stdout tiene búfer de línea cuando se escribe en un terminal y de lo contrario está completamente protegido (por ejemplo, si está escribiendo en un archivo o una tubería) . Algunos de los comandos tienen banderas que pueden cambiar el búfer stdout, pero parece que tr no.
kasperd

Respuestas:

36

Existe una regla general de almacenamiento en búfer seguida de la biblioteca de E / S estándar C ( stdio) que utilizan la mayoría de los programas Unix. Si la salida va a un terminal, se vacía al final de cada línea; de lo contrario, se vacía solo cuando el búfer (8K en mi sistema Linux / amd64; podría ser diferente en el suyo) está lleno.

Si todos sus utilidades estaban siguiendo la regla general, que se vería retrasada de salida en todos sus ejemplos ( cat|sed, cat|try cat|tr|sed). Pero hay una excepción: GNU catnunca amortigua su salida. No se usa stdioo cambia la stdiopolítica de almacenamiento en búfer predeterminada .

Puedo estar bastante seguro de que estás usando GNU caty no algún otro Unix catporque los demás no se comportarían de esta manera. Unix tradicional cattiene una -uopción para solicitar una salida sin búfer. GNU catignora la -uopción porque su salida siempre está sin búfer.

Entonces, siempre que tenga una tubería con un cata la izquierda, en el sistema GNU, el paso de datos a través de la tubería no se retrasará. El catni siquiera se va línea por línea - su terminal está haciendo eso. Mientras escribe la entrada para cat, su terminal está en modo "canónico", basado en líneas, con teclas de edición como retroceso y ctrl-U que le ofrecen la oportunidad de editar la línea que ha escrito antes de enviarla Enter.

En el cat|tr|sedejemplo, trsigue recibiendo datos cattan pronto como presiona Enter, pero trsigue la stdiopolítica predeterminada: su salida se dirige a una tubería, por lo que no se vacía después de cada línea. Escribe en la segunda tubería cuando el búfer está lleno o cuando se recibe un EOF, lo que ocurra primero.

sedtambién sigue la stdiopolítica predeterminada, pero su salida se dirige a un terminal, por lo que escribirá cada línea tan pronto como haya terminado. Esto tiene un efecto sobre cuánto debe escribir antes de que algo aparezca en el otro extremo de la tubería: si sedbloqueaba su salida, tendría que escribir el doble (para llenar trel búfer de salida y sed la salida de buffer).

GNU sedtiene la -uopción, por lo que si invierte el orden y lo usa cat|sed -u|tr, verá que la salida aparece instantáneamente nuevamente. (La sed -uopción puede estar disponible en otros lugares, pero no creo que sea una tradición antigua de Unix cat -u) Hasta donde puedo decir, no hay una opción equivalente para tr.

Hay una utilidad llamada stdbufque le permite alterar el modo de almacenamiento en búfer de cualquier comando que use los stdiovalores predeterminados. Es un poco frágil ya que utiliza LD_PRELOADpara lograr algo que la biblioteca C no fue diseñada para soportar, pero en este caso parece funcionar:

cat | stdbuf -o 0 tr '[:lower:]' '[:upper:]' | sed 'p'

fuente
1
¡Gracias! Impresionante respuesta. Probablemente debería mencionar el buffering en la pregunta de alguna manera, para que uno pueda encontrarlo.
xealits
teey ddtambién suelen jugar según sus propias reglas. Cuando se combinan de manera imaginativa, las tres herramientas pueden negar de forma bastante portátil cualquier necesidad de stdbuftuberías en segundo plano.
mikeserv 01 de
1
Esta es una de las razones para evitar un uso inútil del gato .
hobbs
8

Esto en realidad me llevó un poco de pensamiento para entender y aún más para responder. Gran pregunta (la votaré a continuación).

Olvidó intentar tr | seden los elementos de depuración anteriores:

>tr '[:lower:]' '[:upper:]' | sed 'p'
i am writing
still writing
now ctrl-d
I AM WRITING
I AM WRITING
STILL WRITING
STILL WRITING
NOW CTRL-D
NOW CTRL-D
>

Entonces evidentemente tramortiguadores. ¡Aprenda algo nuevo cada día!

EDITAR :

Mientras pienso en esto, hemos aislado la causa, pero no hemos proporcionado una explicación. Si cat | tr, escribe de inmediato, si cat | sed, escribe de inmediato, pero si tr | sed, se espera para EOF. Yo sugeriría que la respuesta podría ser enterrado en tro sedcódigo fuente entonces, y no ser un problema de la tubería.

EDITAR :

Veo que Wumpus proporcionó la explicación mientras escribía la última edición. ¡Gracias!

Poisson Aerohead
fuente
1
de hecho se amortiguan! y la prueba con líneas de aproximadamente 8 kb, como mencionó Wumpus, muestra que el búfer es de 8 KB. Me gustaría aceptar ambas respuestas para compartir algo de reputación, pero tomaré la de Wumpus como la más completa. ¡Gracias de cualquier manera!
xealits
1
No hay problema, la mía fue la respuesta empírica, la suya fue la experta.
Poisson Aerohead 01 de
Consulte también esta pregunta que muestra cómo usar, stdbufque también podría ser útil. unix.stackexchange.com/questions/182537/…
Joe