Как я могу отправить stdout одного процесса в несколько процессов, используя (предпочтительно безымянные) каналы в Unix (или Windows)?
Я хотел бы перенаправить stdout процесса proc1 на два процесса proc2 и proc3:
proc2 -> stdout
/
proc1
proc3 -> stdout
пробовал
proc1 | (proc2 & proc3)
но это, кажется, не работает, т. е.
echo 123 | (tr 1 a & tr 1 b)
пишет
b23
в stdout вместо
a23
b23
5 ответов:
Примечание редактора:
->(…)это подмена процесса что это нестандартные оболочки на некоторые POSIX-совместимые оболочки:bash,ksh,zsh.
- Как написано, ответ случайно отправляет вывод процесса замещения через конвейер слишком.
-echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/nullпредотвратил бы это, но у него есть подводные камни: выход из процесса подстановки будут непредсказуемо чередоваться, причем, кроме как вzsh, трубопровод может завершиться до команды внутри>(…)do.в unix (или на mac), используйте
teeкоманда:$ echo 123 | tee >(tr 1 a) | tr 1 b b23 a23как правило, вы должны использовать
teeдля перенаправления вывода на несколько файлов, но с помощью >(...) вы можете перенаправление на другой процесс. Так что, в общем,$ proc1 | tee >(proc2) ... >(procN-1) | procNбудет делать то, что вы хотите.
под windows, я не думаю, что встроенная оболочка имеет эквивалент. Microsoft Windows PowerShell есть хотя.
как сказал ДФ,
bashпозволяет использовать>(…)построить выполнение команды вместо имени файла. (Есть также<(…)построить, чтобы заменить выход другой команды вместо имени файла, но это не имеет значения сейчас, я упоминаю его только для полноты).если у вас нет bash или работает в системе со старой версией bash, вы можете сделать вручную то, что делает bash, используя файлы FIFO.
общий способ чтобы достичь того, что вы хотите, это:
- решите, сколько процессов должно получить результат вашей команды, и создайте столько FIFOs, предпочтительно в глобальной временной папке:
subprocesses="a b c d" mypid=$$ for i in $subprocesses # this way we are compatible with all sh-derived shells do mkfifo /tmp/pipe.$mypid.$i done
- запустите все ваши подпроцессы, ожидающие ввода из FIFOs:
for i in $subprocesses do tr 1 $i </tmp/pipe.$mypid.$i & # background! done
- выполнить команду teeing к FIFOs:
proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
- в конце концов, удалить Порта:
for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; doneпримечание: по соображениям совместимости, я бы сделал так
$(…)с обратными кавычками, но я не мог этого сделать, написав этот ответ (обратная кавычка используется в SO). Обычно,$(…)достаточно стар, чтобы работать даже в старых версиях ksh, но если это не так, заключите…часть в обратных кавычках.
поскольку @dF: упоминалось, что PowerShell имеет tee, я думал, что покажу способ сделать это в PowerShell.
PS > "123" | % { $_.Replace( "1", "a"), $_.Replace( "2", "b" ) } a23 1b3обратите внимание, что каждый объект, выходящий из первой команды, обрабатывается перед созданием следующего объекта. Это может позволить масштабировать до очень больших входных данных.
Unix (
bash,ksh,zsh)dF.'ы ответ содержит seed ответа на основе
teeи выходпроцесс замены
(>(...)), что может или не может работа, в зависимости от ваших требований:обратите внимание, что процесс замены являются нештатное функция, что (в основном) POSIX-функции-только оболочки, такие как
dash(который действует как/bin/shна Ubuntu, например), делать не поддержка. Сценарии оболочки таргетинга/bin/shдолжны не полагаться на них.echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/nullThe подводные камни этого подхода являются:
непредсказуемое, асинхронное поведение на выходе: выходные потоки из команд внутри подстановок выходного процесса
>(...)чередование в непредсказуемом пути.на
bashиksh(вместоzsh- но см. исключения ниже):
- вывод может прийти после команда закончила.
- последующие команды могут начать выполнение до команды в процессе замены завершены -
bashиkshdo не дождитесь подстановки выходного процесса-породил процессы для завершения, по крайней мере по умолчанию.- jmb ставит его хорошо в комментарии к dF.'ы ответ:
имейте в виду, что команды начали внутри
>(...)отделены от исходной оболочки, и вы не можете легко определить, когда они заканчиваются;teeзавершится после записи всего, но замещенные процессы все равно будут потреблять данные из различных буферов в ядре и файловом вводе / выводе, плюс любое время, которое занимает их внутренняя обработка данных. Вы можете столкнуться с условиями гонки, если ваша внешняя оболочка затем будет полагаться на все, что производится подпроцессами.
zshэто единственная оболочка, которая тут по умолчанию дождитесь завершения процессов, запущенных в заменах выходных процессов,за исключением если это stderr который перенаправляется на один (2> >(...)).
ksh(по крайней мере в версии93u+) позволяет использовать аргумент-меньшеwaitдождаться завершения процессов, порожденных замещением выходного процесса.
Обратите внимание, что в интерактивном сеансе, который может привести к ожиданию любого ожидающего фоновые задания тоже, однако.
bash v4.4+может подождать совсем недавно запустил процесс выхода замена наwait $!, но без аргументовwaitтут не работа, делая это непригодным для команды с несколько выходной процесс замены.- ,
bashиkshможет быть заставили ждать путем передачи команды| cat, но обратите внимание, что это делает команду Выполнить в subshell. предостережения:
ksh(по состоянию наksh 93u+) не поддерживает отправку stderr к подстановке выходного процесса (2> >(...)); такая попытка игнорируется.пока
zshэто (похвально) синхронно по умолчанию с (гораздо более общих) stdout выходные замены процесса, даже| catтехника не может сделать их синхронными с stderr замены процесса вывода (2> >(...)).- , даже если вы обеспечиваете синхронное исполнение проблема непредсказуемо чередуются выход остается.
следующая команда, при запуске в
bashилиksh, иллюстрирует проблемное поведение (возможно, вам придется запустить его несколько раз, чтобы увидеть и симптомы): ЭлементAFTERобычно печати до выход из выходных подстановок, а выход из последних может быть перемежен непредсказуемо.printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTERкороче:
гарантирование определенной последовательности вывода каждой команды:
- ни
bash, ниksh, ниzshподдерживаю.синхронно исполнение:
- выполнимо, за исключением с stderr - исходные замены процесса вывода:
- на
zsh, они всегда асинхронная.- на
ksh, они вообще не работают.если вы можете жить с этими ограничениями, использование подстановок процесса вывода является жизнеспособным вариантом (например, если все они записываются в отдельный вывод архив.)
обратите внимание, что tzot гораздо более громоздкое, но потенциально POSIX-совместимое решение также проявляет непредсказуемое поведение вывода, однако, с помощью
waitвы можете гарантировать, что последующие команды не начнут выполняться до завершения всех фоновых процессов.
внизу на более надежный, синхронный, сериализованный выход реализация.
только простой
bashрешение с предсказуемым поведением на выходе это следующее, что, однако,запредельно медленно с большими входными наборами, потому что петли оболочки по своей сути медленные.
Также обратите внимание, что это поперся выходные строки из целевых команд.while IFS= read -r line; do tr 1 a <<<"$line" tr 1 b <<<"$line" done < <(echo '123')
Unix (с помощью GNU Параллельно)
установка GNU
parallelпозволяет надежное решение с сериализованный (по команде) вывод, что дополнительно позволяет параллельного выполнения:$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b' a23 b23
parallelпо умолчанию гарантирует, что вывод из различных команд не перемежается (это поведение может быть изменено-см.man parallel).Примечание: некоторые дистрибутивы Linux поставляются с разные
parallelутилита, которая не будет работать с командой выше; использоватьparallel --versionчтобы определить, какой из них, если таковые имеются, у вас есть.
Windows
полезный ответ Джея Базузи показывает, как это делается в PowerShell. Тем не менее: его ответ является аналогом цикла
bashответ выше, это будет запредельно медленно с большими входными наборами и поперся выходные линии от целевые команды.
bash-основанное, но в остальном портативное решение Unix с синхронным выполнением и выходной сериализациейниже приводится простая, но достаточно надежная реализация подхода, представленного в tzot ответ, что дополнительно обеспечивает:
- синхронное исполнение
- сериализованный (сгруппированный) вывод
пока не строго POSIX уступчивый, потому что это
bashскрипт, он должен быть!--87-->переносится на любую платформу Unix, которая имеетbash.Примечание: Вы можете найти более полноценную реализацию выпущен под лицензией MIT в в этом суть.
если вы сохраните код ниже как скрипт
fanout, сделайте его исполняемым и поместите в свойPATHкоманда из вопрос будет работать следующим образом:$ echo 123 | fanout 'tr 1 a' 'tr 1 b' # tr 1 a a23 # tr 1 b b23
fanoutисходный скрипт код:#!/usr/bin/env bash # The commands to pipe to, passed as a single string each. aCmds=( "$@" ) # Create a temp. directory to hold all FIFOs and captured output. tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM" mkdir "$tmpDir" || exit # Set up a trap that automatically removes the temp dir. when this script # exits. trap 'rm -rf "$tmpDir"' EXIT # Determine the number padding for the sequential FIFO / output-capture names, # so that *alphabetic* sorting, as done by *globbing* is equivalent to # *numerical* sorting. maxNdx=$(( $# - 1 )) fmtString="%0${#maxNdx}d" # Create the FIFO and output-capture filename arrays aFifos=() aOutFiles=() for (( i = 0; i <= maxNdx; ++i )); do printf -v suffix "$fmtString" $i aFifos[i]="$tmpDir/fifo-$suffix" aOutFiles[i]="$tmpDir/out-$suffix" done # Create the FIFOs. mkfifo "${aFifos[@]}" || exit # Start all commands in the background, each reading from a dedicated FIFO. for (( i = 0; i <= maxNdx; ++i )); do fifo=${aFifos[i]} outFile=${aOutFiles[i]} cmd=${aCmds[i]} printf '# %s\n' "$cmd" > "$outFile" eval "$cmd" < "$fifo" >> "$outFile" & done # Now tee stdin to all FIFOs. tee "${aFifos[@]}" >/dev/null || exit # Wait for all background processes to finish. wait # Print all captured stdout output, grouped by target command, in sequences. cat "${aOutFiles[@]}"
другой способ сделать было бы,
eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`выход:
a23 b23нет необходимости создавать подоболочку здесь
Comments