Как я могу отправить 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
580   5  

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/null

The подводные камни этого подхода являются:

  • непредсказуемое, асинхронное поведение на выходе: выходные потоки из команд внутри подстановок выходного процесса >(...) чередование в непредсказуемом пути.

  • на bash и ksh (вместо zsh - но см. исключения ниже):

    • вывод может прийти после команда закончила.
    • последующие команды могут начать выполнение до команды в процессе замены завершены -bash и ksh do не дождитесь подстановки выходного процесса-породил процессы для завершения, по крайней мере по умолчанию.
    • 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

    Ничего не найдено.