¿Cómo puedes diferenciar dos tuberías en Bash?


¿Cómo puedes diferenciar dos pipelines sin usar archivos temporales en Bash? Digamos que tienes dos canalizaciones de comandos:

foo | bar
baz | quux

Y desea encontrar el diff en sus salidas. Una solución sería obviamente:

foo | bar > /tmp/a
baz | quux > /tmp/b
diff /tmp/a /tmp/b

Es posible hacerlo sin el uso de archivos temporales en Bash? Puede deshacerse de un archivo temporal canalizando en una de las canalizaciones a diff:

foo | bar > /tmp/a
baz | quux | diff /tmp/a -

Pero no se puede canalizar ambas tuberías en diff simultáneamente (no en cualquier obvio de manera, al menos). ¿Hay algún truco inteligente que involucre /dev/fd para hacer esto sin usar archivos temporales?

Author: Adam Rosenfield, 2008-12-06

3 answers

Una línea con 2 archivos tmp (no lo que quieres) sería:

 foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt

Con bash , usted podría intentar sin embargo:

 diff <(foo | bar) <(baz | quux)

 foo | bar | diff - <(baz | quux)  # or only use process substitution once

La segunda versión le recordará más claramente qué entrada fue cuál, al mostrar
-- /dev/stdin vs. ++ /dev/fd/63 o algo así, en lugar de dos fds numerados.


Ni siquiera una tubería con nombre aparecerá en el sistema de archivos, al menos en sistemas operativos donde bash puede implementar la sustitución de procesos mediante el uso de nombres de archivo como /dev/fd/63 para obtener un nombre de archivo que el comando puede open and read from para leer realmente desde un descriptor de archivo ya abierto que bash configuró antes de ejecutar el comando. (es decir, bash usa pipe(2) antes de la bifurcación, y luego dup2 para redirigir desde la salida de quux a un descriptor de archivo de entrada para diff, en fd 63.)

En un sistema sin "mágico" /dev/fd o /proc/self/fd, bash podría usar tuberías con nombre para implementar la sustitución de procesos, pero al menos las administraría por sí misma, a diferencia de los archivos temporales, y sus datos no se escribirían en el sistema de archivos.

Puede comprobar cómo bash implementa la sustitución de procesos con echo <(true) para imprimir el nombre del archivo en lugar de leerlo. Imprime /dev/fd/63 en un sistema Linux típico. O para obtener más detalles sobre exactamente qué llamadas al sistema usa bash, este comando en un sistema Linux rastreará las llamadas al sistema de archivos y descriptores de archivos

strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)'

Sin bash, podrías hacer una tubería con nombre . Use - para decirle a diff que lea una entrada desde STDIN, y use la tubería nombrada como los demás:

mkfifo file1_pipe.txt
foo|bar > file1_pipe.txt && baz | quux | diff file1_pipe.txt - && rm file1_pipe.txt

Tenga en cuenta que solo puede canalizar una salida a múltiples entradas con el comando tee:

ls *.txt | tee /dev/tty txtlist.txt 

El comando anterior muestra la salida de ls *.txt al terminal y lo envía al archivo de texto txtlist.txt.

Pero con la sustitución de procesos, puede usar tee para alimentar los mismos datos en múltiples canalizaciones:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar
 123
Author: VonC,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-08 04:05:16

En bash puede usar subcells, para ejecutar las canalizaciones de comandos individualmente, encerrando la canalización entre paréntesis. A continuación, puede prefijarlos con

Por ejemplo:

diff <(foo | bar) <(baz | quux)

Las tuberías anónimas con nombre son administradas por bash para que se creen y destruyan automáticamente (a diferencia de los archivos temporales).

 111
Author: BenM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-12-31 21:38:50

Algunas personas que llegan a esta página pueden estar buscando una diferencia línea por línea, para lo cual se debe usar comm o grep -f en su lugar.

Una cosa a señalar es que, en todos los ejemplos de la respuesta, las diferencias no se iniciarán hasta que ambas secuencias hayan terminado. Prueba esto con por ejemplo:

comm -23 <(seq 100 | sort) <(seq 10 20 && sleep 5 && seq 20 30 | sort)

Si esto es un problema, puede probar sd (stream diff), que no requiere ordenación (como comm hace) ni sustitución de procesos como los ejemplos anteriores, es órdenes o magnitude faster than grep -f and supports infinite streams.

El ejemplo de prueba que propongo se escribiría así en sd:

seq 100 | sd 'seq 10 20 && sleep 5 && seq 20 30'

Pero la diferencia es que seq 100 se diffed con seq 10 de inmediato. Tenga en cuenta que, si uno de los flujos es un tail -f, el diff no se puede hacer con la sustitución de procesos.

Aquí hay un blogpost Escribí sobre diffing streams en la terminal, que introduce sd.

 3
Author: mlg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-01 08:40:27