it-swarm-ru.tech

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

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

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

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

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

89
Tim

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

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

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

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

$ 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. Подстановка процессов запускает команды, сохраняет их вывод в специальный временный файл и затем передает это имя файла вместо команды. Любая команда, которую вы используете, обрабатывает ее как имя файла. Обратите внимание, что созданный файл - это не обычный файл, а именованный канал, который удаляется автоматически, когда он больше не нужен.

140
Caleb

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

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

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, чтобы извлечь имена файлов, для которых была отображена ошибка "Отказано в доступе", а затем отправил результаты в файл.

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

26
tylerl

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

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

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

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

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

<(commands)

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

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

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

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

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

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

command3 < <(command1; command2)

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

command3 <(command1; command2)
26
enzotib

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

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

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

10
camh

Следует отметить, что подстановка процесса не ограничивается формой <(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.sh_

_#!/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 помимо того, что упомянуто выше, такой эквивалентной формы не существует, и в этом случае мы не можем использовать ничего вместо подстановки процесса (конечно, вы также можете использовать именованный канал или временный файл, но это уже другая история).

3
Weijun Zhou