Ответы:
совместные процессы являются ksh
функцией (уже в ksh88
). zsh
была функция с самого начала (в начале 90-х), в то время как она была добавлена только bash
в 4.0
(2009).
Тем не менее, поведение и интерфейс значительно отличается между 3 оболочками.
Однако идея та же: она позволяет запускать задание в фоновом режиме и может отправлять входные данные и читать выходные данные, не прибегая к именованным каналам.
Это делается с помощью неназванных каналов с большинством оболочек и пар сокетов с последними версиями ksh93 в некоторых системах.
В a | cmd | b
, a
передает данные cmd
и b
читает их вывод. Запуск cmd
как совместный процесс позволяет оболочке быть a
и b
.
В ksh
, вы запускаете сопроцесс как:
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<&-
Вы не можете запустить второй совместный процесс, если сначала не сохраните дескрипторы файла канала в другие файлы. Например:
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
:
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
, эти дескрипторы файла канала помечаются как близкие к выполнению.
Но bash
единственный способ передать те исполняемые командам , чтобы дублировать их FDS 0
, 1
, или 2
. Это ограничивает количество побочных процессов, с которыми вы можете взаимодействовать для одной команды. (См. Пример ниже.)
yash
сама по себе не имеет функции совместного процесса, но та же самая концепция может быть реализована с ее функциями конвейера и перенаправления процесса . yash
имеет интерфейс к pipe()
системному вызову, так что такого рода вещи можно сделать относительно легко вручную.
Вы начали бы совместный процесс с:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Который сначала создает pipe(4,5)
(5 - конец записи, 4 - конец чтения), затем перенаправляет fd 3 в канал в процесс, который запускается со своим стандартным входом на другом конце, а стандартный вывод - в канал, созданный ранее. Затем мы закрываем конец записи этого канала в родительском элементе, который нам не нужен. Итак, теперь в оболочке у нас есть fd 3, подключенный к stdin cmd, и fd 4, подключенный к stdout cmd с помощью pipe.
Обратите внимание, что флаг close-on-exec не установлен для этих файловых дескрипторов.
Для подачи данных:
echo data >&3 4<&-
Чтобы прочитать данные:
read var <&4 3>&-
И вы можете закрыть fds как обычно:
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 есть пример использования coproc.
Вот упрощенный пример: представьте, что вам нужна функция, которая передает копию вывода команды 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
Выше, для fds-процесса установлены флаги 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
«с zpty
или именованными каналами.
Если вы хотите поработать с трубами, используйте именованные каналы.
Совместные процессы могут делать некоторые из вышеперечисленных, но будьте готовы сделать серьезные царапины на голову для чего-то нетривиального.
exec {tr[1]}>&-
действительно, кажется, работает с более новыми версиями и упоминается в записи CWRU / changelog ( допустим, что слова вроде {array [ind]} должны быть допустимыми перенаправлениями ... 2012-09-01). exec {tr[1]}<&-
(или более корректный >&-
эквивалент, хотя это не имеет значения, поскольку он просто требует close()
обоих) не закрывает стандартный код копрока, а записывает конец канала в этот копрок.
yash
.
mkfifo
является то, что вам не нужно беспокоиться о состоянии гонки и безопасности при доступе к трубе. Вы все еще должны беспокоиться о тупике с FIFO.
stdbuf
команда может помочь предотвратить хотя бы некоторые из них. Я использовал его под Linux и Bash. В любом случае, я считаю, что @ StéphaneChazelas прав в заключении: фаза «чесания головы» закончилась для меня, только когда я переключился на именованные каналы.
Совместные процессы были впервые представлены на языке сценариев оболочки вместе с ksh88
оболочкой (1988), а затем в zsh
некоторый момент до 1993 года.
Синтаксис для запуска совместного процесса под КШ является command |&
. Начиная с этого момента , вы можете записывать в command
стандартный ввод с помощью print -p
и читать его стандартный вывод с помощью read -p
.
Более чем через пару десятилетий bash, у которого не было этой функции, наконец-то представил ее в своем выпуске 4.0. К сожалению, был выбран несовместимый и более сложный синтаксис.
В bash 4.0 и новее вы можете запустить совместный процесс с помощью coproc
команды, например:
$ coproc awk '{print $2;fflush();}'
Затем вы можете передать что-то команде stdin следующим образом:
$ echo one two three >&${COPROC[1]}
и прочитайте вывод awk:
$ read -ru ${COPROC[0]} foo
$ echo $foo
two
Под ksh это было бы:
$ awk '{print $2;fflush();}' |&
$ print -p "one two three"
$ read -p foo
$ echo $foo
two
Что такое «копрок»?
Это сокращение от "co-process", что означает второй процесс, взаимодействующий с оболочкой. Это очень похоже на фоновое задание, начинающееся с символа «&» в конце команды, за исключением того, что вместо совместного использования того же стандартного ввода и вывода, что и его родительская оболочка, его стандартный ввод-вывод подключается к родительской оболочке с помощью специального вид трубы называется FIFO. Для справки нажмите здесь
Начинается копрок в Zsh с
coproc command
Команда должна быть подготовлена для чтения из stdin и / или записи в stdout, иначе она не очень полезна в качестве копрока.
Прочитайте эту статью, здесь приведен пример использования exec и coproc.
|
. (то есть использовать трубы в большинстве оболочек и пары гнезд в ksh93). трубы и соединительные муфты являются первыми, первыми, они все FIFO. mkfifo
создает именованные каналы, сопроцессы не используют именованные каналы.
Вот еще один хороший (и работающий) пример - простой сервер, написанный на BASH. Обратите внимание, что вам понадобятся OpenBSD netcat
, классический не будет работать. Конечно, вы можете использовать inet socket вместо unix one.
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
, теперь вы можете закрывать дескрипторы файлов coproc напрямую, без необходимости в aux. переменная; с точки зрения примера в вашем ответеexec {tr[1]}<&-
теперь будет работать (чтобы закрыть stdin копрока; обратите внимание, что ваш код (косвенно) пытается закрыть{tr[1]}
использование>&-
, но{tr[1]}
является stdin копрока и должен быть закрыт с<&-
). Исправление, должно быть, произошло где-то между4.2.25
, что все еще показывает проблему, и4.3.11
, которая не делает.