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


86

Мені було цікаво, як зрозуміти наступне:

Передача строки команди в stdin іншої - потужна техніка. Але що робити, якщо вам потрібно передати stdout з декількох команд? Тут відбувається заміна процесу.

Іншими словами, чи може процес заміщення робити все, що може зробити труба?

Що може зробити процес заміщення, але труба не може?

Відповіді:


133

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

Давайте скористаємося 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 <(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 < <(date)
<blank>

Оскільки ехо не читає STDIN і аргумент не передається, ми нічого не отримуємо.

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


Якщо я зрозумів по-справжньому, tldp.org/LDP/abs/html/process-sub.html#FTN.AEN18244 каже, що підміна процесу створює тимчасові файли, а не імена труб. Наскільки я знаю названих, не створюйте тимчасові файли. Запис на трубу ніколи не включає запис на диск: stackoverflow.com/a/6977599/788700
Adobe

Я знаю, що ця відповідь легітимна, бо вона використовує слово grok : 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)

так <() і <<() робить однаковий ефект, правда?
солянка

@solfish: not exacllty: firse можна використовувати там, де очікується ім'я файлу, другий - це перенаправлення вводу для цього імені файлу
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 для вилучення імен файлів, для яких відображалася помилка "Дозвіл відхилено", а потім надсилає ТИХ результати у файл.

Зауважте, що перша команда та перенаправлення stdout є в круглих дужках ( підпакет ), так що надсилається лише результат THAT команди, /dev/nullі він не псується з рештою рядка.


Варто відзначити , що в diffприкладі ви можете піклуватися про той випадок , коли cdможе завершитися невдачею: diff <(cd /foo/bar/ && ls) <(cd /foo/baz && ls).
phk

"while piping stderr": чи не сенс у тому, що це не трубопровід, а проходження файлу fifo?
Готьє

@Gauthier ні; команда замінюється не фіфою, а посиланням на дескриптор файлу. Отже, "echo <(echo)" повинен дати щось на кшталт "/ dev / fd / 63", який є спеціальним пристроєм символів, який читає або записує з FD номер 63.
tylerl

10

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

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

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


1
IIRC синтаксис <(команда) - це лише функція bash.
Філомат

@Philomath: Це теж у ZSH.
Калеб

Ну, у ZSH є все ... (або принаймні намагається).
Філомат

@Philomath: Як здійснюється заміна процесу в інших оболонках?
camh

4
@Philomath <(), як і багато передових функцій оболонки, спочатку був особливістю ksh і був прийнятий bash та zsh. psubспеціально рибна риса, нічого спільного з POSIX.
Жиль

3

Слід зазначити, що підміна процесу не обмежується формою <(command), яка використовує вихід у commandвигляді файлу. Він може бути у формі, >(command)яка також подає файл як вхідний файл command. Про це також згадується у цитаті баш-посібника у відповіді @ enzotib.

У date | catнаведеному вище прикладі команда, яка використовує процес підстановки форми >(command)для досягнення того ж ефекту,

date > >(cat)

Зауважте, що >попереднє >(cat)необхідно. Знову це можна наочно проілюструвати echoяк у відповіді @ Калеба.

$ 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окрім зазначеного вище, то такої еквівалентної форми не існує, і в такому випадку немає нічого, що ми можемо використати замість підстановки процесу (звичайно, ви також можете використовувати названий файл pipe або temp, але це інша розповідь).

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