Come realizzare una pipe bidirezionale tra due programmi?


63

Tutti sanno come rendere il tubo unidirezionale tra due programmi (bind stdoutdi primo e stdindi secondo): first | second.

Ma come realizzare pipe bidirezionali, ovvero cross-bind stdine stdoutdue programmi? C'è un modo semplice per farlo in una shell?

shell  pipe 

Risposte:


30

Se le pipe sul tuo sistema sono bidirezionali (come lo sono su Solaris 11 e almeno su alcuni BSD, ma non su Linux):

cmd1 <&1 | cmd2 >&0

Attenzione ai deadlock però.

Si noti inoltre che alcune versioni di ksh93 su alcuni sistemi implementano pipe ( |) utilizzando una coppia di socket . le coppie di socket sono bidirezionali, ma ksh93 arresta esplicitamente la direzione inversa, quindi il comando sopra non funzionerebbe con quei ksh93s anche su sistemi in cui i pipe (come creati dalla pipe(2)chiamata di sistema) sono bidirezionali.


1
qualcuno sa se funziona anche su Linux? (usando archlinux qui)
heinrich5991


41

Bene, è abbastanza "facile" con named pipe ( mkfifo). Ho messo facilmente le virgolette perché a meno che i programmi non siano progettati per questo, è probabile un deadlock.

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

Ora, normalmente c'è il buffering coinvolto nella scrittura di stdout. Quindi, ad esempio, se entrambi i programmi fossero:

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

ti aspetteresti un ciclo infinito. Ma invece, entrambi sarebbero in stallo; dovresti aggiungere $| = 1(o equivalente) per disattivare il buffering dell'output. Il deadlock è causato dal fatto che entrambi i programmi stanno aspettando qualcosa su stdin, ma non lo vedono perché si trova nel buffer stdout dell'altro programma e non è ancora stato scritto nella pipe.

Aggiornamento : incorporando suggerimenti da Stéphane Charzelas e Joost:

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

fa lo stesso, è più corto e più portatile.


23
Una named pipe è sufficiente: prog1 < fifo | prog2 > fifo.
Andrey Vihrov,

2
@AndreyVihrov è vero, puoi sostituire una pipe anonima con una di quelle nominate. Ma mi piace la simmetria :-P
derobert

3
@ user14284: Su Linux, probabilmente puoi farlo con qualcosa del genere prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo.
Andrey Vihrov,

3
Se lo fai prog2 < fifo0 > fifo1, puoi evitare la tua piccola danza con exec 30< ...(che tra l'altro funziona solo con basho yashper fds oltre 10 in quel modo).
Stéphane Chazelas,

1
@Joost Hmmm, sembra che tu abbia ragione che non ce n'è bisogno, almeno in bash. Probabilmente ero preoccupato che dal momento che la shell eseguiva i reindirizzamenti (inclusa l'apertura dei tubi), poteva bloccarsi, ma almeno colpire le forchette prima di aprire i quindici. dashsembra anche OK (ma si comporta in modo leggermente diverso)
derobert

13

Non sono sicuro se questo è ciò che stai cercando di fare:

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

Questo inizia aprendo un socket in ascolto sulla porta 8096, e una volta che viene stabilita una connessione, genera programma secondcon il stdinquale il flusso di output e stdoutcome ingresso flusso.

Poi, un secondo ncviene lanciata che collega la porta di attesa e genera programma firstcon il stdoutquale input flusso e la sua stdincome il flusso di uscita.

Questo non è esattamente fatto usando una pipe, ma sembra fare ciò di cui hai bisogno.

Poiché utilizza la rete, è possibile farlo su 2 computer remoti. Questo è quasi il modo in cui funzionano un server Web ( second) e un browser Web ( first).


1
E nc -Uper socket di dominio UNIX che occupano solo spazio di indirizzi del filesystem.
Ciro Santilli 30 改造 中心 法轮功 六四 事件

-c L'opzione non è disponibile in Linux. La mia felicità fu di breve durata :-(
pablochacin il


6

bashla versione 4 ha un coproccomando che consente di farlo in puro bashsenza named pipe:

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

Anche alcune altre shell possono fare coprocaltrettanto.

Di seguito è una risposta più dettagliata, ma concatena tre comandi, anziché due, il che rende solo un po 'più interessante.

Se sei felice di usare anche cate stdbufquindi costruire può essere reso più facile da capire.

Versione bashcon cate stdbuf, facile da capire:

# 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}"

Nota, devi usare eval perché l'espansione variabile in <& $ var è illegale nel mio versio di bash 4.2.25.

Versione che utilizza pure bash: suddividere in due parti, avviare la prima pipeline in coproc, quindi pranzare la seconda parte (un singolo comando o una pipeline) ricollegandola alla prima:

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

Verifica teorica:

file ./prog, solo un programma fittizio per consumare, taggare e ristampare le linee. L'uso di subshells per evitare problemi di buffering può essere eccessivo, non è questo il punto.

#!/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 Questa è una versione che utilizza bash, catestdbuf

#!/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}"

o file ./start_part. Questa è una versione che utilizza bashsolo puro . Per scopi dimostrativi sto ancora usando stdbufperché il tuo vero prog dovrebbe occuparsi comunque del buffering internamente per evitare il blocco dovuto al buffering.

#!/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}"

Produzione:

> ~/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

Questo lo fa.


5

Un comodo componente per la scrittura di tali tubi bidirezionali è qualcosa che collega insieme lo stdout e lo stdin del processo corrente. Chiamiamolo ioloop. Dopo aver chiamato questa funzione, devi solo avviare una normale pipe:

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

Se non si desidera modificare i descrittori della shell di livello superiore, eseguirlo in una subshell:

( ioloop && cmd1 | cmd2 )

Ecco un'implementazione portatile di ioloop usando una pipe denominata:

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

La pipe denominata esiste nel filesystem solo brevemente durante l'installazione di ioloop. Questa funzione non è abbastanza POSIX perché mktemp è deprecato (e potenzialmente vulnerabile a un attacco di razza).

È possibile un'implementazione specifica per Linux usando / proc / che non richiede una named pipe, ma penso che questa sia più che sufficiente.


Funzione interessante, +1. Probabilmente potrebbe usare una frase o 2 aggiunte che spiegano ( : <$FIFO & )in modo più dettagliato. Grazie per la pubblicazione.
Alex Stragies,

Mi guardai un po 'in giro e mi alzai in bianco; dove posso trovare informazioni sulla deprecazione di mktemp? Lo uso ampiamente e se uno strumento più recente ha preso il suo posto, vorrei iniziare ad usarlo.
DopeGhoti,

Alex: il syscall aperto (2) su un tubo naned sta bloccando. se provi a "exec <$ PIPE> $ PIPE" rimarrà bloccato in attesa di un altro processo per aprire l'altro lato. Il comando ": <$ FIFO &" viene eseguito in una subshell in background e consente il completamento del reindirizzamento bidirezionale.
user873942

DopeGhoti: la funzione della libreria mktemp (3) C è obsoleta. L'utilità mktemp (1) non lo è.
user873942

4

C'è anche

Come @ StéphaneChazelas osserva correttamente nei commenti, sopra gli esempi sono il "modulo base", ha dei bei esempi con opzioni sulla sua risposta per una domanda simile .


Si noti che per impostazione predefinita, socatutilizza socket anziché pipe (è possibile modificarlo con commtype=pipes). Si consiglia di aggiungere l' noforkopzione per evitare un ulteriore processo socat che spinge i dati tra i tubi / prese. (grazie per la modifica sulla mia risposta tra l'altro)
Stéphane Chazelas,

0

Ci sono molte grandi risposte qui. Quindi voglio solo aggiungere qualcosa per giocarci facilmente. Presumo che stderrnon venga reindirizzato da nessuna parte. Crea due script (diciamo a.sh e 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

Quindi quando li colleghi in qualche modo, dovresti vedere sulla 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
...
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.