Unix (bash
, ksh
, zsh
)
Ответ dF. содержит начальное значение ответа на основе tee
и вывода обрабатывают замены
(>(...)
), которые могут или не могут em > работа в зависимости от ваших требований:
Обратите внимание, что подстановка процессов - это нестандартная функция, которая (в основном) оболочки только с функциями POSIX, например dash
(которая действует как /bin/sh
в Ubuntu, например), не em> поддержка. Сценарии оболочки, нацеленные на /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
- единственная оболочка, которая по умолчанию ожидает завершения процессов, запущенных в подстановках выходных процессов, кроме em >, если перенаправлен на stderr (2> >(...)
).
ksh
(по крайней мере, для версии 93u+
) позволяет использовать wait
без аргументов для ожидания завершения процессов, порожденных подстановкой выходного процесса.
Обратите внимание, что в интерактивном сеансе это может привести к ожиданию для любых ожидающих фоновых заданий тоже.
bash v4.4+
может дождаться замены последнего запущенного процесса вывода на wait $!
, но wait
без аргументов не работает, что делает это непригодным для команды с множественными заменами процесса вывода.
Однако bash
и ksh
можно принудительно ждать, передав команду по конвейеру на | cat
, но учтите, что это заставляет команду выполняться в подоболочка. Предостережения:
ksh
(по состоянию на ksh 93u+
) не поддерживает отправку stderr в подстановку процесса вывода (2> >(...)
); такая попытка молча игнорируется.
Хотя zsh
(похвально) синхронно по умолчанию с (гораздо более распространенными) подстановками процесса вывода stdout, даже метод | cat
не может сделать их синхронными с stderr em> подстановки выходного процесса (2> >(...)
).
Однако даже если вы обеспечите синхронное выполнение, проблема непредсказуемого чередования вывода останется.
Следующая команда при запуске в bash
или ksh
иллюстрирует проблемное поведение (возможно, вам придется запустить ее несколько раз, чтобы увидеть оба симптома): AFTER
обычно печатается до выход из выходных подстановок, а выход из последних может чередоваться непредсказуемо.
printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER
Короче:
Если вы можете жить с этими ограничениями, использование подстановок процесса вывода является жизнеспособным вариантом (например, если все они записываются в отдельные файлы вывода).
Обратите внимание, что tzot гораздо более громоздкое, но потенциально POSIX-совместимое решение также демонстрирует непредсказуемое поведение вывода; однако, используя wait
, вы можете гарантировать, что последующие команды не начнут выполняться, пока все фоновые процессы не будут завершены.
См. внизу для более надежной, синхронной реализации сериализованного вывода сильный>.
Единственное простое bash
решение с предсказуемым поведением вывода заключается в следующем, которое, однако, является чрезмерно медленным с большими наборами входных данных strong>, потому что циклы оболочки по своей сути медленные.
Также обратите внимание, что это чередует строки вывода целевых команд.
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