Réponses:
les co-processus sont une ksh
fonctionnalité (déjà dans ksh88
). zsh
a eu la fonction de départ (début des années 90), alors qu'il vient seulement été ajouté à bash
en 4.0
(2009).
Cependant, le comportement et l'interface sont significativement différents entre les 3 coques.
L'idée est la même, cependant: cela permet de démarrer une tâche en arrière-plan et de pouvoir l'envoyer et la lire sans avoir à recourir à des canaux nommés.
Cela est fait avec des pipes non nommées avec la plupart des shells et socketpairs avec les versions récentes de ksh93 sur certains systèmes.
Dans a | cmd | b
, a
alimente les données cmd
et b
lit sa sortie. Exécuter en cmd
tant que co-processus permet au shell d'être à la fois a
et b
.
Dans ksh
, vous démarrez un coprocessus en tant que:
cmd |&
Vous alimentez des données cmd
en faisant des choses comme:
echo test >&p
ou
print -p test
Et lisez cmd
la sortie avec des choses comme:
read var <&p
ou
read -p var
cmd
est commencé comme tout travail d'arrière - plan, vous pouvez utiliser fg
, bg
, kill
sur elle et de le renvoyer par %job-number
ou via $!
.
Pour fermer l’écriture du bout du tuyau cmd
, vous pouvez faire:
exec 3>&p 3>&-
Et pour fermer le bout de lecture de l’autre tuyau (celui qui cmd
écrit):
exec 3<&p 3<&-
Vous ne pouvez pas démarrer un deuxième co-processus à moins d’avoir préalablement enregistré les descripteurs de fichier de canal dans un autre fichier fds. Par exemple:
tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p
Dans zsh
, les co-processus sont presque identiques à ceux de ksh
. La seule différence est que les zsh
co-processus sont démarrés avec le coproc
mot clé.
coproc cmd
echo test >&p
read var <&p
print -p test
read -p var
Faire:
exec 3>&p
Remarque: Cela ne déplace pas le coproc
descripteur de fichier vers fd 3
(comme dans ksh
), mais le duplique. Il n’existe donc aucun moyen explicite de fermer le tuyau d’alimentation ou de lecture, un autre en commençant un autre coproc
.
Par exemple, pour fermer la fin de l'alimentation:
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
En plus des co-processus basés sur des tubes, zsh
(depuis 3.1.6-dev19, publié en 2000), des constructions à base de pseudo-tty similaires expect
. Pour interagir avec la plupart des programmes, les co-processus de style ksh ne fonctionneront pas, car les programmes démarrent la mise en mémoire tampon lorsque leur sortie est un canal.
Voici quelques exemples.
Démarrer le co-processus x
:
zmodload zsh/zpty
zpty x cmd
(Voici cmd
une commande simple. Mais vous pouvez faire des choses plus sophistiquées avec eval
ou des fonctions.)
Introduire des données de co-processus:
zpty -w x some data
Lire les données de co-traitement (dans le cas le plus simple):
zpty -r x var
De même expect
, il peut attendre que le co-processus produise une sortie correspondant à un modèle donné.
La syntaxe bash est beaucoup plus récente et repose sur une nouvelle fonctionnalité récemment ajoutée à ksh93, bash et zsh. Il fournit une syntaxe permettant le traitement des descripteurs de fichier alloués de manière dynamique au-dessus de 10.
bash
propose une syntaxe de base coproc
et une syntaxe étendue .
La syntaxe de base pour démarrer un co-processus ressemble à zsh
ceci:
coproc cmd
Dans ksh
ou zsh
, les tuyaux entrant et sortant du co-processus sont accessibles avec >&p
et <&p
.
Mais dans bash
, les descripteurs de fichier du canal provenant du co-processus et de l'autre canal vers le co-processus sont renvoyés dans le $COPROC
tableau (respectivement ${COPROC[0]}
et ${COPROC[1]}
. Alors…
Transmettre les données au co-processus:
echo xxx >&"${COPROC[1]}"
Lire les données du co-processus:
read var <&"${COPROC[0]}"
Avec la syntaxe de base, vous ne pouvez démarrer qu'un seul co-processus à la fois.
Dans la syntaxe étendue, vous pouvez nommer vos co-processus (comme dans les co-processus zsh
zpty):
coproc mycoproc { cmd; }
La commande doit être une commande composée. (Remarquez comment l'exemple ci-dessus rappelle function f { ...; }
.)
Cette fois, les descripteurs de fichier sont dans ${mycoproc[0]}
et ${mycoproc[1]}
.
Vous pouvez commencer à plus d'un co-processus à la fois, mais vous ne recevez un avertissement lorsque vous démarrez un co-processus tandis que l' un est toujours en cours d' exécution (même en mode non-interactif).
Vous pouvez fermer les descripteurs de fichier lorsque vous utilisez la syntaxe étendue.
coproc tr { tr a b; }
echo aaa >&"${tr[1]}"
exec {tr[1]}>&-
cat <&"${tr[0]}"
Notez que fermer de cette façon ne fonctionne pas dans les versions de bash antérieures à la 4.3, où vous devez l'écrire à la place:
fd=${tr[1]}
exec {fd}>&-
Comme dans ksh
et zsh
, ces descripteurs de fichier de canal sont marqués comme étant proches de l'exéc.
Mais dans bash
, la seule façon de passer les commandes exécutées à est de les dupliquer à fds 0
, 1
ou 2
. Cela limite le nombre de co-processus avec lesquels vous pouvez interagir pour une seule commande. (Voir ci-dessous pour un exemple.)
yash
n'a pas de fonctionnalité de co-processus en soi, mais le même concept peut être mis en œuvre avec ses fonctionnalités de pipeline et de redirection de processus . yash
a une interface avec l' pipe()
appel système, de sorte que ce genre de chose peut être fait relativement facilement à la main.
Vous commencerez un co-processus avec:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Ce qui crée d'abord un pipe(4,5)
(5 l'extrémité d'écriture, 4 l'extrémité de lecture), puis redirige fd 3 vers un canal vers un processus qui s'exécute avec stdin à l'autre extrémité et stdout allant au canal créé précédemment. Ensuite, nous fermons la fin d'écriture de ce tuyau dans le parent, ce dont nous n'avons pas besoin. Alors maintenant, dans le shell, nous avons fd 3 connecté au stdin de la cmd et fd 4 connecté au stdout de la cmd avec des pipes.
Notez que l'indicateur Close-on-Exec n'est pas défini sur ces descripteurs de fichier.
Pour alimenter des données:
echo data >&3 4<&-
Pour lire les données:
read var <&4 3>&-
Et vous pouvez fermer les fds comme d'habitude:
exec 3>&- 4<&-
Les co-processus peuvent facilement être mis en œuvre avec des tubes nommés standard. Je ne sais pas quand les pipes nommées exactement ont été introduites, mais il est possible que ce soit après ksh
des co-processus (probablement au milieu des années 80, ksh88 a été "publié" en 88, mais je crois qu'il ksh
était utilisé en interne chez AT & T quelques années auparavant. cela) qui expliquerait pourquoi.
cmd |&
echo data >&p
read var <&p
Peut être écrit avec:
mkfifo in out
cmd <in >out &
exec 3> in 4< out
echo data >&3
read var <&4
Interagir avec ces éléments est plus simple, en particulier si vous devez exécuter plusieurs processus en même temps. (Voir exemples ci-dessous.)
Le seul avantage de l'utilisation coproc
est que vous n'avez pas à nettoyer ces canaux nommés après utilisation.
Les coquilles utilisent des tuyaux dans quelques constructions:
cmd1 | cmd2
,$(cmd)
,<(cmd)
, >(cmd)
.Dans ces derniers, les données ne circulent que dans un sens entre différents processus.
Avec les co-processus et les canaux nommés, cependant, il est facile de se retrouver dans une impasse. Vous devez garder trace de quelle commande a quel descripteur de fichier est ouvert, pour empêcher quiconque de rester ouvert et de maintenir un processus en vie. Les impasses peuvent être difficiles à enquêter, car elles peuvent survenir de manière non déterministe; par exemple, seulement quand autant de données que pour remplir un tube sont envoyées.
expect
pour ce qu'il a été conçu pourLe but principal des co-processus était de fournir au shell un moyen d'interagir avec les commandes. Cependant, cela ne fonctionne pas si bien.
La forme la plus simple d'impasse mentionnée ci-dessus est:
tr a b |&
echo a >&p
read var<&p
Parce que sa sortie ne va pas à un terminal, elle tr
tamponne sa sortie. Donc, il ne restituera rien tant qu'il ne verra pas la fin du fichier stdin
, ou qu'il aura accumulé toute une mémoire tampon de données à produire. Donc, ci-dessus, une fois que le shell a sorti a\n
(seulement 2 octets), le read
bloc sera bloqué indéfiniment car il tr
attend que le shell lui envoie plus de données.
En bref, les pipes ne sont pas bonnes pour interagir avec les commandes. Les co-processus ne peuvent être utilisés que pour interagir avec des commandes qui ne tamponnent pas leur sortie, ou des commandes auxquelles on peut dire de ne pas tamponner leur sortie; par exemple, en utilisant stdbuf
certaines commandes sur des systèmes GNU ou FreeBSD récents.
C'est pourquoi expect
ou zpty
utilisez plutôt des pseudo-terminaux. expect
est un outil conçu pour interagir avec les commandes, et il le fait bien.
Les co-processus peuvent être utilisés pour effectuer des travaux de plomberie plus complexes que ne le permettent les tuyaux tubulaires simples.
cette autre réponse Unix.SE a un exemple d'utilisation de coproc.
Voici un exemple simplifié: Imaginez que vous souhaitiez une fonction qui alimente une copie de la sortie d’une commande vers 3 autres commandes, puis que la sortie de ces 3 commandes soit concaténée.
Tous utilisent des tuyaux.
Par exemple: nourrir la sortie printf '%s\n' foo bar
à tr a b
, sed 's/./&&/g'
et cut -b2-
d'obtenir quelque chose comme:
foo
bbr
ffoooo
bbaarr
oo
ar
Tout d’abord, ce n’est pas forcément évident, mais il existe une possibilité d’impasse, et cela commencera à se produire après seulement quelques kilo-octets de données.
Ensuite, en fonction de votre shell, vous rencontrerez un certain nombre de problèmes qui doivent être traités différemment.
Par exemple, avec zsh
, vous le feriez avec:
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
Ci-dessus, les fichiers fd du co-processus ont l'indicateur Close-on-Exec défini, mais pas ceux dupliqués à partir d'eux (comme dans {o1}<&p
). Donc, pour éviter les blocages, vous devrez vous assurer qu'ils sont fermés dans tous les processus qui n'en ont pas besoin.
De la même manière, nous devons utiliser un sous-shell et l'utiliser exec cat
à la fin, pour nous assurer qu'il n'y a pas de processus de coque qui ne tient pas avant de maintenir un tuyau ouvert.
Avec ksh
(ici ksh93
), cela devrait être:
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
( Note: Cela ne fonctionnera pas sur les systèmes où ksh
utilise au socketpairs
lieu de pipes
, et où /dev/fd/n
fonctionne comme sous Linux.)
Dans ksh
, les fichiers fds ci 2
- dessus sont marqués de l'indicateur close-on-exec, à moins qu'ils ne soient explicitement passés sur la ligne de commande. C'est pourquoi nous n'avons pas besoin de fermer les descripteurs de fichier inutilisés comme avec zsh
— mais c'est aussi pourquoi nous devons faire {i1}>&$i1
et utiliser eval
pour cette nouvelle valeur de $i1
, être passé à tee
et cat
…
Dans bash
ce ne peut pas être fait, parce que vous ne pouvez pas éviter la fermeture sur-exec.
Ci-dessus, c'est relativement simple, car nous n'utilisons que de simples commandes externes. Cela devient plus compliqué lorsque vous souhaitez utiliser des constructions shell à la place, et que vous commencez à rencontrer des bugs shell.
Comparez ce qui précède avec le même en utilisant des tubes nommés:
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
Si vous souhaitez interagir avec une commande, l' utilisation expect
, ou zsh
« s zpty
, ou les pipes nommés.
Si vous souhaitez effectuer des travaux de plomberie sophistiqués avec des tuyaux, utilisez des tuyaux nommés.
Les co-processus peuvent faire partie de ce qui est mentionné ci-dessus, mais soyez prêt à faire de sérieuses grattages pour tout ce qui n'est pas anodin.
exec {tr[1]}>&-
semble en effet fonctionner avec les versions les plus récentes et est référencé dans une entrée CWRU / changelog ( permet à des mots comme {array [ind]} d'être une redirection valide ... 2012-09-01). exec {tr[1]}<&-
(ou l' >&-
équivalent plus correct bien que cela ne fasse aucune différence, car cela appelle simplement les close()
deux) ne ferme pas le stdin du coproc, mais la fin de l'écriture du tube vers ce coproc.
yash
.
mkfifo
est que vous n'avez pas à vous soucier des conditions de concurrence et de la sécurité pour l'accès par canalisation. Vous devez toujours vous inquiéter de l'impasse avec la fifo.
stdbuf
commande peut aider à empêcher au moins certains d’entre eux. Je l'ai utilisé sous Linux et bash. Quoi qu'il en soit, @ StéphaneChazelas a raison dans la conclusion: la phase de "grattage en profondeur" ne s'est terminée que lorsque je suis revenu à des pipes nommées.
Les co-processus ont été introduits pour la première fois dans un langage de script shell avec le ksh88
shell (1988), puis plus tard, zsh
avant 1993.
La syntaxe pour lancer un co-processus sous ksh est command |&
. À partir de là, vous pouvez écrire sur command
l'entrée standard print -p
et lire sa sortie standard avec read -p
.
Plus de deux décennies plus tard, bash, qui manquait de cette fonctionnalité, l'a finalement introduite dans sa version 4.0. Malheureusement, une syntaxe incompatible et plus complexe a été sélectionnée.
Sous bash 4.0 et versions ultérieures, vous pouvez lancer un co-processus avec la coproc
commande, par exemple:
$ coproc awk '{print $2;fflush();}'
Vous pouvez alors passer quelque chose à la commande stdin de cette façon:
$ echo one two three >&${COPROC[1]}
et lisez la sortie awk avec:
$ read -ru ${COPROC[0]} foo
$ echo $foo
two
Sous ksh, cela aurait été:
$ awk '{print $2;fflush();}' |&
$ print -p "one two three"
$ read -p foo
$ echo $foo
two
Qu'est-ce qu'un "coproc"?
En abrégé, "co-process" signifie un second processus coopérant avec le shell. Cela ressemble beaucoup à un travail en arrière-plan démarré avec un "&" à la fin de la commande, sauf qu'au lieu de partager les mêmes entrées et sorties standard que son shell parent, ses E / S standard sont connectées au shell parent par une connexion spéciale. type de tuyau appelé FIFO.Pour la référence cliquez ici
On commence un coproc dans zsh avec
coproc command
La commande doit être prête à lire à partir de stdin et / ou à écrire sur stdout, sinon elle n’est pas très utile en tant que coproc.
Lisez cet article ici, il fournit une étude de cas entre exec et coproc
|
. (c’est-à-dire utilisez des tuyaux dans la plupart des shells et des socketpairs dans ksh93). les tubes et les paires de connecteurs sont les premiers entrants, les premiers sortants, ils sont tous FIFO. mkfifo
fait des pipes nommés, les coprocesses n'utilisent pas de pipes nommés.
Voici un autre bon exemple (qui fonctionne): un simple serveur écrit en BASH. Veuillez noter que vous auriez besoin d'OpenBSD netcat
, le classique ne fonctionnera pas. Bien sûr, vous pouvez utiliser inet socket au lieu d’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
Usage:
$ ./server.sh
$ ./client.sh ping
pong ping
$ ./client.sh 12345
pong 12345
$ kill $(cat server.pid)
$
bash 4.3.11
, vous pouvez maintenant fermer les descripteurs de fichier coproc directement, sans avoir besoin d'un aux. variable; en termes de l'exemple dans votre réponseexec {tr[1]}<&-
fonctionnerait maintenant (pour fermer le stdin du coproc; notez que votre code (indirectement) tente de fermer en{tr[1]}
utilisant>&-
, mais{tr[1]}
est le stdin du coproc , et doit être fermé avec<&-
). Le correctif doit être arrivé quelque part entre4.2.25
ce qui pose toujours le problème et4.3.11
ce qui ne l’est pas.