Как можно использовать два конвейера разница без использования временных файлов в 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, чтобы сделать это без использования временных файлов?





Однострочный файл с двумя tmp-файлами (не то, что вы хотите) будет:
foo | bar > file1.txt && baz | quux > file2.txt && diff file1.txt file2.txt
С трепать вы можете попробовать:
diff <(foo | bar) <(baz | quux)
foo | bar | diff - <(baz | quux) # or only use process substitution once
Вторая версия будет более четко напоминать вам, какой ввод был какой, показывая -- /dev/stdin против ++ /dev/fd/63 или что-то в этом роде, вместо двух пронумерованных файловых дисков.
В файловой системе не появится даже именованный канал, по крайней мере, в операционных системах, где bash может реализовать подстановку процессов, используя такие имена файлов, как /dev/fd/63, чтобы получить имя файла, которое команда может открыть и прочитать, чтобы фактически прочитать из уже открытого файлового дескриптора, который bash настроить перед выполнением команды. (т.е. bash использует pipe(2) перед вилкой, а затем 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
Вы можете использовать обычный канал для одного из аргументов: pipeline1 | diff -u - <(pipeline2). Тогда результат будет более четко напоминать вам, какой вход был какой, показывая -- /dev/stdin против ++ /dev/fd/67 или что-то в этом роде, вместо двух пронумерованных файловых дисков.
подстановка процесса (foo <( pipe )) не изменяет файловую систему. Труба - анонимный; у него нет имени в файловой системе. Для его создания оболочка использует системный вызов pipe, а не mkfifo. Используйте strace -f -efile,desc,clone,execve bash -c '/bin/true | diff -u - <(/bin/true)' для отслеживания системных вызовов файлов и файловых дескрипторов, если хотите в этом убедиться. В Linux /dev/fd/63 является частью виртуальной файловой системы /proc; он автоматически содержит записи для каждого дескриптора файла и не является копией содержимого. Так что вы не можете называть это «временным файлом», если foo 3<bar.txt не считает
@PeterCordes Хорошие отзывы. Я включил ваш комментарий в ответ для большей наглядности.
Почему бы просто не исправить свой первый большой абзац, вместо того, чтобы оставлять ошибки и только вносить исправления? Обратите внимание, что Дэниел Кэссиди удалил свой ответ через год после публикации, предположительно потому, что он был неправильным.
@PeterCordes Я оставлю вам любые правки: это то, что делает Stack Overflow интересным: любой может "исправить" ответ.
Мне определенно нравится философия SO, заключающаяся в исправлении существующих ответов вместо того, чтобы всегда публиковать новые. Особенно, когда он уже принят и высоко оценен.
В bash вы можете использовать подоболочки для индивидуального выполнения командных конвейеров, заключив конвейер в круглые скобки. Затем вы можете добавить к ним префикс <, чтобы создать анонимные именованные каналы, которые затем можно передать в diff.
Например:
diff <(foo | bar) <(baz | quux)
Анонимные именованные каналы управляются bash, поэтому они создаются и уничтожаются автоматически (в отличие от временных файлов).
Намного более подробно, чем моя редакция того же решения - анонимной партии -. +1
В 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.
даже без bash вы можете использовать временный FIFO
mkfifo a; cmd >a& cmd2|diff a -; rm a