Процесс замещения и трубы

86

Мне было интересно, как понять следующее:

Передача стандартного вывода команды в стандартную команду другого является мощной техникой. Но что, если вам нужно передать стандартный вывод нескольких команд? Это где процесс замены приходит.

Другими словами, может ли процесс замещения делать то, что может делать канал?

Что может заменить процесс, а труба - нет?

Тим
источник

Ответы:

134

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

Давайте использовать dateкоманду для тестирования.

$ date | cat
Thu Jul 21 12:39:18 EEST 2011

Это бессмысленный пример, но он показывает, что catпринял вывод команды dateSTDIN и выплюнул ее обратно. Те же результаты могут быть достигнуты путем замены процесса:

$ cat <(date)
Thu Jul 21 12:40:53 EEST 2011

Однако то, что произошло за кадром, было другим. Вместо того, чтобы получить поток STDIN, catему фактически передали имя файла, который нужно было открыть и прочитать. Вы можете увидеть этот шаг, используя echoвместо cat.

$ echo <(date)
/proc/self/fd/11

Когда cat получил имя файла, он прочитал содержимое файла для нас. С другой стороны, echo просто показал нам имя файла, который был передан. Эта разница становится более очевидной, если вы добавите больше замен:

$ cat <(date) <(date) <(date)
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011
Thu Jul 21 12:44:45 EEST 2011

$ echo <(date) <(date) <(date)
/proc/self/fd/11 /proc/self/fd/12 /proc/self/fd/13

Можно объединить подстановку процесса (который генерирует файл) и перенаправление ввода (который подключает файл к STDIN):

$ cat < <(date)
Thu Jul 21 12:46:22 EEST 2011

Это выглядит примерно так же, но на этот раз кошке был передан поток STDIN вместо имени файла. Вы можете увидеть это, попробовав это с помощью echo:

$ echo < <(date)
<blank>

Поскольку echo не читает STDIN и аргумент не передается, мы ничего не получаем.

Каналы и входные данные перенаправляют содержимое в поток STDIN. Подстановка процессов запускает команды, сохраняет их вывод в специальный временный файл и затем передает это имя файла вместо команды. Любая команда, которую вы используете, обрабатывает ее как имя файла. Обратите внимание, что созданный файл - это не обычный файл, а именованный канал, который удаляется автоматически, когда он больше не нужен.

Калеб
источник
Если я правильно понял, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 говорит, что подстановка процесса создает временные файлы, а не именованные каналы. Насколько я знаю, по имени не создавать временные файлы. Запись в канал никогда не требует записи на диск: stackoverflow.com/a/6977599/788700
Adobe
Я знаю, что этот ответ является законным, потому что он использует слово « грок» : D
Aqn
2
@Adobe Вы можете подтвердить , производит ли замена временного процесса файла является именованным каналом с: [[ -p <(date) ]] && echo true. Это выдает, trueкогда я запускаю его с bash 4.4 или 3.2.
De Novo
24

Я должен предположить, что вы говорите о bashкакой-то другой расширенной оболочке, потому что оболочка posix не имеет подстановки процессов .

bash справочные страницы отчетов:

Подстановка процессов Подстановка
процессов поддерживается в системах, которые поддерживают именованные каналы (FIFO) или метод / dev / fd для именования открытых файлов. Он принимает форму <(список) или> (список). Список процессов запускается с его входом или выходом, подключенным к FIFO или некоторому файлу в / dev / fd. Имя этого файла передается в качестве аргумента текущей команде в результате расширения. Если используется форма> (список), запись в файл обеспечит ввод для списка. Если используется форма <(list), файл, переданный в качестве аргумента, следует прочитать, чтобы получить вывод списка.

Когда доступно, подстановка процесса выполняется одновременно с расширением параметров и переменных, подстановкой команд и арифметическим расширением.

Другими словами, и с практической точки зрения, вы можете использовать выражение, подобное следующему

<(commands)

в качестве имени файла для других команд, для которых в качестве параметра требуется файл. Или вы можете использовать перенаправление для такого файла:

while read line; do something; done < <(commands)

Возвращаясь к вашему вопросу, мне кажется, что процесс замещения и каналы имеют мало общего.

Если вы хотите последовательно передать вывод нескольких команд, вы можете использовать одну из следующих форм:

(command1; command2) | command3
{ command1; command2; } | command3

но вы также можете использовать перенаправление при замене процесса

command3 < <(command1; command2)

наконец, если command3принимает параметр файла (в подмену stdin)

command3 <(command1; command2)
enzotib
источник
поэтому <() и <<() дают одинаковый эффект, верно?
Solfish
@solfish: not exacllty: первый может использоваться везде, где ожидается имя файла, второе - перенаправление ввода для этого имени файла
enzotib
23

Вот три вещи, которые вы можете сделать с заменой процесса, которые невозможны в противном случае.

Несколько входов процесса

diff <(cd /foo/bar/; ls) <(cd /foo/baz; ls)

Там просто нет способа сделать это с трубами.

Сохранение STDIN

Скажем, у вас есть следующее:

curl -o - http://example.com/script.sh
   #/bin/bash
   read LINE
   echo "You said ${LINE}!"

И вы хотите запустить его напрямую. Следующее терпит неудачу с треском. Bash уже использует STDIN для чтения сценария, поэтому другой ввод невозможен.

curl -o - http://example.com/script.sh | bash 

Но этот способ работает отлично.

bash <(curl -o - http://example.com/script.sh)

Замена исходящего процесса

Также обратите внимание, что процесс подстановки работает и в другом направлении. Таким образом, вы можете сделать что-то вроде этого:

(ls /proc/*/exe >/dev/null) 2> >(sed -n \
  '/Permission denied/ s/.*\(\/proc.*\):.*/\1/p' > denied.txt )

Это немного запутанный пример, но он отправляет stdout во /dev/nullвремя передачи stderr сценарию sed для извлечения имен файлов, для которых была отображена ошибка «Permission denied», а затем отправляет результаты THOSE в файл.

Обратите внимание, что первая команда и перенаправление stdout заключены в круглые скобки ( подоболочка ), поэтому отправляется только результат команды THAT, /dev/nullи он не связывается с остальной частью строки.

tylerl
источник
Стоит отметить , что в diffпримере вы можете заботиться о том случае , когда cdможет завершиться неудачей: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
phk
«пока пиппинг stderr»: разве не в том, что это не пайпинг, а прохождение через файл fifo?
Готье
@ Готье нет; команда заменяется не fifo, а ссылкой на дескриптор файла. Таким образом, «echo <(echo)» должно выдавать что-то вроде «/ dev / fd / 63», которое представляет собой специальное символьное устройство, которое читает или записывает с FD номер 63.
tylerl
10

Если команда принимает список файлов в качестве аргументов и обрабатывает эти файлы как ввод (или вывод, но не часто), каждый из этих файлов может быть именованным каналом или псевдофайлом / dev / fd, прозрачно предоставляемым подстановкой процесса:

$ sort -m <(command1) <(command2) <(command3)

Это будет «передавать» выходные данные трех команд для сортировки, так как сортировка может принимать список входных файлов в командной строке.

CAMH
источник
1
IIRC синтаксис <(команда) является функцией только для bash.
Филомат
@Philomath: Это тоже в ZSH.
Калеб
Ну, у ZSH есть все ... (или, по крайней мере, пытается).
Филомат
@Philomath: Как осуществляется замена процесса в других оболочках?
Camh
4
@Philomath <(), как и многие расширенные функции оболочки, изначально был функцией ksh и был принят bash и zsh. psubэто особенность рыбы, никак не связанная с POSIX.
Жиль
3

Следует отметить, что процесс подстановки не ограничивается формой <(command), в которой выходные данные используются commandв виде файла. Это может быть в форме, >(command)которая также подает файл в качестве входных данных command. Это также упоминается в цитате из руководства bash в ответе @ enzotib.

Для date | catприведенного выше примера команда, использующая процесс подстановки формы >(command)для достижения того же эффекта, будет иметь вид:

date > >(cat)

Обратите внимание, что >перед >(cat)необходимо. Это снова может быть ясно проиллюстрировано, echoкак в ответе @ Caleb.

$ echo >(cat)
/dev/fd/63

Так что, без лишних >, date >(cat)будет то же самое, date /dev/fd/63что будет выводить сообщение на stderr.

Предположим , у вас есть программа , которая только принимает имена файлов в качестве параметров и не обрабатывать stdinили stdout. Я буду использовать упрощенный сценарий, psub.shчтобы проиллюстрировать это. Содержание psub.shIS

#!/bin/bash
[ -e "$1" -a -e "$2" ] && awk '{print $1}' "$1" > "$2"

В принципе, он проверяет , что оба его аргументы файлы (не обязательно обычные файлы) , и если это так, то напишите первое поле каждой строки "$1"с "$2"использованием AWK. Затем команда, которая объединяет все, что упомянуто до сих пор,

./psub.sh <(printf "a a\nc c\nb b") >(sort)

Это напечатает

a
b
c

и эквивалентно

printf "a a\nc c\nb b" | awk '{print $1}' | sort

но следующее не будет работать, и мы должны использовать здесь процесс подстановки,

printf "a a\nc c\nb b" | ./psub.sh | sort

или его эквивалентная форма

printf "a a\nc c\nb b" | ./psub.sh /dev/stdin /dev/stdout | sort

Если ./psub.shтакже читает stdinкроме того, что упомянуто выше, то такой эквивалентной формы не существует, и в этом случае мы ничего не можем использовать вместо подстановки процесса (конечно, вы также можете использовать именованный канал или временный файл, но это другое сказка).

Вейцзюнь Чжоу
источник