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 ( ).| cat2> >(...)
Однак, навіть якщо ви забезпечуєте синхронне виконання , проблема непередбачувано перемежованого виводу залишається.
Наступна команда, коли запускається в 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[@]}"