Как вы можете использовать два конвейера в Bash? - PullRequest
133 голосов
/ 06 декабря 2008

Как вы можете diff два конвейера без использования временных файлов в Bash? Скажем, у вас есть два командных конвейера:

foo | bar
baz | quux

И вы хотите найти diff в их выходах. Одно из решений, очевидно, было бы:

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

Возможно ли это сделать без использования временных файлов в Bash? Вы можете избавиться от одного временного файла, отправив в один из конвейеров команду diff:

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

Но вы не можете направить оба конвейера в diff одновременно (по крайней мере, не совсем очевидным образом). Есть ли какая-нибудь хитрая уловка с использованием /dev/fd, чтобы сделать это без использования временных файлов?

Ответы [ 3 ]

136 голосов
/ 06 декабря 2008

Однострочная с двумя файлами tmp (не то, что вы хотите) будет:

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

С bash вы можете попробовать:

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

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

2-я версия более четко напомнит вам, какой вход был какой, показывая
-- /dev/stdin против ++ /dev/fd/63 или что-то, вместо двух пронумерованных FDS.


Даже именованный канал не появится в файловой системе, по крайней мере, в операционных системах, где bash может реализовать подстановку процессов, используя имена файлов, такие как /dev/fd/63, чтобы получить имя файла, из которого команда может открывать и читать, чтобы фактически прочитать из уже открыть дескриптор файла, который настроил bash перед выполнением команды. (то есть bash использует pipe(2) перед fork, а затем dup2 для перенаправления с вывода quux на дескриптор входного файла для diff на fd 63.)

В системе без "магического" /dev/fd или /proc/self/fd bash может использовать именованные каналы для реализации подстановки процессов, но он по крайней мере будет управлять ими сам, в отличие от временных файлов, и ваши данные не будут записываться в файловую систему.

Вы можете проверить, как bash реализует подстановку процессов с помощью echo <(true), чтобы напечатать имя файла вместо чтения из него. Он печатает /dev/fd/63 в типичной системе Linux. Или для получения более подробной информации о том, какие именно системные вызовы использует bash, эта команда в системе Linux будет отслеживать системные вызовы файлов и дескрипторов файлов

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

Без bash вы можете создать именованную трубу . Используйте -, чтобы сказать diff, чтобы прочитать один ввод из STDIN, и использовать именованный канал в качестве другого:

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

Обратите внимание, что вы можете передать только один выход на несколько входов с помощью команды tee:

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

Приведенная выше команда отображает вывод ls * .txt на терминал и выводит его в текстовый файл txtlist.txt.

Но с заменой процесса вы можете использовать tee для подачи одних и тех же данных в несколько конвейеров:

cat *.txt | tee >(foo | bar > result1.txt)  >(baz | quux > result2.txt) | foobar
117 голосов
/ 06 декабря 2008

В bash вы можете использовать подоболочки, чтобы выполнять команды конвейеров индивидуально, заключая конвейер в круглые скобки. Затем вы можете поставить перед ними префикс <, чтобы создать анонимные именованные каналы, которые затем можно будет передать в diff. </p>

Например:

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

Анонимные именованные каналы управляются bash, поэтому они создаются и уничтожаются автоматически (в отличие от временных файлов).

5 голосов
/ 01 августа 2016

Некоторые люди, заходящие на эту страницу, могут искать построчную разность, для которой вместо нее следует использовать comm или grep -f.

Следует отметить, что во всех примерах ответа различия не начнутся до тех пор, пока не завершатся оба потока. Проверьте это, например:

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

Если это проблема, вы можете попробовать sd (stream diff), которая не требует сортировки (как это делает comm) и не выполняет подстановку, как в приведенных выше примерах, на порядок или на порядок быстрее чем grep -f и поддерживает бесконечные потоки.

Тестовый пример, который я предлагаю, будет написан следующим образом: sd:

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

Но разница в том, что seq 100 будет сразу же получено с seq 10. Обратите внимание, что если один из потоков является tail -f, diff не может быть выполнен с подстановкой процесса.

Вот blogpost Я писал о разнесении потоков на терминале, которое вводит sd.

...