GNU/Linux >> Tutoriels Linux >  >> Linux

Comment utiliser la commande Coproc dans divers shells ?

Quelqu'un peut-il fournir quelques exemples sur la façon d'utiliser coproc ?

Réponse acceptée :

les co-processus sont un ksh fonctionnalité (déjà dans ksh88 ). zsh a eu la fonctionnalité depuis le début (début des années 90), alors qu'elle vient juste d'être ajoutée à bash en 4.0 (2009).

Cependant, le comportement et l'interface sont significativement différents entre les 3 shells.

L'idée est cependant la même :cela permet de démarrer une tâche en arrière-plan et de pouvoir lui envoyer une entrée et lire sa sortie sans avoir à recourir à des canaux nommés.

Cela se fait avec des canaux sans nom avec la plupart des shells et des paires de sockets avec des versions récentes de ksh93 sur certains systèmes.

Dans a | cmd | b , a envoie des données à cmd et b lit sa sortie. Exécution de cmd en tant que co-processus permet au shell d'être à la fois a et b .

co-processus ksh

En ksh , vous démarrez un coprocess comme :

cmd |&

Vous fournissez 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 démarré comme n'importe quel travail d'arrière-plan, vous pouvez utiliser fg , bg , kill dessus et faites-y référence par %job-number ou via $! .

Pour fermer l'extrémité d'écriture du tube cmd est en train de lire, vous pouvez faire :

exec 3>&p 3>&-

Et pour fermer l'extrémité lecture de l'autre tuyau (celui cmd écrit à):

exec 3<&p 3<&-

Vous ne pouvez pas démarrer un deuxième co-processus à moins que vous n'enregistriez d'abord les descripteurs de fichier de canal dans d'autres fds. Par exemple :

tr a b |&
exec 3>&p 4<&p
tr b c |&
echo aaa >&3
echo bbb >&p

co-processus zsh

En zsh , les co-processus sont presque identiques à ceux de ksh . La seule vraie différence est que zsh les co-processus sont lancés avec le coproc mot-clé.

coproc cmd
echo test >&p
read var <&p
print -p test
read -p var

En cours :

exec 3>&p

Remarque :Cela ne déplace pas le coproc descripteur de fichier à fd 3 (comme dans ksh ), mais le duplique. Donc, il n'y a pas de moyen explicite de fermer le tuyau d'alimentation ou de lecture, un autre en commençant un autre coproc .

Par exemple, pour fermer l'extrémité d'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 canaux, zsh (depuis 3.1.6-dev19, sorti en 2000) a des constructions basées sur des pseudo-tty comme expect . Pour interagir avec la plupart des programmes, les co-processus de style ksh ne fonctionneront pas, car les programmes commencent à se mettre en mémoire tampon lorsque leur sortie est un tube.

Voici quelques exemples.

Démarrer le co-processus x :

zmodload zsh/zpty
zpty x cmd

(Ici, cmd est une simple commande. Mais vous pouvez faire des choses plus fantaisistes avec eval ou fonctions.)

Nourrir une donnée co-traitée :

zpty -w x some data

Lire les données du co-processus (dans le cas le plus simple) :

zpty -r x var

Comme expect , il peut attendre une sortie du co-processus correspondant à un modèle donné.

co-processus bash

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 pour permettre la gestion des descripteurs de fichiers alloués dynamiquement au-dessus de 10.

bash offre une base coproc syntaxe, et une syntaxe étendue un.

Syntaxe de base

La syntaxe de base pour démarrer un co-processus ressemble à zsh ‘s :

coproc cmd

En ksh ou zsh , les canaux vers et depuis le co-processus sont accessibles avec >&p et <&p .

Mais en bash , les descripteurs de fichier du tube du co-processus et de l'autre tube vers le co-processus sont retournés dans le $COPROC tableau (respectivement ${COPROC[0]} et ${COPROC[1]} . Alors…

Fournir des 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.

Syntaxe étendue

Dans la syntaxe étendue, vous pouvez nommer vos co-processus (comme dans zsh co-processus zpty) :

coproc mycoproc { cmd; }

La commande a être une commande composée. (Remarquez comment l'exemple ci-dessus rappelle function f { ...; } .)

Cette fois, les descripteurs de fichiers sont en ${mycoproc[0]} et ${mycoproc[1]} .

Vous pouvez démarrer plus d'un co-processus à la fois, mais vous le faites recevoir un avertissement lorsque vous démarrez un co-processus alors qu'il est encore en cours d'exécution (même en mode non interactif).

Vous pouvez fermer les descripteurs de fichiers lorsque vous utilisez la syntaxe étendue.

coproc tr { tr a b; }
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 fichier pipe sont marqués comme close-on-exec.

Mais en bash , le seul moyen de les transmettre aux commandes exécutées est de les dupliquer dans fds , 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.)

En relation :Exécuter une application non approuvée en toute sécurité via la commande sandbox-exec ?

processus yash et redirection du pipeline

yash n'a pas de fonctionnalité de co-processus en soi, mais le même concept peut être implémenté avec son pipeline et traiter fonctionnalités de redirection. yash a une interface avec le pipe() appel système, donc ce genre de chose peut être fait relativement facilement à la main.

Vous démarreriez un co-processus avec :

exec 5>>|4 3>(cmd >&5 4<&- 5>&-) 5>&-

Qui crée d'abord un pipe(4,5) (5 la fin de l'écriture, 4 la fin de la lecture), puis redirige fd 3 vers un tube vers un processus qui s'exécute avec son stdin à l'autre extrémité, et stdout vers le tube créé précédemment. Ensuite, nous fermons l'extrémité d'écriture de ce tube dans le parent dont nous n'aurons pas besoin. Alors maintenant, dans le shell, nous avons fd 3 connecté au stdin du cmd et fd 4 connecté au stdout du cmd avec des tuyaux.

Notez que l'indicateur close-on-exec n'est pas défini sur ces descripteurs de fichiers.

Pour alimenter les données :

echo data >&3 4<&-

Pour lire les données :

read var <&4 3>&-

Et vous pouvez fermer fds comme d'habitude :

exec 3>&- 4<&-

Maintenant, pourquoi ils ne sont pas si populaires

peu d'avantages par rapport à l'utilisation de canaux nommés

Les co-processus peuvent facilement être mis en œuvre avec des tubes nommés standard. Je ne sais pas quand exactement les canaux nommés ont été introduits, mais il est possible que ce soit après ksh est venu avec des co-processus (probablement au milieu des années 80, ksh88 a été "publié" en 88, mais je crois que ksh était utilisé en interne chez AT&T quelques années auparavant) ce 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

L'interaction avec ceux-ci est plus simple, surtout si vous devez exécuter plus d'un co-processus. (Voir les exemples ci-dessous.)

Le seul avantage d'utiliser coproc est que vous n'avez pas à nettoyer ces canaux nommés après utilisation.

sujet aux impasses

Les coques utilisent des tuyaux dans quelques constructions :

  • tuyaux en coque : cmd1 | cmd2 ,
  • substitution de commande : $(cmd) ,
  • et substitution de processus : <(cmd) , >(cmd) .

Dans ceux-ci, les données circulent dans un seul direction entre les 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 une trace de quelle commande a quel descripteur de fichier ouvert, pour éviter qu'une seule reste ouverte et maintienne un processus en vie. Les blocages peuvent être difficiles à étudier, car ils peuvent se produire de manière non déterministe; par exemple, uniquement lorsque suffisamment de données pour remplir un tuyau sont envoyées.

fonctionne moins bien que expect pour ce pour quoi il a été conçu

L'objectif principal des co-processus était de fournir au shell un moyen d'interagir avec les commandes. Cependant, cela ne fonctionne pas très bien.

La forme la plus simple de blocage mentionnée ci-dessus est :

tr a b |&
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 la fin du fichier sur son stdin , ou il a accumulé un tampon plein de données à sortir. Donc ci-dessus, après que le shell a sorti an (seulement 2 octets), le read 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 commandes. Les co-processus ne peuvent être utilisés que pour interagir avec des commandes qui ne tamponnent pas leur sortie, ou commandes auxquelles on peut dire de ne pas tamponner leur sortie ; par exemple, en utilisant stdbuf avec certaines commandes sur les 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.

La gestion des descripteurs de fichiers est fastidieuse et difficile à maîtriser

Les co-processus peuvent être utilisés pour effectuer des travaux de plomberie plus complexes que ce que permettent les simples tuyaux en coque.

cette autre réponse Unix.SE a un exemple d'utilisation de coproc.

Voici un exemple simplifié : Imaginez que vous vouliez une fonction qui envoie une copie de la sortie d'une commande à 3 autres commandes, puis que la sortie de ces 3 commandes soit concaténée.

Tous utilisant des tuyaux.

Par exemple :alimentez la sortie de printf '%sn' foo bar à tr a b , sed 's/./&&/g' , et cut -b2- pour obtenir quelque chose comme :

foo
bbr
ffoooo
bbaarr
oo
ar

Tout d'abord, ce n'est pas nécessairement évident, mais il y a là une possibilité de blocage, et cela commencera à se produire après seulement quelques kilo-octets de données.

Ensuite, selon votre shell, vous rencontrerez un certain nombre de problèmes différents qui doivent être résolus différemment.

En relation :Comment le caractère générique * est-il interprété comme une commande ?

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 '%sn' foo bar | f

Ci-dessus, les fds du co-processus ont l'indicateur close-on-exec défini, mais pas ceux qui en sont dupliqués (comme dans {o1}<&p ). Ainsi, pour éviter les blocages, vous devrez vous assurer qu'ils sont fermés dans tous les processus qui n'en ont pas besoin.

De même, nous devons utiliser un sous-shell et utiliser exec cat en fin de compte, pour s'assurer qu'aucun processus shell ne ment à 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 '%sn' foo bar | f

(Remarque : Cela ne fonctionnera pas sur les systèmes où ksh utilise socketpairs au lieu de pipes , et où /dev/fd/n fonctionne comme sous Linux.)

En ksh , fds au-dessus de 2 sont marqués avec le drapeau close-on-exec, à moins qu'ils ne soient passés explicitement sur la ligne de commande. C'est pourquoi nous n'avons pas à fermer les descripteurs de fichiers inutilisés comme avec zsh — mais c'est aussi pourquoi nous devons faire {i1}>&$i1 et utilisez eval pour cette nouvelle valeur de $i1 , à passer à tee et cat

Dans bash cela ne peut pas être fait, car vous ne pouvez pas éviter l'indicateur de fermeture à l'exécution.

Au-dessus, c'est relativement simple, car on n'utilise que des commandes externes simples. Cela devient plus compliqué lorsque vous souhaitez utiliser des constructions shell à la place, et vous commencez à rencontrer des bogues shell.

Comparez ce qui précède avec le même en utilisant des canaux 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 '%sn' foo bar | f

Conclusion

Si vous souhaitez interagir avec une commande, utilisez expect , ou zsh est zpty , ou canaux nommés.

Si vous voulez faire de la plomberie sophistiquée avec des tuyaux, utilisez des tuyaux nommés.

Les co-processus peuvent faire certaines des choses ci-dessus, mais soyez prêt à vous gratter sérieusement la tête pour tout ce qui n'est pas trivial.


Linux
  1. Comment utiliser la commande Linux sed

  2. Comment utiliser la commande Linux grep

  3. Comment utiliser la commande basename ?

  4. Comment utiliser la commande id sous Linux

  5. Comment utiliser la commande "screen" sous Linux

Comment utiliser la commande nmap

Comment utiliser la commande fd sur le système Linux

Comment utiliser la commande wget sous Linux ?

Comment utiliser la commande xargs sous Linux ?

Comment utiliser la commande RPM sous Linux

Comment utiliser la commande which sous Linux