Jak zrobić potok dwukierunkowy między dwoma programami?

63

Każdy wie, jak sprawić, by rurę jednokierunkowy pomiędzy dwoma programami (wiążą się stdoutz pierwszego i stdindrugiego jednego) first | second.

Ale jak zrobić potok dwukierunkowy, tj. Wiązanie krzyżowe stdini stdoutdwóch programów? Czy istnieje prosty sposób, aby to zrobić w powłoce?


źródło

Odpowiedzi:

30

Jeśli potoki w twoim systemie są dwukierunkowe (tak jak w Solaris 11 i przynajmniej niektórych BSD, ale nie Linux):

cmd1 <&1 | cmd2 >&0

Uważaj jednak na impasy.

Zauważ też, że niektóre wersje ksh93 w niektórych systemach implementują potoki ( |) przy użyciu pary gniazd . pary gniazd są dwukierunkowe, ale ksh93 wyraźnie wyłącza kierunek odwrotny, więc powyższe polecenie nie będzie działać z tymi ksh93s nawet w systemach, w których potoki (utworzone przez pipe(2)wywołanie systemowe) są dwukierunkowe.

Stéphane Chazelas
źródło
1
Czy ktoś wie, czy to działa również na Linuksie? (używając archlinux tutaj)
heinrich5991,
41

Cóż, jest dość „łatwy” z nazwanymi potokami ( mkfifo). Wstawiam łatwe cytaty, ponieważ jeśli programy nie są zaprojektowane do tego, impas jest prawdopodobny.

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

Teraz zwykle występuje buforowanie przy pisaniu standardowego wyjścia. Na przykład, jeśli oba programy były:

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

można oczekiwać nieskończonej pętli. Ale zamiast tego oba byłyby w impasie; musisz dodać $| = 1(lub równoważny), aby wyłączyć buforowanie danych wyjściowych. Zakleszczenie powstaje, ponieważ oba programy czekają na coś na standardowym wejściu, ale nie widzą tego, ponieważ znajduje się w buforze standardowego drugiego programu i nie zostało jeszcze zapisane w potoku.

Aktualizacja : uwzględniająca sugestie Stéphane'a Charzelasa i Joosta:

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

robi to samo, jest krótszy i bardziej przenośny.

derobert
źródło
23
Jeden nazwany potok jest wystarczająca: prog1 < fifo | prog2 > fifo.
Andrey Vihrov,
2
@AndreyVihrov to prawda, możesz zastąpić anonimową fajkę jednym z wymienionych. Ale podoba mi się symetria :-P
derobert
3
@ user14284: W systemie Linux prawdopodobnie można to zrobić za pomocą czegoś takiego prog1 < fifo | tee /dev/stderr | prog2 | tee /dev/stderr > fifo.
Andrey Vihrov,
3
Jeśli ci się uda prog2 < fifo0 > fifo1, można uniknąć swój mały taniec z exec 30< ...(który nawiasem mówiąc działa tylko z bashlub yashdo FDS ponad 10 tak).
Stéphane Chazelas,
1
@ Joost Hmmm, wygląda na to, że masz rację, że nie ma takiej potrzeby, przynajmniej w bash. Prawdopodobnie martwiłem się, że ponieważ powłoka wykonuje przekierowania (w tym otwieranie rur), może blokować - ale przynajmniej rozwalić widelce przed otwarciem fifos. dashwydaje się też OK (ale zachowuje się trochę inaczej)
derobert,
13

Nie jestem pewien, czy to właśnie próbujesz zrobić:

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

Zaczyna się to od otwarcia gniazda nasłuchującego na porcie 8096, a po ustanowieniu połączenia spawnuje program secondz stdinwyjściem strumienia i stdoutwejściem strumienia.

Następnie ncuruchamiany jest drugi, który łączy się z portem nasłuchującym i odradza program firstz jego stdoutwejściowym strumieniem i stdinwyjściowym strumieniem.

Nie robi się tego dokładnie za pomocą potoku, ale wydaje się, że robi to, czego potrzebujesz.

Ponieważ korzysta to z sieci, można to zrobić na 2 komputerach zdalnych. W ten sposób działa prawie serwer WWW ( second) i przeglądarka internetowa ( first).

jfg956
źródło
1
A nc -Udla gniazd domeny UNIX, które zajmują tylko przestrzeń adresową systemu plików.
Ciro Santilli 30 改造 中心 法轮功 六四 事件
Opcja -c nie jest dostępna w systemie Linux. Moje szczęście było krótkotrwałe :-(
pablochacin
9

Możesz użyć pipexec :

$ pipexec -- '[A' cmd1 ] '[B' cmd2 ] '{A:1>B:0}' '{B:1>A:0}'
Andreas Florath
źródło
6

bashwersja 4 ma coprocpolecenie, które pozwala to zrobić w czystej postaci bashbez nazwanych potoków:

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

Niektóre inne powłoki również mogą to zrobić coproc.

Poniżej znajduje się bardziej szczegółowa odpowiedź, ale łączy trzy polecenia zamiast dwóch, co czyni tylko trochę bardziej interesującym.

Jeśli jesteś szczęśliwy w użyciu, cata stdbufnastępnie skonstruowanie może być łatwiejsze do zrozumienia.

Wersja korzystająca bashz cati stdbufłatwa do zrozumienia:

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

Zauważ, że muszę użyć eval, ponieważ rozszerzenie zmiennych w <& $ var jest nielegalne w mojej wersji bash 4.2.25.

Wersja z użyciem pure bash: Podziel na dwie części, uruchom pierwszy potok w ramach coproc, a następnie uruchom drugą część (jedno polecenie lub potok) ponownie łącząc go z pierwszą:

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

Dowód koncepcji:

plik ./prog, tylko sztuczny program do konsumpcji, oznaczania i ponownego drukowania linii. Używanie podpowłoki w celu uniknięcia problemów z buforowaniem może być nadmierne, nie o to tutaj chodzi.

#!/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 To jest wersja użyciu bash, catastdbuf

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

lub plik ./start_part. To jest wersja wykorzystująca bashwyłącznie czysty . Do celów demonstracyjnych wciąż używam, stdbufponieważ twój prawdziwy program musiałby i tak zająć się buforowaniem wewnętrznym, aby uniknąć blokowania z powodu buforowania.

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

Wynik:

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

To wystarczy

AnyDev
źródło
5

Wygodnym elementem składowym do pisania takich dwukierunkowych potoków jest coś, co łączy stdout i stdin bieżącego procesu razem. Nazwijmy to Ioloop. Po wywołaniu tej funkcji wystarczy uruchomić zwykły potok:

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

Jeśli nie chcesz modyfikować deskryptorów powłoki najwyższego poziomu, uruchom to w podpowłoce:

( ioloop && cmd1 | cmd2 )

Oto przenośna implementacja ioloop za pomocą nazwanego potoku:

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

Nazwany potok istnieje w systemie plików tylko na krótko podczas instalacji ioloop. Ta funkcja nie jest całkiem POSIX, ponieważ mktemp jest przestarzały (i potencjalnie podatny na atak rasowy).

Możliwe jest wdrożenie specyficzne dla systemu Linux przy użyciu / proc /, które nie wymaga nazwanego potoku, ale myślę, że ten jest wystarczająco dobry.

użytkownik873942
źródło
Ciekawa funkcja, +1. Prawdopodobnie można użyć dodanego zdania lub 2 wyjaśnień ( : <$FIFO & )bardziej szczegółowo. Dziękujemy za wysłanie.
Alex Stragies,
Rozejrzałem się trochę i wyszedłem pusty; gdzie mogę znaleźć informacje o wycofaniu mktemp? Używam go intensywnie, a jeśli zastąpi go nowsze narzędzie, chciałbym zacząć z niego korzystać.
DopeGhoti
Alex: blokuje się otwarte (2) wywołanie systemowe na rurce nanowanej. jeśli spróbujesz „exec <$ PIPE> $ PIPE”, utknie w oczekiwaniu na inny proces otwarcia drugiej strony. Polecenie „: <$ FIFO &” jest wykonywane w podpowłoce w tle i umożliwia pomyślne zakończenie przekierowania dwukierunkowego.
user873942,
DopeGhoti: funkcja biblioteki mktemp (3) C jest przestarzała. Narzędzie mktemp (1) nie jest.
user873942,
4

Jest również

Jak @ StéphaneChazelas poprawnie zauważa w komentarzach, powyższe przykłady są „formą podstawową”, ma ładne przykłady z opcjami odpowiedzi na podobne pytanie .

Alex Stragies
źródło
Zauważ, że domyślnie socatużywa gniazd zamiast rur (możesz to zmienić za pomocą commtype=pipes). Możesz dodać noforkopcję, aby uniknąć dodatkowego procesu spychania danych między rurami / gniazdami. (dzięki za edycję mojej odpowiedzi btw)
Stéphane Chazelas
0

Jest tu wiele świetnych odpowiedzi. Chcę tylko dodać coś, co pozwoli im się z nimi dobrze bawić. Zakładam, że stderrnie jest nigdzie przekierowywany. Utwórz dwa skrypty (powiedzmy a.sh i 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

Następnie, gdy podłączysz je w jakikolwiek dobry sposób, powinieneś zobaczyć na konsoli:

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
...
pawel7318
źródło