Tout le monde sait comment faire un pipe unidirectionnel entre deux programmes (lier stdout
du premier et stdin
du second): first | second
.
Mais comment créer un tube bidirectionnel, c'est-à-dire réticuler stdin
et stdout
de deux programmes? Existe-t-il un moyen facile de le faire dans un shell?
Si les canaux de votre système sont bidirectionnels (comme ils le sont sur Solaris 11 et certains BSD au moins, mais pas Linux):
cmd1 <&1 | cmd2 >&0
Attention aux impasses cependant.
Notez également que certaines versions de ksh93 sur certains systèmes implémentent des canaux (|
) Utilisant un socket socket. les paires de sockets sont bidirectionnelles, mais ksh93 arrête explicitement le sens inverse, de sorte que la commande ci-dessus ne fonctionnerait pas avec ces ksh93 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 pipes nommées (mkfifo
). Je mets facile entre guillemets car à moins que les programmes ne soient conçus pour cela, un blocage 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 tampon 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 vous attendriez à une boucle infinie. Mais au lieu de cela, les deux seraient dans l'impasse; vous devez ajouter $| = 1
(ou équivalent) pour désactiver la mise en mémoire tampon de sortie. Le blocage est dû au fait que les deux programmes attendent quelque chose sur stdin, mais ils ne le voient pas car il se trouve dans le tampon stdout de l'autre programme , et n'a pas encore été écrit sur le tuyau.
Mise à jour: intégrant les suggestions de Stéphane Charzelas et Joost:
mkfifo fifo0 fifo1
prog1 > fifo0 < fifo1 &
prog2 < fifo0 > fifo1
fait de même, est plus court 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'un socket d'écoute sur le port 8096, et une fois la connexion établie, le programme second
avec son stdin
comme sortie de flux et stdout
comme entrée de flux.
Ensuite, un deuxième nc
est lancé qui se connecte au port d'écoute et génère le programme first
avec son stdout
comme entrée de flux et son stdin
comme flux production.
Cela ne se fait pas exactement à l'aide d'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 ainsi qu'un serveur Web (second
) et un navigateur Web (first
) fonctionnent.
Vous pouvez utiliser pipexec :
$ pipexec -- '[A' cmd1 ] '[B' cmd2 ] '{A:1>B:0}' '{B:1>A:0}'
bash
la version 4 dispose de la commande coproc
qui permet de le faire en pur bash
sans canaux nommés:
coproc cmd1
eval "exec cmd2 <&${COPROC[0]} >&${COPROC[1]}"
Certains autres shells peuvent également faire coproc
.
La réponse ci-dessous est plus détaillée mais enchaîne trois commandes, plutôt que deux, ce qui rend la commande un peu plus intéressante.
Si vous êtes heureux d'utiliser également cat
et stdbuf
alors la 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'expansion des variables dans <& $ var est illégale dans ma version de bash 4.2.25.
Version utilisant pure bash
: Se décompose en deux parties, lance le premier pipeline sous coproc, puis lance la deuxième partie (soit une seule commande soit un pipeline) en le reconnectant à la première:
coproc {
cmd 1 | cmd2
}
endcmd="exec cmd3 <&${COPROC[0]} >&${COPROC[1]}"
eval "${endcmd}"
Preuve de concept:
fichier ./prog
, juste un programme factice pour consommer, étiqueter et réimprimer les lignes. L'utilisation de sous-coquilles pour éviter les problèmes de mise en mémoire tampon peut être exagérée, 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
fichier ./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 fichier ./start_part
. Il s'agit d'une version utilisant uniquement bash
pur. À des fins de démonstration, j'utilise toujours stdbuf
parce que votre véritable prog devrait de toute façon gérer la mise en mémoire tampon pour éviter le blocage dû à la mise en mémoire 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}"
Production:
> ~/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
Ça le fait.
Un bloc de construction pratique pour écrire de tels tuyaux bidirectionnels est quelque chose qui relie ensemble la sortie standard et la sortie standard du processus en cours. Appelons-le ioloop. Après avoir appelé cette fonction, il vous suffit de démarrer un canal normal:
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 fichiers pendant la configuration d'ioloop. Cette fonction n'est pas tout à fait POSIX car mktemp est déconseillé (et potentiellement vulnérable à une attaque raciale).
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.
Il y a aussi
dpipe
, le "tube bidirectionnel", inclus dans le package vde2, et inclus dans systèmes de gestion de packages de distribution actuels .
dpipe processA = processB
socat , l'outil de connexion de tout à tout.
socat EXEC:Program1 EXEC:Program2
Comme @ StéphaneChazelas le note correctement dans les commentaires, les exemples ci-dessus sont le "formulaire de base", il a Bons exemples avec options sur sa réponse pour un question similaire .
Il y a beaucoup de bonnes réponses ici. Je veux donc simplement ajouter quelque chose pour jouer facilement 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, 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
...