Як я можу надсилати stdout до декількох команд?


186

Є деякі команди, які фільтрують або діють на вхід, а потім передають їх як вихід, але я думаю, що зазвичай stdout- але деякі команди просто приймуть stdinі робити все, що з ним робити, і нічого не вивести.

Я найбільше знайомий з ОС X, і тому є дві, які відразу ж приходять до тями, pbcopyі це pbpaste- засоби доступу до буфера обміну системи.

Так чи інакше, я знаю, що якщо я хочу взяти stdout і плюнути на вихід, щоб перейти до обох stdoutі файлів, то я можу використовувати teeкоманду. І я трохи знаю про це xargs, але не думаю, що це те, що я шукаю.

Я хочу знати, як я можу розділитись stdoutміж двома (або більше) командами. Наприклад:

cat file.txt | stdout-split -c1 pbcopy -c2 grep -i errors

Мабуть, є кращий приклад, ніж цей, але мені дуже цікаво знати, як я можу надіслати stdout до команди, яка не ретранслює його, і при цьому stdoutне перешкоджає "приглушенню" - я не запитую про те, catяк файл і grepчастину його та скопіюйте у буфер обміну - конкретні команди не так важливі.

Крім того - я не запитую, як надіслати це у файл і stdout- це може бути "дублікат" питання (вибачте), але я щось шукав і міг знайти лише подібні, які запитували про те, як розділити між stdout і файлом - і, здається, відповіді на ці запитання tee, які, на мою думку, не спрацюють для мене.

Нарешті, ви можете запитати "чому б просто не зробити pbcopy останньою справою в ланцюзі труб?" і моя відповідь 1) що робити, якщо я хочу його використовувати і все ще бачу вихід на консолі? 2) що робити, якщо я хочу використовувати дві команди, які не виводяться stdoutпісля того, як вони обробляють вхід?

О, і ще одне - я усвідомлюю, що міг би використати teeі названу трубу ( mkfifo), але сподівався на спосіб, як це можна зробити в прямому порядку, стисло, без попереднього налаштування :)


Можливий дублікат unix.stackexchange.com/questions/26964/…
Nikhil Mulley

Відповіді:


239

Ви можете використовувати teeта обробляти заміну для цього:

cat file.txt | tee >(pbcopy) | grep errors

Це надішле весь результат cat file.txtна pbcopy, і ви отримаєте результат лише grepна своїй консолі.

Ви можете розмістити декілька процесів у teeчастині:

cat file.txt | tee >(pbcopy) >(do_stuff) >(do_more_stuff) | grep errors

21
Чи не заклопотаність pbcopy, але варто відзначити , в цілому: які б заміни процесу виходів також розглядається наступний сегмент труби, після початкового введення; наприклад: seq 3 | tee >(cat -n) | cat -e( cat -nнумерує рядки введення, cat -eпозначає нові рядки $; ви побачите, що cat -eзастосовано як до початкового вводу (спочатку), так і (потім) виходу з cat -n). Вихід з декількох технологічних підстановок буде надходити в недетермінованому порядку.
mklement0

49
>(Працює тільки в bash. Якщо ви спробуєте це, наприклад, shвоно не вийде. Важливо зробити це повідомлення.
AAlvz

10
@AAlvz: Добре: підміна процесу не є функцією POSIX; dash, який діє як shна Ubuntu, не підтримує його, і навіть сам Bash деактивує функцію, коли викликається як shабо коли set -o posixдіє. Однак, не лише Bash підтримує процес заміни: kshі zshпідтримує їх теж (не впевнений у інших).
mklement0

2
@ mklement0, що здається неправдивим. На zsh (Ubuntu 14.04) ваш рядок друкує: 1 1 2 2 3 3 1 $ 2 $ 3 $ Що сумно, тому що я дуже хотів, щоб функціонал був таким, як ви це говорите.
Актау

2
@Aktau: Дійсно, моя вибіркова команда працює лише так, як описано в, bashі ksh- zshмабуть, не надсилає вихід із заміни вихідного процесу через конвеєр (можливо, це бажано , тому що не забруднює те, що надсилається в наступний сегмент конвеєра - хоча він досі друкує ). У всіх згаданих оболонках, однак, як правило, не годиться мати єдиний конвеєр, в якому регулярний вихід та вихідний процес із заміни процесів змішується - впорядкування виходу не буде передбачуваним таким чином, що може виникати лише нечасто або з великою поверхнею набори вихідних даних
mklement0

124

Ви можете вказати кілька імен файлів для tee, і крім того, стандартний вихід може бути об'єднаний в одну команду. Щоб відправити вихід на кілька команд, потрібно створити кілька труб і вказати кожну з них як один вихід tee. Існує кілька способів зробити це.

Процес заміщення

Якщо ваша оболонка - ksh93, bash або zsh, ви можете використовувати підстановку процесу. Це спосіб передати трубу команді, яка очікує імені файлу. Оболонка створює трубу і передає ім'я файлу на зразок /dev/fd/3команди. Номер - дескриптор файлу , до якого підключена труба. Деякі варіанти Unix не підтримують /dev/fd; на них замість цього використовується названа труба (див. нижче).

tee >(command1) >(command2) | command3

Дескриптори файлів

У будь-якій оболонці POSIX ви можете явно використовувати декілька дескрипторів файлів . Для цього потрібен варіант unix, який підтримує /dev/fd, оскільки всі, крім одного з виходів, teeповинні бути вказані по імені.

{ { { tee /dev/fd/3 /dev/fd/4 | command1 >&9;
    } 3>&1 | command2 >&9;
  } 4>&1 | command3 >&9;
} 9>&1

Названі труби

Найбільш основний і портативний метод - це використання названих труб . Мінусом є те, що вам потрібно знайти каталог, який можна записати, створити труби та очистити потім.

tmp_dir=$(mktemp -d)
mkfifo "$tmp_dir/f1" "$tmp_dir/f2"
command1 <"$tmp_dir/f1" & pid1=$!
command2 <"$tmp_dir/f2" & pid2=$!
tee "$tmp_dir/f1" "$tmp_dir/f2" | command3
rm -rf "$tmp_dir"
wait $pid1 $pid2

10
Дякую за надання двох альтернативних версій для тих, хто не хоче покладатися на bash чи певний ksh.
trr

tee "$tmp_dir/f1" "$tmp_dir/f2" | command3неодмінно повинно бути command3 | tee "$tmp_dir/f1" "$tmp_dir/f2", як ви хочете, щоб stdout command3труби tee, ні? Я перевіряв вашу версію dashі teeблокує нескінченно, чекаючи введення, але перемикання замовлення дало очікуваний результат.
Адріан Гюнтер

1
@ AdrianGünter No. Всі три приклади читання даних зі стандартного вводу і відправити його кожен з command, command2і command3.
Жиль

@Gilles Я бачу, я неправильно витлумачив наміри і намагався використовувати фрагмент неправильно. Дякуємо за роз’яснення!
Адріан Гюнтер

Якщо у вас немає контролю над використовуваною оболонкою, але ви можете використовувати bash явно, ви можете це зробити <command> | bash -c 'tee >(command1) >(command2) | command3'. Це допомогло в моєму випадку.
gc5

16

Просто пограйте з заміною процесу.

mycommand_exec |tee >(grep ook > ook.txt) >(grep eek > eek.txt)

grep- це два бінарні файли, які мають такий самий вихід, mycommand_execяк і конкретний вхід для процесу.


16

Якщо ви користуєтесь, zshви можете скористатися потужністю MULTIOSфункції, тобто teeповністю позбутися команди:

uname >file1 >file2

буде просто записати вихід unameдвох різних файлів: file1і file2, що еквівалентноuname | tee file1 >file2

Аналогічно перенаправлення стандартних входів

wc -l <file1 <file2

еквівалент cat file1 file2 | wc -l(зауважте, що це не те саме wc -l file1 file2, що пізніше рахує кількість рядків у кожному файлі окремо).

Звичайно, ви також можете використовувати MULTIOSдля перенаправлення виводу не на файли, а на інші процеси, використовуючи підстановку процесу, наприклад:

echo abc > >(grep -o a) > >(tr b x) > >(sed 's/c/y/')

3
Добре знати. MULTIOS- це параметр, який за замовчуванням увімкнено (і його можна вимкнути unsetopt MULTIOS).
mklement0

6

Для досить невеликого результату, отриманого командою, ми можемо перенаправити вихід у тимчасовий файл і надіслати цей тимчасовий файл командам у циклі. Це може бути корисно, коли порядок виконання команд може мати значення.

Наступний сценарій, наприклад, може зробити це:

#!/bin/sh

temp=$( mktemp )
cat /dev/stdin > "$temp"

for arg
do
    eval "$arg" < "$temp"
done
rm "$temp"

Тест працює на Ubuntu 16.04 з , /bin/shяк dashоболонки:

$ cat /etc/passwd | ./multiple_pipes.sh  'wc -l'  'grep "root"'                                                          
48
root:x:0:0:root:/root:/bin/bash

5

Захопіть команду STDOUTзмінною і повторно використовуйте її скільки завгодно разів:

commandoutput="$(command-to-run)"
echo "$commandoutput" | grep -i errors
echo "$commandoutput" | pbcopy

Якщо вам потрібно також зробити захоплення STDERR, тоді 2>&1в кінці команди використовуйте так:

commandoutput="$(command-to-run 2>&1)"

3
Де зберігаються змінні? Якби ви мали справу з великим файлом чи чимось подібним, чи не принесла б це багато пам’яті? Чи обмежені розміри змінних?
cwd

1
що робити, якщо $ commandoutput є величезним ?, то краще використовувати труби та замінити процес.
Нікхіл Маллі

4
Очевидно, що це рішення можливе лише тоді, коли ви знаєте, що розмір виводу легко впишеться в пам’ять, і ви все в порядку з буферизацією всього виходу, перш ніж виконувати наступні команди на ньому. Труби вирішують ці дві проблеми, дозволяючи дані довільної довжини та передаючи їх у режимі реального часу до приймача по мірі їх генерування.
trr

2
Це хороше рішення, якщо у вас невеликий вихід, і ви знаєте, що вихід буде текстовим, а не двійковим. (змінні оболонки часто не є бінарними)
Rucent88

1
Я не можу змусити це працювати з бінарними даними. Я думаю, що це щось із луною, що намагається інтерпретувати нульові байти чи інші нехарактерні дані.
Рольф

1

Це може бути корисно : http://www.spinellis.gr/sw/dgsh/ (спрямована оболонка графіка) Схоже, що заміна bash підтримує більш простий синтаксис для команд "multipipe".


0

Ось швидке і брудне часткове рішення, сумісне з будь-якою оболонкою, включаючи busybox.

Більш вузька проблема, яку вона вирішує: надрукуйте повний stdoutна одній консолі та відфільтруйте її на іншій, без тимчасових файлів або названих труб.

  • Почніть ще один сеанс того ж хоста. Щоб дізнатися його назву TTY, введіть tty. Припустимо /dev/pty/2.
  • На першому сеансі запустіть the_program | tee /dev/pty/2 | grep ImportantLog:

Ви отримуєте один повний журнал і відфільтрований.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.