Как я могу отправить стандартный вывод одного процесса нескольким процессам, используя (желательно безымянные) каналы в Unix (или Windows)?
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
команду, хотя.
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, но если это не так, заключите его в кавычки …
.
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
, но учтите, что это заставляет команду работать в подоболочке . Предостережения :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
, они не работают вообще .
- В
- Выполнимо, за исключением случаев замены процесса вывода из stderr :
Если вы можете жить с этими ограничениями, использование замен процесса вывода является жизнеспособным вариантом (например, если все они записывают в отдельные файлы вывода).
Обратите внимание, что гораздо более громоздкое, но потенциально 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Вопросы из категории :
- windows Эквивалент Windows «хороший»
- windows Реестр или INI-файл для хранения пользовательских настроек приложения
- windows Как я могу разработать для iPhone, используя машину для разработки Windows?
- windows Как настроить OpenFileDialog для выбора папок?
- bash Как разрешить символические ссылки в сценарии оболочки
- bash Удалить все, кроме самых последних файлов X в Bash
- bash Распараллелить скрипт Bash с максимальным количеством процессов
- bash Как мне написать цикл для Bash
- unix Использование X Window X Window Server через VPN
- unix Как вы делаете нечувствительный к регистру поиск с использованием модификатора шаблона, используя меньше?
- shell Вызов команд оболочки из Ruby
- shell Проверьте, существует ли каталог в сценарии оболочки
- shell Как я могу отправить стандартный вывод одного процесса нескольким процессам, используя (желательно безымянные) каналы в Unix (или Windows)?
- pipe Область видимости переменной Bash
- pipe Консоль C # получает ввод с конвейера
- pipe Как использовать subprocess.Popen для соединения нескольких процессов по каналам?