Как я могу отправить стандартный вывод нескольким командам?

187

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

Я знаком с OS X и так есть два , которые приходят на ум сразу же являются pbcopyи pbpaste- которые являются средством доступа к системному буферу.

Во всяком случае, я знаю, что если я хочу взять стандартный вывод и выплюнуть вывод, чтобы перейти к обоим stdoutи файл, то я могу использовать teeкоманду. И я немного знаю xargs, но не думаю, что это то, что я ищу.

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

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

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

Кроме того - я не спрашиваю, как отправить это в файл и stdout- это может быть «дублирующий» вопрос (извините), но я немного искал и смог найти только похожие вопросы, которые спрашивали о том, как разделить между stdout и файлом - и ответы на эти вопросы, казалось, были tee, что я не думаю, что будет работать для меня.

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

Да, и еще одна вещь - я понимаю, что мог бы использовать teeименованную трубу ( mkfifo), но я надеялся, что это можно будет сделать inline, сжато, без предварительной настройки :)

УХО
источник
Возможная Дубликат unix.stackexchange.com/questions/26964/...
Нихилу Mulley

Ответы:

241

Вы можете использовать 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видимому , не посылает выходной сигнал из выходного процесса замен через трубопровод (возможно, что это предпочтительнее , потому что он не загрязняет окружающую среды , что отправляется к следующему сегменту трубопровода - хотя это все еще печатает ). Однако во всех упомянутых оболочках, как правило, не очень хорошая идея иметь один конвейер, в котором обычные выходные данные stdout и выходные данные подстановок процессов смешаны - упорядочение вывода не будет предсказуемым, что может происходить только нечасто или с большими наборы выходных данных.
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", как вы хотите стандартный вывод по command3конвейеру tee, нет? Я проверил вашу версию dashи teeблокировал бесконечно, ожидая ввода, но переключение порядка дало ожидаемый результат.
Адриан Гюнтер
1
@ AdrianGünter Нет. Все три примера читают данные из стандартного ввода и отправляют их каждому из command, command2и command3.
Жиль
@ Жиль, я вижу, я неправильно истолковал намерение и попытался использовать фрагмент неправильно. Спасибо за разъяснения!
Адриан Гюнтер
Если у вас нет контроля над используемой оболочкой, но вы можете явно использовать 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/')
jimmij
источник
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)"
laebshade
источник
3
Где хранятся переменные? Если бы вы имели дело с большим файлом или чем-то в этом роде, разве это не заняло бы много памяти? Ограничены ли переменные по размеру?
cwd
1
что если $ commandoutput огромен ?, лучше использовать каналы и подстановку процессов.
Nikhil Mulley
4
Очевидно, что это решение возможно только в том случае, если вы знаете, что размер вывода легко помещается в памяти, и вы согласны с буферизацией всего вывода перед выполнением следующих команд на нем. Каналы решают эти две проблемы, предоставляя данные произвольной длины и передавая их в реальном времени получателю по мере их генерирования.
trr
2
Это хорошее решение, если у вас небольшой вывод и вы знаете, что вывод будет текстовым, а не двоичным. (переменные оболочки часто не являются бинарными)
Rucent88
1
Я не могу заставить это работать с двоичными данными. Я думаю, что это что-то с эхом, пытающимся интерпретировать нулевые байты или некоторые другие нехарактерные данные.
Рольф
1

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

sivann
источник
0

Вот быстрое частичное решение, совместимое с любой оболочкой, включая busybox.

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

  • Начните другой сеанс на том же хосте. Чтобы узнать его имя TTY, введите tty. Давайте предположим /dev/pty/2.
  • В первом сеансе запустите the_program | tee /dev/pty/2 | grep ImportantLog:

Вы получаете один полный журнал и один отфильтрованный.

Виктор Сергиенко
источник