Quelqu'un peut-il fournir quelques exemples sur la façon d'utiliser coproc
?
les co-process sont une fonctionnalité ksh
(déjà dans ksh88
). zsh
a eu la fonctionnalité dès le début (début des années 90), alors qu'il vient d'être ajouté à bash
en 4.0
(2009).
Cependant, le comportement et l'interface sont significativement différents entre les 3 obus.
L'idée est la même: elle permet de démarrer un travail en arrière-plan et de pouvoir l'envoyer en input et lire sa sortie sans devoir recourir à des pipes nommées.
Cela se fait avec des tuyaux sans nom avec la plupart des shells et des paires de socket avec des versions récentes de ksh93 sur certains systèmes.
Dans a | cmd | b
a | cmd | b
a | cmd | b
, a
alimente datatables en cmd
et b
lit sa sortie. Exécuter cmd
tant que co-process permet à l'interpréteur de commands d'être à la fois a
et b
.
Dans ksh
, vous démarrez un coprocess comme:
cmd |&
Vous alimentez des données en cmd
en faisant des choses comme:
echo test >&p
ou
print -p test
Et lisez la sortie de cmd
avec des choses comme:
read var <&p
ou
read -p var
cmd
est lancé comme n'importe quel travail en arrière-plan, vous pouvez utiliser fg
, bg
, kill
dessus et le referencer par %job-number
ou via $!
.
Pour fermer la fin d'écriture de la cmd
lecture, vous pouvez faire:
exec 3>&p 3>&-
Et pour fermer la fin de la lecture de l'autre pipe (la seule cmd
écrit):
exec 3<&p 3<&-
Vous ne pouvez pas démarrer un second co-process sauf si vous enregistrez d'abord les descripteurs de files de pipe sur d'autres files. Par exemple:
tr ab |& exec 3>&p 4<&p tr bc |& echo aaa >&3 echo bbb >&p
En zsh
, les co-process sont presque identiques à ceux de ksh
. La seule vraie différence est que les co-process zsh
sont démarrés avec le mot key coproc
.
coproc cmd echo test >&p read var <&p print -p test read -p var
Faire:
exec 3>&p
Note: Cela ne déplace pas le descripteur de file coproc
sur fd 3
(comme dans ksh
), mais le duplique. Donc, il n'y a pas de moyen explicite de fermer l'alimentation ou de lire le tuyau, l'autre commençant un autre coproc
.
Par exemple, pour fermer l'extrémité d'alimentation:
coproc tr ab 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-process basés sur les pipes, zsh
(depuis 3.1.6-dev19, publié en 2000) a des constructions pseudo-tty comme expect
. Pour interagir avec la plupart des programmes, les co-process de type ksh ne fonctionneront pas, car les programmes commencent à mettre en memory tampon lorsque leur sortie est un canal.
Voici quelques exemples.
Lancer le co-process x
:
zmodload zsh/zpty zpty x cmd
(Ici, cmd
est une simple command, mais vous pouvez faire des choses plus intéressantes avec eval
ou des fonctions.)
Fournir des données de co-process:
zpty -wx some data
Lire des données de traitement conjoint (dans le cas le plus simple):
zpty -rx var
Comme expect
, il peut attendre une sortie du process de co-traitement correspondant à un model donné.
La syntaxe bash est beaucoup plus récente et s'appuie sur une nouvelle fonctionnalité récemment ajoutée à ksh93, bash et zsh. Il fournit une syntaxe permettant la gestion des descripteurs de files alloués dynamicment au-dessus de 10.
bash
offre une syntaxe coproc
basique et une syntaxe étendue .
La syntaxe de base pour démarrer un co-process ressemble à celle de zsh
:
coproc cmd
Dans ksh
ou zsh
, les canaux vers et depuis le co-process sont accessibles avec >&p
et <&p
.
Mais dans bash
, les descripteurs de file du pipe du co-process et de l'autre pipe au co-process sont returnnés dans le tableau $COPROC
(respectivement ${COPROC[0]}
et ${COPROC[1]}
. …
Flux de données vers le co-process:
echo xxx >&"${COPROC[1]}"
Lire datatables du co-process:
read var <&"${COPROC[0]}"
Avec la syntaxe de base, vous ne pouvez démarrer qu'un seul co-process à la fois.
Dans la syntaxe étendue, vous pouvez nommer vos co-process (comme dans les co-process zsh
zpty):
coproc mycoproc { cmd; }
La command doit être une command composée. (Remarquez comment l'exemple ci-dessus callbackle la function f { ...; }
.)
Cette fois, les descripteurs de files sont dans ${mycoproc[0]}
et ${mycoproc[1]}
.
Vous pouvez démarrer plus d'un co-process à la fois, mais vous obtenez un avertissement lorsque vous démarrez un co-process pendant que vous êtes encore en cours d'exécution (même en mode non interactif).
Vous pouvez fermer les descripteurs de files lorsque vous utilisez la syntaxe étendue.
coproc tr { tr ab; } echo aaa >&"${tr[1]}" exec {tr[1]}>&- cat <&"${tr[0]}"
Notez que la fermeture de cette façon ne fonctionne pas dans les versions bash antérieures à 4.3 où vous devez l'écrire à la place:
fd=${tr[1]} exec {fd}>&-
Comme dans ksh
et zsh
, ces descripteurs de files de pipe sont marqués comme close-on-exec.
Mais dans bash
, la seule façon de les transmettre aux commands exécutées est de les dupliquer sur fds 0
, 1
ou 2
. Cela limite le nombre de co-process avec lesquels vous pouvez interagir pour une seule command. (Voir ci-dessous pour un exemple.)
yash
n'a pas de fonctionnalité de co-traitement en soi, mais le même concept peut être implémenté avec ses fonctionnalités de redirection de pipeline et de process . yash
a une interface avec l'appel système pipe()
, donc ce genre de chose peut être fait relativement facilement à la main.
Vous commenceriez un co-process avec:
exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-
Le premier crée un pipe(4,5)
(5 l'extrémité d'écriture, 4 la fin de lecture), redirige ensuite fd 3 vers un canal qui exécute son stdin à l'autre extrémité et stdout vers le canal créé précédemment . Ensuite, nous fermons la fin d'écriture de ce tuyau chez le parent dont nous n'aurons pas besoin. Alors maintenant dans la coquille nous avons fd 3 connecté à la std de la cmd et fd 4 connecté à stdout de cmd avec des tuyaux.
Notez que l'indicateur close-on-exec n'est pas défini sur ces descripteurs de file.
Pour alimenter datatables:
echo data >&3 4<&-
Pour lire des données:
read var <&4 3>&-
Et vous pouvez fermer fds comme d'habitude:
exec 3>&- 4<&-
Les co-process peuvent facilement être implémentés avec des tubes nommés standard. Je ne sais pas quand des tubes nommés ont été introduits, mais il est possible que ce soit après que ksh
eu des co-process (probablement au milieu des années 80, ksh88 était "sorti" en 88, mais je crois que ksh
était utilisé en interne chez AT & T années avant 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 ceux-ci est plus simple, surtout si vous avez besoin d'exécuter plus d'un co-process. (Voir les exemples ci-dessous.)
Le seul avantage de l'utilisation de coproc
est que vous n'avez pas à nettoyer ces tuyaux nommés après utilisation.
Les réservoirs utilisent des tuyaux dans quelques constructions:
cmd1 | cmd2
cmd1 | cmd2
, $(cmd)
, <(cmd)
, >(cmd)
. Dans ceux-ci, datatables ne circulent que dans un seul sens entre différents process.
Avec les co-process et les pipes nommés, cependant, il est facile de se refind dans l'impasse. Vous devez savoir quelle command possède quel descripteur de file ouvert, pour empêcher que l'user rest ouvert et maintienne un process en vie. Les impasses peuvent être difficiles à étudier, car elles peuvent se produire de manière non déterministe; par exemple, uniquement lorsque l'on envoie autant de données pour remplir une canalisation.
expect
pour ce qu'il a été conçu pour Le but principal des co-process était de fournir au shell un moyen d'interagir avec les commands. Cependant, cela ne fonctionne pas très bien.
La forme la plus simple d'impasse mentionnée ci-dessus est la suivante:
tr ab |& echo a >&p read var<&p
Parce que sa sortie ne va pas à un terminal, tr
tamponne sa sortie. Donc, il ne sortira rien tant qu'il ne verra pas de fin de file sur son stdin
, ou qu'il a accumulé un buffer-full de données à sortir. Ainsi ci-dessus, après que le shell a généré a\n
(seulement 2 octets), la read
se bloquera indéfiniment car tr
attend que le shell lui envoie plus de données.
En bref, les pipes ne sont pas bonnes pour interagir avec les commands. Les co-process ne peuvent être utilisés que pour interagir avec des commands qui ne tamponnent pas leur sortie, ou des commands qui peuvent être dites de ne pas tamponner leur sortie; par exemple, en utilisant stdbuf
avec certaines commands sur les systèmes GNU ou FreeBSD récents.
C'est pourquoi expect
ou zpty
utilisent à la place des pseudo-terminaux. expect
est un outil conçu pour interagir avec les commands, et il le fait bien.
Les co-process peuvent être utilisés pour effectuer des travaux de plomberie plus complexes que ce que permettent les simples tuyaux en coquille.
cette autre réponse Unix.SE a un exemple d'utilisation coproc.
Voici un exemple simplifié: Imaginez que vous voulez une fonction qui transmet une copy de la sortie d'une command à 3 autres commands, puis que la sortie de ces trois commands soit concaténée.
Tout en utilisant des tuyaux.
Par exemple: alimente la sortie de printf '%s\n' foo bar
en tr ab
, sed 's/./&&/g'
et cut -b2-
pour get quelque chose comme:
foo bbr ffoooo bbaarr oo ar
Tout d'abord, ce n'est pas nécessairement évident, mais il y a une possibilité de blocage, et cela commencera après seulement quelques kilo-octets de données.
Ensuite, en fonction de votre shell, vous allez rencontrer un certain nombre de problèmes différents qui doivent être traités différemment.
Par exemple, avec zsh
, vous le feriez avec:
f() ( coproc tr ab 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
Au-dessus, les co-process fds ont le drapeau close-on-exec, mais pas ceux qui en sont dupliqués (comme dans {o1}<&p
). Donc, pour éviter les blocages, vous devrez vous assurer qu'ils sont fermés dans tous les process qui n'en ont pas besoin.
De la même façon, nous devons utiliser un sous-shell et utiliser un exec cat
à la fin, afin de s'assurer qu'il n'y a pas de process shell qui consiste à maintenir un tuyau ouvert.
Avec ksh
(ici ksh93
), cela devrait être:
f() ( tr ab |& 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 des socketpairs
au lieu de pipes
et où /dev/fd/n
fonctionne comme sur Linux).
Dans ksh
, les fds supérieurs à 2
sont marqués avec l'indicateur close-on-exec, sauf s'ils sont transmis explicitement sur la command line. C'est pourquoi nous n'avons pas besoin de fermer les descripteurs de files inutilisés comme avec zsh
mais c'est aussi pour cela que nous devons faire {i1}>&$i1
et utiliser eval
pour cette nouvelle valeur de $i1
, …
En bash
cela ne peut pas être fait, parce que vous ne pouvez pas éviter le drapeau close-on-exec.
Au-dessus, c'est relativement simple, parce que nous n'utilisons que des commands externes simples. Cela devient plus compliqué lorsque vous voulez utiliser des constructions shell ici, et vous commencez à courir dans des bogues shell.
Comparez ce qui précède avec le même en utilisant des tuyaux nommés:
f() { mkfifo p{i,o}{1,2,3} tr ab < pi1 > po1 & sed 's/./&&/g' < pi2 > po2 & cut -c2- < pi3 > po3 & tee pi{1,2} > pi3 & cat po{1,2,3} rm -fp{i,o}{1,2,3} } printf '%s\n' foo bar | f
Si vous souhaitez interagir avec une command, utilisez expect
, zsh
ou zsh
, ou les canaux nommés.
Si vous voulez faire de la plomberie de fantaisie avec des tuyaux, utilisez des tuyaux nommés.
Co-process peuvent faire certains de ce qui précède, mais être prêt à faire une tête sérieuse gratter pour quelque chose de non négligeable.
Les co-process ont été introduits dans un langage de script shell en 1988 avec ksh.
La syntaxe pour lancer un co-process sous ksh est la command |&
. A partir de là, vous pouvez écrire pour command
l'input standard avec print -p
et lire sa sortie standard avec read -p
.
Plus de quelques décennies plus tard, bash qui manquait de cette fonctionnalité l'a finalement introduit dans sa version 4.0. Malheureusement, une syntaxe incompatible et plus complexe a été sélectionnée.
Sous bash 4.0 et plus récent, vous pouvez lancer un co-process avec la command coproc
, par exemple:
$ coproc awk '{print $2;fflush();}'
Vous pouvez alors passer quelque chose à la command 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"?
C'est court pour "co-process" qui signifie un deuxième process de coopération avec le shell. Il est très similaire à un travail d'arrière-plan démarré avec un "&" à la fin de la command, sauf qu'au lieu de partager la même input et sortie standard que son shell parent, son E / S standard est connecté au shell parent par un sorte de tuyau appelé un FIFO.Pour reference cliquez ici
On commence un coproc dans zsh avec
coproc command
La command doit être préparée pour lire à partir de stdin et / ou écrire dans stdout, ou 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
Voici un autre bon exemple (et fonctionnel): un simple server écrit en BASH. Veuillez noter que vous auriez besoin du netcat
d'OpenBSD, le classique ne fonctionnera pas. Bien sûr, vous pourriez utiliser socket inet 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) $