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
do not ждать процесса вывода порожденные замещением процессы, по крайней мере, по умолчанию.
- jmb хорошо описывает это в комментарии к ответу Д.Ф .:
имейте в виду, что команды, начатые внутри >(...)
, отделены от исходной оболочки, и вы не можете легко определить, когда они заканчиваются; tee
завершит работу после записи всего, но замещенные процессы будут по-прежнему потреблять данные из различных буферов в ядре и файловом вводе-выводе, а также все время, затрачиваемое на их внутреннюю обработку данных. Вы можете столкнуться с условиями гонки, если ваша внешняя оболочка затем будет полагаться на все, что создается подпроцессами.
zsh
является единственной оболочкой, которая по умолчанию ожидает завершения процессов, выполняемых в подстановках выходных процессов, , за исключением , если stderr перенаправлен на один (2> >(...)
).
ksh
(по крайней мере, начиная с версии 93u+
) позволяет использовать без аргументов wait
для ожидания завершения процессов, вызываемых подстановкой выходного процесса.
Обратите внимание, что в интерактивном сеансе это может привести к ожиданию любых ожидающих фоновых заданий , однако.
bash v4.4+
может подождать самую последнюю запущенную замену процесса вывода с wait $!
, но без аргументов wait
не не работать, делая это непригодным для команды с множественными заменами выходных процессов.
Однако, bash
и ksh
могут быть вынуждены ждать , отправив команду на | cat
, но учтите, что это заставляет команду работать в subshell . Предостережения
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
Короче :
Если вы можете жить с этими ограничениями, использование замен процесса вывода является жизнеспособным вариантом (например, если все они записывают в отдельные файлы вывода).
Обратите внимание, что гораздо более громоздкое, но потенциально POSIX-совместимое решение tzot также демонстрирует непредсказуемое поведение вывода ; однако, используя 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
, чтобы определить, какой из них у вас есть.
Windows
Полезный ответ Джея Базузи показывает, как это сделать в PowerShell . Тем не менее, его ответ аналогичен зацикленному bash
ответу выше, он будет непомерно медленным с большими входными наборами , а также чередует выходные строки из цели команды .
bash
на основе, но в остальном портативное решение Unix с синхронным выполнением и сериализацией вывода
Ниже приводится простая, но достаточно надежная реализация подхода, представленного в ответе tzot , который дополнительно обеспечивает:
- синхронное исполнение
- сериализованный (сгруппированный) выход
Хотя он не является строго POSIX-совместимым, поскольку это сценарий bash
, он должен быть переносимым на любую платформу Unix с bash
.
Примечание. Более полную реализацию, выпущенную по лицензии MIT, можно найти в this 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[@]}"