¡Se intercalan! Solo probó ráfagas de salida cortas, que permanecen sin dividir, pero en la práctica es difícil garantizar que cualquier salida en particular permanezca sin dividir.
Búfer de salida
Depende de cómo los programas amortiguan su salida. La biblioteca stdio que la mayoría de los programas usan cuando escriben usa buffers para hacer que la salida sea más eficiente. En lugar de generar datos tan pronto como el programa llama a una función de biblioteca para escribir en un archivo, la función almacena estos datos en un búfer y solo genera los datos una vez que el búfer se ha llenado. Esto significa que la salida se realiza en lotes. Más precisamente, hay tres modos de salida:
- Sin búfer: los datos se escriben inmediatamente, sin usar un búfer. Esto puede ser lento si el programa escribe su salida en pequeños pedazos, por ejemplo, carácter por carácter. Este es el modo predeterminado para el error estándar.
- Totalmente almacenado en búfer: los datos solo se escriben cuando el búfer está lleno. Este es el modo predeterminado cuando se escribe en una tubería o en un archivo normal, excepto con stderr.
- Búfer de línea: los datos se escriben después de cada nueva línea o cuando el búfer está lleno. Este es el modo predeterminado al escribir en un terminal, excepto con stderr.
Los programas pueden reprogramar cada archivo para que se comporte de manera diferente y pueden vaciar explícitamente el búfer. El búfer se vacía automáticamente cuando un programa cierra el archivo o sale normalmente.
Si todos los programas que escriben en la misma tubería usan el modo de búfer de línea o usan el modo de no búfer y escriben cada línea con una sola llamada a una función de salida, y si las líneas son lo suficientemente cortas como para escribir en un solo fragmento, entonces la salida será un entrelazado de líneas enteras. Pero si uno de los programas usa el modo totalmente protegido, o si las líneas son demasiado largas, verá líneas mixtas.
Aquí hay un ejemplo donde intercalo la salida de dos programas. Usé GNU coreutils en Linux; diferentes versiones de estas utilidades pueden comportarse de manera diferente.
yes aaaa
escribe aaaa
para siempre en lo que es esencialmente equivalente al modo de almacenamiento en línea. La yes
utilidad en realidad escribe varias líneas a la vez, pero cada vez que emite salida, la salida es un número entero de líneas.
echo bbbb; done | grep b
escribe bbbb
para siempre en modo totalmente protegido. Utiliza un tamaño de búfer de 8192, y cada línea tiene una longitud de 5 bytes. Como 5 no divide 8192, los límites entre escrituras no están en un límite de línea en general.
Vamos a lanzarlos juntos.
$ { yes aaaa & while true; do echo bbbb; done | grep b & } | head -n 999999 | grep -e ab -e ba
bbaaaa
bbbbaaaa
baaaa
bbbaaaa
bbaaaa
bbbaaaa
ab
bbbbaaa
Como puede ver, sí a veces interrumpió grep y viceversa. Solo alrededor del 0.001% de las líneas se interrumpieron, pero sucedió. La salida es aleatoria, por lo que el número de interrupciones variará, pero vi al menos algunas interrupciones cada vez. Habría una fracción mayor de líneas interrumpidas si las líneas fueran más largas, ya que la probabilidad de una interrupción aumenta a medida que disminuye el número de líneas por buffer.
Hay varias formas de ajustar el búfer de salida . Los principales son:
- Desactive el almacenamiento en búfer en los programas que usan la biblioteca stdio sin cambiar su configuración predeterminada con el programa que se
stdbuf -o0
encuentra en GNU coreutils y algunos otros sistemas como FreeBSD. Alternativamente, puede cambiar a almacenamiento en línea con stdbuf -oL
.
- Cambie al almacenamiento en línea al dirigir la salida del programa a través de un terminal creado solo para este propósito con
unbuffer
. Algunos programas pueden comportarse de manera diferente de otras maneras, por ejemplo, grep
utiliza colores por defecto si su salida es un terminal.
- Configure el programa, por ejemplo, pasando
--line-buffered
a GNU grep.
Veamos el fragmento de arriba otra vez, esta vez con almacenamiento en línea en ambos lados.
{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & } | head -n 999999 | grep -e ab -e ba
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
abbbb
Esta vez sí, nunca interrumpió grep, pero grep a veces interrumpió sí. Iré a por qué más tarde.
Intercalado de tuberías
Mientras cada programa emite una línea a la vez, y las líneas son lo suficientemente cortas, las líneas de salida estarán perfectamente separadas. Pero hay un límite para la longitud de las líneas para que esto funcione. La tubería en sí tiene un búfer de transferencia. Cuando un programa sale a una tubería, los datos se copian del programa de escritura al búfer de transferencia de la tubería, y luego desde el búfer de transferencia de la tubería al programa lector. (Al menos conceptualmente, el núcleo a veces puede optimizar esto en una sola copia).
Si hay más datos para copiar de los que caben en el búfer de transferencia de la tubería, entonces el núcleo copia un búfer lleno a la vez. Si varios programas están escribiendo en la misma tubería, y el primer programa que elige el núcleo quiere escribir más de un búfer, entonces no hay garantía de que el núcleo volverá a elegir el mismo programa la segunda vez. Por ejemplo, si P es el tamaño del búfer, foo
quiere escribir 2 * P bytes y bar
quiere escribir 3 bytes, entonces una posible intercalación es P bytes desde foo
, luego 3 bytes desde bar
y P bytes desde foo
.
Volviendo al ejemplo anterior de yes + grep, en mi sistema, yes aaaa
sucede que escribe tantas líneas como caben en un búfer de 8192 bytes de una sola vez. Como hay 5 bytes para escribir (4 caracteres imprimibles y la nueva línea), eso significa que escribe 8190 bytes cada vez. El tamaño del búfer de tubería es 4096 bytes. Por lo tanto, es posible obtener 4096 bytes de sí, luego algo de salida de grep y luego el resto de la escritura de sí (8190 - 4096 = 4094 bytes). 4096 bytes deja espacio para 819 líneas con aaaa
y un solitario a
. Por lo tanto, una línea con este solitario a
seguido de una escritura desde grep, dando una línea con abbbb
.
Si desea ver los detalles de lo que está sucediendo, getconf PIPE_BUF .
le indicará el tamaño del búfer de tubería en su sistema y podrá ver una lista completa de las llamadas al sistema realizadas por cada programa con
strace -s9999 -f -o line_buffered.strace sh -c '{ stdbuf -oL yes aaaa & while true; do echo bbbb; done | grep --line-buffered b & }' | head -n 999999 | grep -e ab -e ba
Cómo garantizar la intercalación de líneas limpias
Si las longitudes de línea son más pequeñas que el tamaño del búfer de tubería, entonces el búfer de línea garantiza que no habrá ninguna línea mixta en la salida.
Si las longitudes de línea pueden ser mayores, no hay forma de evitar mezclas arbitrarias cuando varios programas escriben en la misma tubería. Para garantizar la separación, debe hacer que cada programa escriba en una tubería diferente y usar un programa para combinar las líneas. Por ejemplo, GNU Parallel hace esto por defecto.