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

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

         proc2 -> stdout
       /
 proc1
       \ 
         proc3 -> stdout

Я пытался

 proc1 | (proc2 & proc3)

но вроде не работает, т.е.

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

пишет

 b23

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

 a23
 b23

person secr    schedule 13.09.2008    source источник


Ответы (6)


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

person dF.    schedule 13.09.2008
comment
Большое Вам спасибо. Эта ›(...) - концепция для меня нова. Примечание. Похоже, что это не работает под Window cmd, PowerShell и даже под cygwin bash. - person secr; 14.09.2008
comment
Я не уверен, как это сделать в PowerShell ... Это не работает и в cygwin bash для меня, интересно, известное ли это ограничение или ошибка! - person dF.; 14.09.2008
comment
Это не конструкция POSIX и требует bash или ksh. Вам не повезло с tcsh, dash и т. Д. - person pixelbeat; 15.09.2008
comment
@pixelbeat:… но его можно разбить на конструкции POSIX (см. мой ответ :) - person tzot; 10.10.2008
comment
Я пробовал применить это там, где одно из направлений - стандартный вывод. Я придумал это: команда | тройник ›(кот -› ./log.txt). Интересно, есть ли способ лучше. - person Harvey; 04.05.2009
comment
Это не совсем то, что запрашивал @secr. tee добавит вывод перенаправления процесса в stdout перед его отправкой по конвейеру, что существенно отличается от конвейерной передачи одного и того же экземпляра stdout нескольким командам. @dF, например, echo 123 | tee >(tr 1 a) | tr 2 b приведет к 1b3 ab3, что не имеет смысла в контексте исходного вопроса. - person Dejay Clayton; 21.09.2011
comment
@Harvey Если я понимаю ваш вопрос: вы можете использовать command | tee log.txt, и он будет отправлять вывод в stdout и log.txt. tee записывает в стандартный вывод и файлы, >() трюк заключается в отправке в процесс. - person Matt Curtis; 15.04.2012
comment
Хотя это очень удобно, имейте в виду, что команды, запущенные внутри ›(...), отделены от исходной оболочки, и вы не можете легко определить, когда они заканчиваются; tee завершится после того, как все будет записано, но замещенные процессы по-прежнему будут потреблять данные из различных буферов ядра и файлового ввода-вывода, а также время, затрачиваемое на их внутреннюю обработку данных. Вы можете столкнуться с условиями гонки, если ваша внешняя оболочка будет полагаться на все, что создается подпроцессами. - person jmb; 27.12.2012
comment
Не работает, когда я пробовал это с большим примером: cat / usr / share / dict / words | тройник ›(голова -1) | хвост -1 тройник: / dev / fd / 63: сломана труба ZZZ - person naumcho; 01.04.2013
comment
@Dejay Clayton: Вы можете отказаться от исходного ввода, используя inproc | tee >(outproc1) >(outproc2) > /dev/null | outproc. outproc будет видеть только вывод, произведенный outproc1 и outproc2. Исходный ввод «пропал». - person ack; 15.07.2013
comment
Дополнение к вышесказанному: это работает, потому что подстановка процесса происходит до перенаправления (недокументированный AFAICT), поэтому outproc1 и outproc2 получают то, что раньше было tee 'собственным' stdout, прежде чем оно будет перенаправлено в битовое ведро. - person ack; 15.07.2013

Как сказал dF, 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
  • выполните команду teeing в 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, но если это не так, заключите часть в обратные кавычки.

person tzot    schedule 10.10.2008
comment
++ для отличного подхода, но вам следует использовать mkfifo, а не mknod, потому что только первый является POSIX-совместимым. Кроме того, использование подстановок команд без кавычек является хрупким, и есть потенциал для использования подстановки для повышения эффективности. В своем ответе я взял на себя смелость реализовать более надежное решение, хотя и основанное на bash. Обратите внимание, что $(…) уже долгое время является частью POSIX, поэтому я бы держался подальше от менее предсказуемого `…` (и SO определенно позволяет использовать ` в блоках кода и даже в строках кода (по крайней мере, сейчас :)) . - person mklement0; 15.05.2017
comment
Кажется, что сторона записи заблокируется, если один из процессов на стороне чтения перестанет потреблять (т.е. не запустится, умрет и т. Д.). Что нужно учитывать, думая о необходимой устойчивости вашего решения. - person Russell Speight; 02.01.2020

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, но см. Исключение ниже):

    • output may arrive after the command has finished.
    • последующие команды могут начать выполнение до завершения команд в процессе подстановок - 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 (похвально) синхронно по умолчанию с (гораздо более распространенными) подстановками процесса вывода stdout, даже метод | cat не может сделать их синхронными с stderr подстановки выходного процесса (2> >(...)).

  • Однако даже если вы обеспечите синхронное выполнение, проблема непредсказуемого чередования вывода останется.

Следующая команда при запуске в bash или ksh иллюстрирует проблемное поведение (возможно, вам придется запустить ее несколько раз, чтобы увидеть оба симптома): AFTER обычно печатается до выход из выходных подстановок, а выход из последних может чередоваться непредсказуемо.

printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER

Короче:

  • Гарантия определенной выходной последовательности для каждой команды:

    • Neither bash nor ksh nor zsh support that.
  • Синхронное исполнение:

    • Doable, except with stderr-sourced output process substitutions:
      • In zsh, they're invariably asynchronous.
      • В 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)

Установка GNU 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 с синхронным выполнением и сериализацией вывода

Ниже приводится простая, но достаточно надежная реализация подхода, представленного в ответе tzot, который дополнительно предоставляет:

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

Хотя это не совсем совместимость с POSIX, поскольку это bash скрипт, он должен быть переносимым на любую платформу Unix, имеющую bash.

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

Если вы сохраните приведенный ниже код как скрипт fanout, сделаете его исполняемым и поместите int свой 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[@]}"
person mklement0    schedule 11.05.2017

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

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

a23
1b3

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

person Jay Bazuzi    schedule 14.09.2008
comment
Да, но это эквивалент выполнения while IFS= read -r line; do tr 1 a <<<"$line"; tr 1 b <<<"$line"; done < <(echo '123') в Bash, который хорошо масштабируется по памяти, но не по производительности. - person mklement0; 11.05.2017

Вы также можете сохранить результат в переменной и использовать его для других процессов:

out=$(proc1); echo "$out" | proc2; echo "$out" | proc3

Однако это работает, только если

  1. proc1 в какой-то момент обрывается :-)
  2. proc1 не производит слишком большого объема вывода (не знаю, какие ограничения есть, но, вероятно, это ваша оперативная память)

Но его легко запомнить, и он оставляет вам больше возможностей для вывода, который вы получаете от процессов, которые вы там создали, например. г.:

out=$(proc1); echo $(echo "$out" | proc2) / $(echo "$out" | proc3) | bc

Мне было трудно сделать что-то подобное с подходом | tee >(proc2) >(proc3) >/dev/null.

person exic    schedule 04.06.2020

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

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

вывод:

a23
b23

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

person munish    schedule 14.05.2013
comment
На какой оболочке это работает? Все, что он делает, это eval echo 123 |{tr 1 a,tr 1 b}, который жалуется, что {tr не существует, и если вы добавите дополнительные пробелы, он ждет дополнительного ввода из-за запятой, и если вы измените запятую на точку с запятой или амперсанд, вы получите только первый напечатанный, а не оба. - person Jerry Jeremiah; 30.04.2014
comment
@JerryJeremiah: Он работает в оболочках, которые ограничивают расширение (bash, ksh, zsh), создавая командную строку echo '&& echo 123 |'{'tr 1 a','tr 1 b'} | sed -n 's/^&&//gp' в строке и затем передавая эту строку в eval. Тем не менее, он (а) создает 3 подоболочки в процессе построения строки (1 для `...` и 2 для сегментов встроенного конвейера, и (b), что более важно, он дублирует команда ввода, так что для каждой целевой команды tr запускается отдельная копия. - person mklement0; 10.05.2017