Як ви використовуєте командний копрок у різних оболонках?


Відповіді:


118

спільні процеси - це kshособливість (вже є ksh88). zshця функція мала ще з початку (на початку 90-х), тоді як вона лише була додана bashу 4.0(2009).

Однак поведінка та інтерфейс значно відрізняються між 3 оболонками.

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

Це робиться з неназваними трубами з більшістю оболонок і socketpairs з останніми версіями ksh93 в деяких системах.

В a | cmd | b, aподає дані cmdі bчитає їх вихід. Запуск cmdяк спільний процес дозволяє оболонці бути і обома, aі b.

ksh спільні процеси

В 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 спільні процеси

В 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 синтаксис і розширений .

Основний синтаксис

Основний синтаксис для запуску спільного процесу виглядає як zshs:

coproc cmd

У kshабо zshдо труб, що перебувають під час спільного процесу, і з них, можна отримати доступ до >&pта <&p.

Але в bash, дескриптори файлів труби від спільного процесу та іншої труби до спільного процесу повертаються в $COPROCмасив (відповідно ${COPROC[0]}і ${COPROC[1]}. Отже ...

Подайте дані до спільного процесу:

echo xxx >&"${COPROC[1]}"

Прочитайте дані спільного процесу:

read var <&"${COPROC[0]}"

За допомогою базового синтаксису ви можете запустити лише один спільний процес одночасно.

Розширений синтаксис

У розширеному синтаксисі ви можете назвати ваші спільні процеси (наприклад, у zshzpty спільних процесах):

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, або zshs zpty, або з назвою pipe.

Якщо ви хочете зробити сантехніку з трубами, використовуйте названі труби.

Спільні процеси можуть виконати щось з вищезазначеного, але будьте готові зробити серйозні подряпини по голові за що-небудь нетривіальне.


Справді, чудова відповідь. Я не знаю, коли конкретно це було виправлено, але, принаймні bash 4.3.11, тепер ви можете закривати дескриптори файлів копроків безпосередньо, не потребуючи допоміжних даних. змінна; З точки зору прикладу у вашій відповіді exec {tr[1]}<&- , тепер буде працювати (щоб закрити stdin копрока; зауважте, що ваш код (опосередковано) намагається закрити з {tr[1]}використанням >&-, але {tr[1]}є stdin копроку , і його потрібно закрити <&-). Виправлення повинно бути десь між 4.2.25, що все ще виявляє проблему, і 4.3.11, що ні.
mklement0

1
@ mklement0, спасибі exec {tr[1]}>&-Насправді, схоже, він працює з новішими версіями і на нього посилається у записі CWRU / changelog ( дозвольте таким словам, як {array [ind]}, бути дійсним перенаправленням ... 2012-09-01). exec {tr[1]}<&-(або більш правильний >&-еквівалент, хоча це не має ніякої різниці, оскільки це просто дзвінки close()для обох), не закриває stdin копроку, а кінець запису в цей копрок.
Стефан Шазелас

1
@ mklement0, хороший момент, я оновив його і додав yash.
Стефан Шазелас

1
Однією з переваг mkfifoє те, що вам не потрібно турбуватися про умови перегонів та безпеку доступу до труби. Ви все ще повинні турбуватися про тупик з фіфо.
Отей

1
Про тупикові місця: stdbufкоманда може допомогти запобігти принаймні деякі з них. Я використовував його під Linux та bash. У будь-якому випадку я вважаю, що @ StéphaneChazelas має рацію у висновку: фаза "подряпин по голові" закінчилася для мене лише тоді, коли я перейшов до названих труб.
шуб

7

Спільні процеси спочатку були введені на мові сценаріїв оболонки з 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

-1

Що таке "копрок"?

Це короткий термін "спільний процес", що означає другий процес, що співпрацює з оболонкою. Це дуже схоже на фонове завдання, розпочате з "&" в кінці команди, за винятком того, що замість того, щоб ділити той самий стандартний вхід і вихід, як його батьківська оболонка, його стандартний введення / вивід з'єднується з батьківською оболонкою спеціальним вид труби називається FIFO.Для довідки натисніть тут

Один запускає копрок в zsh з

coproc command

Команда повинна бути готова читати зі stdin та / або писати в stdout, або це не дуже корисно як копрок.

Читайте цю статтю тут, вона пропонує тематичне дослідження між exec та coproc


Чи можете ви додати відповідь до статті? Я намагався висвітлити цю тему в галузі U&L, оскільки вона здавалася недостатньо представленою. Дякую за вашу відповідь! Також зауважте, що я встановлюю тег як Bash, а не zsh.
slm

@slm Ви вже вказали на хакерів Баша. Я бачив там достатньо прикладів. Якщо ви хотіли довести це питання до уваги, то так, вам це вдалося:>
Валентин Байрамі

Це не спеціальні види труб, це ті самі труби, що і раніше |. (тобто використання труб у більшості корпусів, а гнізда в кш93). труби та розетки є першими, вони все FIFO. mkfifoробить названі труби, копроцеси не використовують названі труби.
Стефан Шазелас

@slm вибачте за zsh ... насправді я працюю на zsh. Я схильний робити це іноді з потоком. Це чудово працює і в Баші ...
Munai Das Udasin

@ Stephane Chazelas Я майже впевнений, що я десь читав, що це введення-виведення пов'язане зі спеціальними видами труб під назвою FIFO ...
Munai Das Udasin

-1

Ось ще один хороший (і працюючий) приклад - простий сервер, написаний у 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)
$
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.