Comment faire un tuyau bidirectionnel entre deux programmes?

Tout le monde sait comment faire un pipe unidirectionnel entre deux programmes (bind stdout du premier et stdin du second): first | second first | second .

Mais comment faire du pipe bidirectionnel, c'est-à-dire stdin et stdout de deux programmes? Y at-il moyen facile de le faire dans un shell?

Si les canaux de votre système sont bidirectionnels (comme sur Solaris 11 et certains BSD au less mais pas Linux):

 cmd1 <&1 | cmd2 >&0 

Méfiez-vous des blocages cependant.

Notez également que certaines versions de ksh93 sur certains systèmes implémentent des pipes ( | ) à l'aide d'une paire de socket . les paires de socket sont bidirectionnelles, mais ksh93 ferme explicitement la direction inverse, de sorte que la command ci-dessus ne fonctionnerait pas avec ces ksh93s même sur les systèmes où les canaux (tels que créés par l'appel système pipe(2) ) sont bidirectionnels.

Eh bien, c'est assez "facile" avec des tuyaux nommés ( mkfifo ). Je mets facilement entre guillemets parce que si les programmes sont conçus pour cela, l'impasse est probable.

 mkfifo fifo0 fifo1 ( prog1 > fifo0 < fifo1 ) & ( prog2 > fifo1 < fifo0 ) & ( exec 30<fifo0 31<fifo1 ) # write can't open until there is a reader # and vice versa if we did it the other way 

Maintenant, il y a normalement un buffer impliqué dans l'écriture de stdout. Ainsi, par exemple, si les deux programmes étaient:

 #!/usr/bin/perl use 5.010; say 1; print while (<>); 

vous attendez une boucle infinie. Mais au lieu de cela, les deux seraient dans l'impasse; vous auriez besoin d'append $| = 1 $| = 1 (ou équivalent) pour désactiver la mise en memory tampon de sortie. Le blocage est provoqué parce que les deux programmes attendent quelque chose sur stdin, mais ils ne le voient pas car il est assis dans le tampon stdout de l'autre programme et n'a pas encore été écrit dans le tuyau.

Mise à jour : intégrant les suggestions de Stéphane Charzelas et Joost:

 mkfifo fifo0 fifo1 prog1 > fifo0 < fifo1 & prog2 < fifo0 > fifo1 

fait la même chose, est plus courte et plus portable.

Je ne sais pas si c'est ce que vous essayez de faire:

 nc -l -p 8096 -c second & nc -c first 127.0.0.1 8096 & 

Cela commence par l'ouverture d'une prise d'écoute sur le port 8096 et, une fois la connection établie, le second programme est généré avec son stdin comme sortie de stream et stdout comme input de stream.

Ensuite, un second nc est lancé qui se connecte au port d'écoute et lance le programme en first avec son stdout comme input de stream et son stdin comme sortie de stream.

Ce n'est pas exactement fait en utilisant un tuyau, mais il semble faire ce dont vous avez besoin.

Comme cela utilise le réseau, cela peut être fait sur 2 ordinateurs distants. C'est presque la façon dont un server Web ( second ) et un browser Web ( first ) fonctionnent.

Vous pouvez utiliser pipexec :

 $ pipexec -- '[A' cmd1 ] '[B' cmd2 ] '{A:1>B:0}' '{B:1>A:0}' 

Un bloc de construction pratique pour écrire de tels tuyaux bidirectionnels est quelque chose qui relie les stdout et stdin du process actuel set. Appelons cela ioloop. Après avoir appelé cette fonction, il vous suffit de démarrer un canal régulier:

 ioloop && # stdout -> stdin cmd1 | cmd2 # stdin -> cmd1 -> cmd2 -> stdout (-> back to stdin) 

Si vous ne souhaitez pas modifier les descripteurs du shell de niveau supérieur, exécutez-le dans un sous-shell:

 ( ioloop && cmd1 | cmd2 ) 

Voici une implémentation portable d'ioloop utilisant un canal nommé:

 ioloop() { FIFO=$(mktemp -u /tmp/ioloop_$$_XXXXXX ) && trap "rm -f $FIFO" EXIT && mkfifo $FIFO && ( : <$FIFO & ) && # avoid deadlock on opening pipe exec >$FIFO <$FIFO } 

Le canal nommé n'existe que brièvement dans le système de files pendant la configuration d'ioloop. Cette fonction n'est pas tout à fait POSIX car mktemp est obsolète (et potentiellement vulnérable à une attaque de course).

Une implémentation spécifique à linux utilisant / proc / est possible qui ne nécessite pas de canal nommé, mais je pense que celui-ci est plus que suffisant.

bash version 4 a la command coproc qui permet ceci fait en bash pur sans pipes nommées:

 coproc cmd1 eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}" 

D'autres coquilles peuvent également faire coproc aussi.

Voici une réponse plus détaillée mais enstring trois commands, plutôt que deux, ce qui rend un peu plus intéressant.

Si vous êtes heureux d'utiliser également cat et stdbuf construction peut être rendue plus facile à comprendre.

Version utilisant bash avec cat et stdbuf , facile à comprendre:

 # start pipeline coproc { cmd1 | cmd2 | cmd3 } # create command to reconnect STDOUT `cmd3` to STDIN of `cmd1` endcmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}" # eval the command. eval "${endcmd}" 

Notez que vous devez utiliser eval car l'extension de variable dans <& $ var est illégale dans mon versio de bash 4.2.25.

Version utilisant pur bash : Divisez en deux parties, lancez le premier pipeline sous coproc, puis déjeunez la deuxième partie (soit une seule command ou un pipeline) le reconnectant au premier:

 coproc { cmd 1 | cmd2 } endcmd="exec cmd3 <&${COPROC[0]} >&${COPROC[1]}" eval "${endcmd}" 

Preuve de concept:

file ./prog , juste un programme factice pour consumr, étiqueter et ./prog les lignes. Utiliser des sous-couches pour éviter les problèmes de mise en memory tampon peut s'avérer excessif, ce n'est pas le but ici.

 #!/bin/bash let c=0 sleep 2 [ "$1" == "1" ] && ( echo start ) while : ; do line=$( head -1 ) echo "$1:${c} ${line}" 1>&2 sleep 2 ( echo "$1:${c} ${line}" ) let c++ [ $c -eq 3 ] && exit done 

file ./start_cat Ceci est une version utilisant bash , cat et stdbuf

 #!/bin/bash echo starting first cmd>&2 coproc { stdbuf -i0 -o0 ./prog 1 \ | stdbuf -i0 -o0 ./prog 2 \ | stdbuf -i0 -o0 ./prog 3 } echo "Delaying remainer" 1>&2 sleep 5 cmd="exec stdbuf -i0 -o0 /bin/cat <&${COPROC[0]} >&${COPROC[1]}" echo "Running: ${cmd}" >&2 eval "${cmd}" 

ou file ./start_part . Ceci est une version utilisant pur bash seulement. À des fins de démonstration, j'utilise encore stdbuf car votre prog réel devrait faire face à la mise en tampon interne de toute façon pour éviter le blocage dû à la mise en memory tampon.

 #!/bin/bash echo starting first cmd>&2 coproc { stdbuf -i0 -o0 ./prog 1 \ | stdbuf -i0 -o0 ./prog 2 } echo "Delaying remainer" 1>&2 sleep 5 cmd="exec stdbuf -i0 -o0 ./prog 3 <&${COPROC[0]} >&${COPROC[1]}" echo "Running: ${cmd}" >&2 eval "${cmd}" 

Sortie:

 > ~/iolooptest$ ./start_part starting first cmd Delaying remainer 2:0 start Running: exec stdbuf -i0 -o0 ./prog 3 <&63 >&60 3:0 2:0 start 1:0 3:0 2:0 start 2:1 1:0 3:0 2:0 start 3:1 2:1 1:0 3:0 2:0 start 1:1 3:1 2:1 1:0 3:0 2:0 start 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start 3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start 1:2 3:2 2:2 1:1 3:1 2:1 1:0 3:0 2:0 start 

Cela le fait.

Il y a aussi

  • dpipe , le "tube bidirectionnel", inclus dans le packageage vde2, et inclus dans les systèmes de gestion des packages distro actuels .

    dpipe processA = processB

  • socat , l'outil de connection tout-à-tout.

    socat EXEC:Program1 EXEC:Program2

Comme le souligne Stéphane Stéphane dans les commentaires, les exemples ci-dessus sont le "formulaire de base", il a de bons exemples avec des options sur sa réponse pour une question similaire .

Il y a beaucoup de bonnes réponses ici. Donc, je veux juste append quelque chose à jouer avec eux. Je suppose que stderr n'est redirigé nulle part. Créez deux scripts (disons a.sh et b.sh):

 #!/bin/bash echo "foo" # change to 'bar' in second file for i in {1..10}; do read input echo ${input} echo ${i} ${0} got: ${input} >&2 done 

Ensuite, lorsque vous les connectez de toute façon, vous devriez voir sur la console:

 1 ./a.sh got: bar 1 ./b.sh got: foo 2 ./a.sh got: foo 2 ./b.sh got: bar 3 ./a.sh got: bar 3 ./b.sh got: foo 4 ./a.sh got: foo 4 ./b.sh got: bar ...