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, який перенаправляється на один ( 2> >(...)
).
ksh
(принаймні станом на версію 93u+
) дозволяє використовувати без аргументів wait
очікування завершення процесів, що породили заміщення вихідного процесу.
Зауважте, що в інтерактивному сеансі, що може призвести до очікування будь-яких фонових завдань , які очікують на розгляд, теж.
bash v4.4+
може чекати зовсім недавно запущеного процесу виведення заміщення wait $!
, але аргумент менше wait
робить НЕ роботу, що робить це невідповідним для команди з декількома замінами вихідного процесу.
Тим НЕ менше, bash
і ksh
може бути змушений чекати , перенаправивши команду | cat
, але зауважте , що це робить команду запуску в субоболочке . Застереження :
ksh
(станом на ksh 93u+
) не підтримує надсилання stderr для заміни вихідного процесу ( 2> >(...)
); така спроба мовчки ігнорується .
Хоча за замовчуваннямzsh
(похвально) синхронний із (набагато більш поширеними) підстановками вихідного процесу stdout , навіть техніка не може зробити їх синхронними із підстановками вихідного процесу stderr ( ).| cat
2> >(...)
Однак, навіть якщо ви забезпечуєте синхронне виконання , проблема непередбачувано перемежованого виводу залишається.
Наступна команда, коли запускається в bash
або ksh
, ілюструє проблемну поведінку (можливо, вам доведеться запустити її кілька разів, щоб побачити обидва симптоми): AFTER
Типово друкується перед виходом із вихідних підстановок, а вихід з останньої може бути непередбачувано переміщений.
printf 'line %s\n' {1..30} | tee >(cat -n) >(cat -n) >/dev/null; echo AFTER
Коротше :
Якщо ви можете жити з цими обмеженнями, використання підстановок вихідного процесу є життєздатним варіантом (наприклад, якщо всі вони записують у окремі вихідні файли).
Зверніть увагу, що набагато більш громіздке, але потенційно сумісне з POSIX рішення також демонструє непередбачувану поведінку на виході ; однак, використовуючи 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
.
Примітка: Ви можете знайти більш повноцінну реалізацію, випущену за ліцензією MIT, у цій статті .
Якщо ви збережете наведений нижче код як скрипт fanout
, зробите його виконуваним і введете int your PATH
, команда з запитання буде працювати наступним чином:
$ echo 123 | fanout 'tr 1 a' 'tr 1 b'
a23
b23
fanout
вихідний код сценарію :
#!/usr/bin/env bash
aCmds=( "$@" )
tmpDir="${TMPDIR:-/tmp}/$kTHIS_NAME-$$-$(date +%s)-$RANDOM"
mkdir "$tmpDir" || exit
trap 'rm -rf "$tmpDir"' EXIT
maxNdx=$(( $# - 1 ))
fmtString="%0${#maxNdx}d"
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
mkfifo "${aFifos[@]}" || exit
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
tee "${aFifos[@]}" >/dev/null || exit
wait
cat "${aOutFiles[@]}"