Как я могу отправить стандартный вывод одного процесса нескольким процессам, используя (желательно безымянные) каналы в Unix (или Windows)?

StackOverflow https://stackoverflow.com/questions/60942

Вопрос

Я хотел бы перенаправить стандартный вывод процесса proc1 на два процесса proc2 и proc3:

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

Я пытался

 proc1 | (proc2 & proc3)

но, похоже, это не работает, т.е.

 echo 123 | (tr 1 a & tr 1 b)

пишет

 b23

в стандартный вывод вместо

 a23
 b23
Это было полезно?

Решение

Примечание редактора:
- >(…) это замена процесса это нестандартная функция оболочки из некоторый POSIX-совместимые оболочки: bash, ksh, zsh.
- Этот ответ случайно отправляет выходные данные подстановки процесса через конвейер. слишком: echo 123 | tee >(tr 1 a) | tr 1 b.
- Выходные данные подстановок процесса будут непредсказуемо чередоваться, и, за исключением zsh, конвейер может завершиться до того, как команды внутри >(…) делать.

В Unix (или на Mac) используйте команду tee команда:

$ echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null
b23
a23

Обычно вы используете tee Чтобы перенаправить вывод на несколько файлов, но используя> (...), вы можете перенаправить в другой процесс.Итак, в общем,

$ proc1 | tee >(proc2) ... >(procN-1) >(procN) >/dev/null

сделаю то, что ты хочешь.

Я не думаю, что встроенная оболочка под Windows имеет эквивалент.Microsoft Windows PowerShell имеет tee команда однако.

Другие советы

Как сказал ДФ: bash позволяет использовать >(…) сконструируйте запуск команды вместо имени файла.(Также есть <(…) построить, чтобы заменить выход другой команды вместо имени файла, но сейчас это не имеет значения, я упоминаю об этом только для полноты картины).

Если у вас нет bash или вы работаете в системе с более старой версией bash, вы можете сделать то же, что и bash, вручную, используя файлы FIFO.

Общий способ достижения того, чего вы хотите:

  • решите, сколько процессов должны получить выходные данные вашей команды, и создайте столько же FIFO, желательно в глобальной временной папке:
    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
  • запустите все ваши подпроцессы, ожидающие ввода из FIFO:
    for i in $subprocesses
    do
        tr 1 $i </tmp/pipe.$mypid.$i & # background!
    done
  • выполните команду, соответствующую FIFO:
    proc1 | tee $(for i in $subprocesses; do echo /tmp/pipe.$mypid.$i; done)
  • наконец, удалите FIFO:
    for i in $subprocesses; do rm /tmp/pipe.$mypid.$i; done

ПРИМЕЧАНИЕ:из соображений совместимости я бы сделал $(…) с обратными кавычками, но я не смог этого сделать, написав этот ответ (обратная кавычка используется в SO).Обычно $(…) достаточно старый, чтобы работать даже в старых версиях ksh, но если это не так, приложите часть в обратных кавычках.

Юникс (bash, ksh, zsh)

Ответ dF. содержит семя ответа, основанного на tee и выход замены процессов
(>(...)) что может или не может работа в зависимости от ваших требований:

Обратите внимание, что замены процессов нестандартный характеризуется, что (в основном) только Posix-Features Shells, такие как dash (который действует как /bin/sh Например, на Ubuntu) нет поддерживать.Таргетинг на сценарии оболочки /bin/sh должен нет полагаться на них.

echo 123 | tee >(tr 1 a) >(tr 1 b) >/dev/null

А ловушки этого подхода являются:

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

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

    • вывод может прийти после команда завершилась.
    • последующие команды могут начать выполнение до команды в процессе замены завершились - bash и ksh делать нет дождитесь завершения процессов, порожденных подстановкой выходного процесса, по крайней мере, по умолчанию.
    • JMB хорошо выразил это в комментарии к ответу dF.:

имейте в виду, что команды начинаются внутри >(...) отделены от исходной оболочки, и вы не можете легко определить, когда они завершатся;тот tee завершит работу после того, как все запишет, но заменяемые процессы по-прежнему будут потреблять данные из различных буферов ядра и файлового ввода-вывода, а также время, затраченное на их внутреннюю обработку данных.Вы можете столкнуться с условиями гонки, если ваша внешняя оболочка продолжит полагаться на все, что создается подпроцессами.

  • zsh это единственная оболочка, которая делает по умолчанию дождитесь завершения процессов, запущенных в подстановках выходных процессов, кроме если это stderr который перенаправляется на один (2> >(...)).

  • ksh (по крайней мере, начиная с версии 93u+) позволяет использовать без аргументов wait дождаться завершения процессов, порожденных подстановкой выходного процесса.
    Обратите внимание, что в интерактивном сеансе это может привести к ожиданию любого ожидающего ответа. фоновые задания тоже, однако.

  • bash v4.4+ можно подождать Совсем недавно запустил подстановку процесса вывода с wait $!, но без аргументов wait делает нет работать, что делает это непригодным для команды с несколько замены процесса вывода.

  • Однако, bash и ksh возможно принужденный ждать передав команду | cat, но учтите, что при этом команда запускается в подоболочка. Предостережения:

    • ksh (по состоянию на ksh 93u+) не поддерживает отправку stderr к подстановке выходного процесса (2> >(...));такая попытка молча игнорируется.

    • Пока zsh (похвально) синхронно по умолчанию с (гораздо более распространенным) стандартный вывод замены процесса вывода, даже | 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 Parallel)

Установка ГНУ parallel позволяет надежное решение с сериализованный (для каждой команды) вывод что дополнительно позволяет параллельное выполнение:

$ echo '123' | parallel --pipe --tee {} ::: 'tr 1 a' 'tr 1 b'
a23
b23

parallel по умолчанию гарантирует, что вывод различных команд не будет чередоваться (это поведение можно изменить — см. man parallel).

Примечание:Некоторые дистрибутивы Linux поставляются с другой parallel утилита, которая не будет работать с приведенной выше командой;использовать parallel --version чтобы определить, какой из них у вас есть.


Окна

Полезный ответ Джея Базузи показывает, как это сделать в PowerShell.Это сказало:его ответ - аналог цикла bash ответ выше, так и будет непомерно медленный с большими наборами входных данных а также заменяет выходные строки целевых команд.



bashоснованное на переносимости решение Unix с синхронным выполнением и сериализацией вывода.

Ниже представлена ​​простая, но достаточно надежная реализация подхода, представленного в ответ Цота что дополнительно обеспечивает:

  • синхронное выполнение
  • сериализованный (сгруппированный) вывод

Хотя это и не строго соответствует POSIX, поскольку это bash сценарий, так и должно быть переносим на любую платформу 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[@]}"

Поскольку @dF:упомянул, что в PowerShell есть тройник, я подумал, что покажу, как сделать это в PowerShell.

PS > "123" | % { 
    $_.Replace( "1", "a"), 
    $_.Replace( "2", "b" ) 
}

a23
1b3

Обратите внимание, что каждый объект, выходящий из первой команды, обрабатывается перед созданием следующего объекта.Это может обеспечить масштабирование до очень больших входных данных.

другой способ сделать это:

 eval `echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp'`

выход:

a23
b23

нет необходимости создавать здесь подоболочку

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top