Digamos que ejecuto algunos procesos:
#!/usr/bin/env bash
foo &
bar &
baz &
wait;
Ejecuto el script anterior así:
foobarbaz | cat
Por lo que puedo decir, cuando cualquiera de los procesos escribe en stdout / stderr, su salida nunca se intercala: cada línea de stdio parece ser atómica. ¿Cómo funciona? ¿Qué utilidad controla cómo cada línea es atómica?
Respuestas:
¡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:
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
escribeaaaa
para siempre en lo que es esencialmente equivalente al modo de almacenamiento en línea. Layes
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
escribebbbb
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.
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:
stdbuf -o0
encuentra en GNU coreutils y algunos otros sistemas como FreeBSD. Alternativamente, puede cambiar a almacenamiento en línea constdbuf -oL
.unbuffer
. Algunos programas pueden comportarse de manera diferente de otras maneras, por ejemplo,grep
utiliza colores por defecto si su salida es un terminal.--line-buffered
a GNU grep.Veamos el fragmento de arriba otra vez, esta vez con almacenamiento en línea en ambos lados.
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 ybar
quiere escribir 3 bytes, entonces una posible intercalación es P bytes desdefoo
, luego 3 bytes desdebar
y P bytes desdefoo
.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 conaaaa
y un solitarioa
. Por lo tanto, una línea con este solitarioa
seguido de una escritura desde grep, dando una línea conabbbb
.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 conCó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.
fuente
cat
atómicamente, de modo que el proceso cat reciba líneas completas de foo / bar / baz pero no media línea de una y media línea de otra, etc. ¿Hay algo que pueda hacer con el script bash?awk
se produjeron dos (o más) líneas de salida para la misma ID,find -type f -name 'myfiles*' -print0 | xargs -0 awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
pero confind -type f -name 'myfiles*' -print0 | xargs -0 cat| awk '{ seen[$1]= seen[$1] $2} END { for(x in seen) print x, seen[x] }'
ella se produjo correctamente solo una línea para cada ID.http://mywiki.wooledge.org/BashPitfalls#Non-atomic_writes_with_xargs_-P ha analizado esto:
fuente
xargs echo
no llama al echo bash incorporado, sino a laecho
utilidad from$PATH
. Y de todos modos no puedo reproducir ese comportamiento bash echo con bash 4.4. Sin embargo, en Linux, las escrituras en una tubería (no / dev / null) más grande que 4K no se garantiza que sean atómicas.