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

windows bash unix shell pipe

24854 просмотра

5 ответа

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

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

Я пытался

 proc1 | (proc2 & proc3)

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

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

пишет

 b23

вытащить вместо

 a23
 b23
Автор: secr Источник Размещён: 11.11.2019 03:39

Ответы (5)


121 плюса

Решение

Примечание редактора :
- >(…)это процесс замещение , что является нестандартной особенностью оболочки из некоторых 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 PowerShell от Microsoft имеет teeкоманду, хотя.

Автор: dF. Размещён: 13.09.2008 10:37

22 плюса

Как сказал dF, bashпозволяет использовать >(…)конструкцию, выполняющую команду вместо имени файла. (Существует также <(…)конструкция для замены вывода другой команды вместо имени файла, но сейчас это не имеет значения, я упоминаю это только для полноты).

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

Общий способ достичь того, что вы хотите, это:

  • решить, сколько процессов должно получить выходные данные вашей команды, и создать столько FIFO, предпочтительно в глобальной временной папке:
    подпроцессы = "abc d"
    mypid = $$
    для i в $ subprocesses # таким образом мы совместимы со всеми производными от sh оболочками  
    делать
        mkfifo /tmp/pipe.$mypid.$i
    сделанный
  • запустите все ваши подпроцессы, ожидающие ввода от FIFO:
    для меня в $ подпроцессов
    делать
        tr 1 $ i 

  • выполнить вашу команду, отправляя FIFO:
    proc1 | tee $ (для i в $ subprocesses; сделать echo /tmp/pipe.$mypid.$i; готово)
  • наконец, удалите FIFO:
    для я в $ подпроцессов; do rm /tmp/pipe.$mypid.$i; сделанный

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

Автор: tzot Размещён: 10.10.2008 10:43

8 плюса

Unix ( bash, ksh, zsh)

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

Обратите внимание, что подстановки процессов - это нестандартная функция, которая (в основном) оболочек только для POSIX-функций, таких как 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, который перенаправляется в one ( 2> >(...)).

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

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

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

    • kshksh 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, они не работают вообще .

Если вы можете жить с этими ограничениями, использование замен процесса вывода является жизнеспособным вариантом (например, если все они записывают в отдельные файлы вывода).


Обратите внимание, что гораздо более громоздкое, но потенциально POSIX-совместимое решение tzot также демонстрирует непредсказуемое поведение вывода ; однако, используя его, waitвы можете убедиться, что последующие команды не начнут выполняться, пока не завершатся все фоновые процессы.
Смотрите внизу для более надежной, синхронной реализации сериализованного вывода .


Единственное простое bash решение с предсказуемым выходным поведением заключается в следующем, что, однако, является чрезмерно медленным с большими входными наборами , потому что циклы оболочки по своей природе медленные.
Также обратите внимание, что это чередует выходные строки из целевых команд .

while IFS= read -r line; do 
  tr 1 a <<<"$line"
  tr 1 b <<<"$line"
done < <(echo '123')

Unix (с использованием GNU Parallel)

Установка GNUparallel обеспечивает надежное решение с сериализованным выводом (для каждой команды), которое дополнительно допускает параллельное выполнение :

$ 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скрипт, он должен быть переносимым на любую платформу Unix, которая имеетbash .

Примечание: в этом Gist вы можете найти более полноценную реализацию, выпущенную под лицензией MIT .

Если вы сохраните приведенный ниже код как скрипт fanout, сделаете его исполняемым и поместите int your 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[@]}"
Автор: mklement0 Размещён: 11.05.2017 04:51

5 плюса

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

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

a23
1b3

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

Автор: Jay Bazuzi Размещён: 14.09.2008 11:16

-1 плюса

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

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

выход:

a23
b23

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

Автор: munish Размещён: 14.05.2013 11:23
Вопросы из категории :
32x32