Как вы можете использовать два конвейера в Bash?
Как вы можете использовать два конвейера без использования временных файлов в 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 ответа
Одна строка с двумя файлами 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
Вторая версия более четко напомнит вам, какой вход был какой, показывая-- /dev/stdin
против ++ /dev/fd/63
или что-то, вместо двух пронумерованных FDS.
Даже именованный канал не появится в файловой системе, по крайней мере, в операционных системах, где bash может реализовать подстановку процессов, используя такие имена файлов, как /dev/fd/63
чтобы получить имя файла, из которого команда может открывать и читать, чтобы фактически прочитать из уже открытого файлового дескриптора, который bash настроил перед выполнением команды. (т.е. Bash использует pipe(2)
до развилки, а затем dup2
перенаправить с выхода quux
в дескриптор входного файла для diff
на фд 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
В bash вы можете использовать подоболочки, чтобы выполнять командные конвейеры индивидуально, заключая конвейер в круглые скобки. Затем вы можете поставить перед ними префикс <, чтобы создать анонимные именованные каналы, которые затем можно будет передать в diff.
Например:
diff <(foo | bar) <(baz | quux)
Анонимные именованные каналы управляются bash, поэтому они создаются и уничтожаются автоматически (в отличие от временных файлов).
Некоторые люди, попадающие на эту страницу, могут искать построчную разность, для которой 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
Разница не может быть сделано с заменой процесса.
Вот блог, который я написал о разносе потоков на терминале, который представляет sd
,