Відповіді:
спільні процеси - це ksh
особливість (вже є ksh88
). zsh
ця функція мала ще з початку (на початку 90-х), тоді як вона лише була додана bash
у 4.0
(2009).
Однак поведінка та інтерфейс значно відрізняються між 3 оболонками.
Однак ідея така ж: вона дозволяє запустити роботу у фоновому режимі та можливість відправити його на вхід та прочитати його вихід, не вдаючись до названих труб.
Це робиться з неназваними трубами з більшістю оболонок і socketpairs з останніми версіями ksh93 в деяких системах.
В a | cmd | b
, a
подає дані cmd
і b
читає їх вихід. Запуск cmd
як спільний процес дозволяє оболонці бути і обома, a
і b
.
В ksh
, ви починаєте coprocess як:
cmd |&
Ви живите дані cmd
, виконуючи такі дії:
echo test >&p
або
print -p test
І читайте cmd
результати з такими речами, як:
read var <&p
або
read -p var
cmd
запускається як будь-який фонове завдання, ви можете використовувати fg
, bg
, kill
на ньому і передати його %job-number
або через $!
.
Щоб закрити кінець запису в кінці труби cmd
, можна зробити:
exec 3>&p 3>&-
І щоб закрити кінець зчитування другої труби (в яку cmd
пишеться):
exec 3<&p 3<&-
Ви не можете запустити другий спільний процес, якщо ви спочатку не збережете дескриптори файлів труб на деяких інших FDS. Наприклад:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
В zsh
, спільні процеси майже ідентичні тим, що в ksh
. Єдина реальна відмінність полягає в тому, що zsh
спільні процеси починаються з coproc
ключового слова.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Виконуємо:
exec 3>&p
Примітка. Це не переміщує coproc
дескриптор файлу в fd 3
(як у ksh
), а дублює його. Отже, немає явного способу закрити подачу або зчитування, інше починаючи інше coproc
.
Наприклад, щоб закрити кінець подачі:
coproc tr a b
echo aaaa >&p # send some data
exec 4<&p # preserve the reading end on fd 4
coproc : # start a new short-lived coproc (runs the null command)
cat <&4 # read the output of the first coproc
На додаток до спільних процесів на основі труб zsh
(з 3.1.6-dev19, випущений у 2000 році) є псевдо-тти конструкції на зразок expect
. Для взаємодії з більшістю програм спільні процеси в стилі ksh не працюватимуть, оскільки програми починають буферизуватись, коли їх вихід - це труба.
Ось кілька прикладів.
Почніть спільний процес x
:
zmodload zsh/zpty
zpty x cmd
(Тут cmd
проста команда. Але ви можете робити химерні речі за допомогою eval
або функцій.)
Подайте дані про спільний процес:
zpty -w x some data
Прочитайте дані спільної обробки (у найпростішому випадку):
zpty -r x var
Мовляв expect
, він може зачекати деякого результату спільного процесу, який відповідає заданому шаблону.
Синтаксис bash набагато новіший і будується на основі нової функції, нещодавно доданої до ksh93, bash та zsh. Він надає синтаксис, що дозволяє обробляти динамічно виділені дескриптори файлів вище 10.
bash
пропонує основний coproc
синтаксис і розширений .
Основний синтаксис для запуску спільного процесу виглядає як zsh
s:
coproc cmd
У ksh
або zsh
до труб, що перебувають під час спільного процесу, і з них, можна отримати доступ до >&p
та <&p
.
Але в bash
, дескриптори файлів труби від спільного процесу та іншої труби до спільного процесу повертаються в $COPROC
масив (відповідно ${COPROC[0]}
і ${COPROC[1]}
. Отже ...
Подайте дані до спільного процесу:
echo xxx >&"${COPROC[1]}"
Прочитайте дані спільного процесу:
read var <&"${COPROC[0]}"
За допомогою базового синтаксису ви можете запустити лише один спільний процес одночасно.
У розширеному синтаксисі ви можете назвати ваші спільні процеси (наприклад, у zsh
zpty спільних процесах):
coproc mycoproc { cmd; }
Команда має бути складовою командою. (Зверніть увагу, як нагадує наведений вище приклад function f { ...; }
.)
Цього разу дескриптори файлів знаходяться у ${mycoproc[0]}
та ${mycoproc[1]}
.
Ви можете почати більш ніж один процес співпраці в той час , але ви робите отримати попередження при запуску спільного процесу , а один все ще працює (навіть в не інтерактивному режимі).
Дескриптори файлів можна закрити, використовуючи розширений синтаксис.
coproc tr { tr a b; }
echo aaa >&"${tr[1]}"
exec {tr[1]}>&-
cat <&"${tr[0]}"
Зауважте, що закриття цього способу не працює в bash-версіях до 4.3, де потрібно замість цього записати:
fd=${tr[1]}
exec {fd}>&-
Як у, так ksh
і в zsh
цих дескрипторах файлових файлів позначаються як "close-on-exec".
Але bash
єдиний спосіб передати ті виконувані командам , щоб дублювати їх FDS 0
, 1
, або 2
. Це обмежує кількість спільних процесів, з якими можна взаємодіяти для однієї команди. (Див. Приклад нижче.)
yash
не має функції спільного процесу по суті, але та ж концепція може бути реалізована з її трубопровідної і процесом перепрофілювання функцій. yash
має інтерфейс до pipe()
системного виклику, тому подібні речі можна зробити вручну порівняно легко.
Ви б розпочали спільний процес із:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Котрий спочатку створює pipe(4,5)
(5 записуючий кінець, 4 кінець читання), потім перенаправляє fd 3 до труби до процесу, який працює зі своїм stdin на іншому кінці, а stdout йде до створеної раніше труби. Тоді ми закриваємо кінець запису тієї труби в батьківській програмі, яка нам не потрібна. Отже, в оболонці у нас є fd 3, підключений до stdin cmd і fd 4, з'єднаний з stdout cmd за допомогою труб.
Зауважте, що прапор закриття на exec не встановлений для цих дескрипторів файлів.
Для подачі даних:
echo data >&3 4<&-
Щоб прочитати дані:
read var <&4 3>&-
І ви можете закрити файли, як зазвичай:
exec 3>&- 4<&-
Спільні процеси можна легко здійснити за допомогою стандартних названих труб. Я не знаю, коли саме були введені названі труби, але можливо, це було після ksh
спільних процесів (ймовірно, в середині 80-х, ksh88 був "випущений" у 88, але я вважаю, що ksh
він використовувався внутрішньо в AT&T за кілька років до цього те), що пояснило б, чому.
cmd |&
echo data >&p
read var <&p
Можна записати:
mkfifo in out
cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4
Взаємодія з ними простіша - особливо, якщо вам потрібно запустити більше одного спільного процесу. (Див. Приклади нижче.)
Єдиною перевагою використання coproc
є те, що вам не доведеться очищати названі труби після використання.
Снаряди використовують труби в кількох конструкціях:
cmd1 | cmd2
,$(cmd)
,<(cmd)
, >(cmd)
.У них дані протікають лише в одному напрямку між різними процесами.
Зі спільними процесами та назвами труб, однак, легко потрапити в глухий кут. Ви повинні відслідковувати, яка команда має дескриптор файлу відкритим, щоб не залишатись відкритим і тримати процес живим. Тупики можуть бути складними у дослідженні, оскільки вони можуть виникати недетерміновано; наприклад, лише тоді, коли надсилається стільки даних, скільки можна заповнити одну трубу.
expect
для того, для чого він призначенийОсновна мета спільних процесів полягала в наданні оболонці способу взаємодії з командами. Однак це працює не так добре.
Найпростіша форма тупика, згадана вище:
tr a b |&
echo a >&p
read var<&p
Оскільки його вихід не йде до терміналу, tr
буферизує його вихід. Таким чином, він нічого не виведе, поки або не побачить кінець файлу на своєму stdin
, або він накопичив повний буфер даних для виводу. Отже вище, після отримання оболонки a\n
(лише 2 байти), read
блок буде блокуватися нескінченно, оскільки tr
чекає, коли оболонка надішле їй більше даних.
Коротше кажучи, труби не підходять для взаємодії з командами. Спільні процеси можуть використовуватися лише для взаємодії з командами, які не блокують їх вихід, або командами, яким можна сказати не буферувати свій вихід; наприклад, за допомогою stdbuf
деяких команд в останніх системах GNU або FreeBSD.
Ось чому expect
або zpty
використовуйте натомість псевдотермінали. expect
це інструмент, призначений для взаємодії з командами, і він робить це добре.
Спільні процеси можна використовувати для виконання більш складної сантехніки, ніж те, що дозволяють прості корпусні труби.
інша відповідь Unix.SE містить приклад використання копроку.
Ось спрощений приклад. Уявіть, що ви хочете, щоб функція, яка подає копію виводу команди на 3 інші команди, а потім отримала з’єднання цих 3 команд.
Всі використовують труби.
Наприклад: годувати вихід printf '%s\n' foo bar
до tr a b
, sed 's/./&&/g'
і , cut -b2-
щоб отримати що - щось на кшталт:
foo
bbr
ffoooo
bbaarr
oo
ar
По-перше, це не обов’язково очевидно, але існує можливість тупикової ситуації, і це почне відбуватися лише через кілька кілобайт даних.
Тоді, залежно від вашої оболонки, ви зіткнетеся з низкою різних проблем, які потрібно вирішити по-різному.
Наприклад, з zsh
, ви зробите це з:
f() (
coproc tr a b
exec {o1}<&p {i1}>&p
coproc sed 's/./&&/g' {i1}>&- {o1}<&-
exec {o2}<&p {i2}>&p
coproc cut -c2- {i1}>&- {o1}<&- {i2}>&- {o2}<&-
tee /dev/fd/$i1 /dev/fd/$i2 >&p {o1}<&- {o2}<&- &
exec cat /dev/fd/$o1 /dev/fd/$o2 - <&p {i1}>&- {i2}>&-
)
printf '%s\n' foo bar | f
Наверху, у файлах f-файлів для спільного процесу встановлений прапор close-on-exec, але не той, який дублюється з них (як у {o1}<&p
). Отже, щоб уникнути тупикових ситуацій, вам доведеться переконатися, що вони закриті в будь-яких процесах, які їм не потрібні.
Аналогічно, ми повинні використовувати нижню exec cat
оболонку і використовувати в кінцевому підсумку, щоб переконатися, що немає жодного процесу оболонки, що лежить про тримання труби відкритою.
З ksh
(тут ksh93
), це повинно бути:
f() (
tr a b |&
exec {o1}<&p {i1}>&p
sed 's/./&&/g' |&
exec {o2}<&p {i2}>&p
cut -c2- |&
exec {o3}<&p {i3}>&p
eval 'tee "/dev/fd/$i1" "/dev/fd/$i2"' >&"$i3" {i1}>&"$i1" {i2}>&"$i2" &
eval 'exec cat "/dev/fd/$o1" "/dev/fd/$o2" -' <&"$o3" {o1}<&"$o1" {o2}<&"$o2"
)
printf '%s\n' foo bar | f
( Примітка. Це не буде працювати в системах, де ksh
використовується socketpairs
замість pipes
, і де /dev/fd/n
працює, як у Linux.)
У ksh
, fds вище 2
позначені прапором close-on-exec, якщо тільки вони не передаються явно в командному рядку. Ось чому нам не потрібно закривати дескриптори невикористаних файлів, як, наприклад, zsh
- але це також, чому ми повинні робити {i1}>&$i1
і використовувати eval
для цього нового значення $i1
, яке буде передано tee
та cat
...
У bash
цьому не може бути зроблено, тому що ви не можете уникнути закрити при ехесе прапора.
Вище це порівняно просто, тому що ми використовуємо лише прості зовнішні команди. Це ускладнюється, коли ви хочете замість цього використовувати конструкції оболонок, і ви починаєте працювати з помилками оболонок.
Порівняйте вищезгадане з тим же, використовуючи названі труби:
f() {
mkfifo p{i,o}{1,2,3}
tr a b < pi1 > po1 &
sed 's/./&&/g' < pi2 > po2 &
cut -c2- < pi3 > po3 &
tee pi{1,2} > pi3 &
cat po{1,2,3}
rm -f p{i,o}{1,2,3}
}
printf '%s\n' foo bar | f
Якщо ви хочете взаємодіяти з командою, використовуйте expect
, або zsh
s zpty
, або з назвою pipe.
Якщо ви хочете зробити сантехніку з трубами, використовуйте названі труби.
Спільні процеси можуть виконати щось з вищезазначеного, але будьте готові зробити серйозні подряпини по голові за що-небудь нетривіальне.
exec {tr[1]}>&-
Насправді, схоже, він працює з новішими версіями і на нього посилається у записі CWRU / changelog ( дозвольте таким словам, як {array [ind]}, бути дійсним перенаправленням ... 2012-09-01). exec {tr[1]}<&-
(або більш правильний >&-
еквівалент, хоча це не має ніякої різниці, оскільки це просто дзвінки close()
для обох), не закриває stdin копроку, а кінець запису в цей копрок.
yash
.
mkfifo
є те, що вам не потрібно турбуватися про умови перегонів та безпеку доступу до труби. Ви все ще повинні турбуватися про тупик з фіфо.
stdbuf
команда може допомогти запобігти принаймні деякі з них. Я використовував його під Linux та bash. У будь-якому випадку я вважаю, що @ StéphaneChazelas має рацію у висновку: фаза "подряпин по голові" закінчилася для мене лише тоді, коли я перейшов до названих труб.
Спільні процеси спочатку були введені на мові сценаріїв оболонки з ksh88
оболонкою (1988), а пізніше в zsh
якийсь момент до 1993 року.
Синтаксис для запуску спільного процесу під ksh є command |&
. Починаючи звідти, ви можете записувати на command
стандартний вхід print -p
і читати його стандартний вихід за допомогою read -p
.
Більше ніж через кілька десятиліть, баш, якому бракувало цієї функції, нарешті представив його у своєму випуску 4.0. На жаль, було обрано несумісний і складніший синтаксис.
Під coproc
командою bash 4.0 та новішими, ви можете запустити спільний процес з командою, наприклад:
$ coproc awk '{print $2;fflush();}'
Потім ви можете передати щось команді stdin таким чином:
$ echo one two three >&${COPROC[1]}
і читати вихідний сигнал за допомогою:
$ read -ru ${COPROC[0]} foo
$ echo $foo
two
Під ksh це було б:
$ awk '{print $2;fflush();}' |&
$ print -p "one two three"
$ read -p foo
$ echo $foo
two
Що таке "копрок"?
Це короткий термін "спільний процес", що означає другий процес, що співпрацює з оболонкою. Це дуже схоже на фонове завдання, розпочате з "&" в кінці команди, за винятком того, що замість того, щоб ділити той самий стандартний вхід і вихід, як його батьківська оболонка, його стандартний введення / вивід з'єднується з батьківською оболонкою спеціальним вид труби називається FIFO.Для довідки натисніть тут
Один запускає копрок в zsh з
coproc command
Команда повинна бути готова читати зі stdin та / або писати в stdout, або це не дуже корисно як копрок.
Читайте цю статтю тут, вона пропонує тематичне дослідження між exec та coproc
|
. (тобто використання труб у більшості корпусів, а гнізда в кш93). труби та розетки є першими, вони все FIFO. mkfifo
робить названі труби, копроцеси не використовують названі труби.
Ось ще один хороший (і працюючий) приклад - простий сервер, написаний у BASH. Зверніть увагу, що вам знадобляться OpenBSD netcat
, класичний не працює. Звичайно, ви можете використовувати inet socket замість Unix.
server.sh
:
#!/usr/bin/env bash
SOCKET=server.sock
PIDFILE=server.pid
(
exec </dev/null
exec >/dev/null
exec 2>/dev/null
coproc SERVER {
exec nc -l -k -U $SOCKET
}
echo $SERVER_PID > $PIDFILE
{
while read ; do
echo "pong $REPLY"
done
} <&${SERVER[0]} >&${SERVER[1]}
rm -f $PIDFILE
rm -f $SOCKET
) &
disown $!
client.sh
:
#!/usr/bin/env bash
SOCKET=server.sock
coproc CLIENT {
exec nc -U $SOCKET
}
{
echo "$@"
read
} <&${CLIENT[0]} >&${CLIENT[1]}
echo $REPLY
Використання:
$ ./server.sh
$ ./client.sh ping
pong ping
$ ./client.sh 12345
pong 12345
$ kill $(cat server.pid)
$
bash 4.3.11
, тепер ви можете закривати дескриптори файлів копроків безпосередньо, не потребуючи допоміжних даних. змінна; З точки зору прикладу у вашій відповідіexec {tr[1]}<&-
, тепер буде працювати (щоб закрити stdin копрока; зауважте, що ваш код (опосередковано) намагається закрити з{tr[1]}
використанням>&-
, але{tr[1]}
є stdin копроку , і його потрібно закрити<&-
). Виправлення повинно бути десь між4.2.25
, що все ще виявляє проблему, і4.3.11
, що ні.