2008-09-02 10 views
19

Angenommen, ich habe Programme P0, P1, ... P(n-1) für einige n > 0. Wie kann ich die Ausgabe des Programms Pi auf das Programm P(i+1 mod n) für alle i (0 <= i < n) umleiten?Wie man eine Pipe-Schleife in Bash

Zum Beispiel lassen Sie uns sagen, ich habe ein Programm square, die immer wieder eine Reihe liest und als druckt das Quadrat dieser Zahl, und ein Programm calc, die manchmal druckt eine Zahl, nach der es den Platz der Lage sein, erwartet zu lesen davon. Wie verbinde ich diese Programme so, dass, wenn calc eine Nummer, square Quadrate druckt, es an calc zurückgibt?

Edit: Ich sollte wahrscheinlich klarstellen, was ich mit "leicht" meine. Die benannte Pipe-/Fifo-Lösung ist eine, die tatsächlich funktioniert (und ich habe sie in der Vergangenheit benutzt), aber es erfordert tatsächlich eine Menge Arbeit, um sie richtig zu machen, wenn man sie mit einer Bash-Pipe vergleicht. (Sie müssen einen noch nicht vorhandenen Dateinamen abrufen, eine Pipe mit diesem Namen erstellen, die Pipe-Schleife ausführen und die Named Pipe bereinigen.) Stellen Sie sich vor, Sie könnten prog1 | prog2 nicht mehr schreiben und müssten immer Named Pipes verwenden, um Programme zu verbinden .

Ich bin auf der Suche nach etwas, das fast so einfach ist wie das Schreiben einer "normalen" Pfeife. Zum Beispiel wäre so etwas wie { prog1 | prog2 } >&0 großartig.

Antwort

5

Dies ist eine sehr interessante Frage. Ich erinnere mich (vage) an eine Aufgabe, die vor 17 Jahren in der Schule sehr ähnlich war. Wir mussten ein Array von Pipes erstellen, wo unser Code Dateihandles für die Eingabe/Ausgabe jeder Pipe bekommen würde. Dann würde der Code verzweigen und die nicht verwendeten Dateihandles schließen.

Ich denke, Sie könnten etwas Ähnliches mit Named Pipes in bash tun. Verwenden Sie mknod oder mkfifo, um eine Reihe von Pipes mit eindeutigen Namen zu erstellen, die Sie referenzieren können, und verzweigen Sie dann Ihr Programm.

-1

Ich bezweifle, dass sh/bash es tun kann. ZSH wäre eine bessere Wette, mit seinen MULTIOS- und Koproc-Funktionen.

+1

Können Sie ein Beispiel über Zsh geben? Ich bin daran interessiert. –

1

Benannte Rohre.

eine Reihe von fifos erstellen, mit mkfifo

dh Fifo0, FIFO1

Dann jeden Prozess in Sicht zu den Rohren Sie anhängen:

processn < Fifo (n-1)> FIFOn

13

Eine Named Pipe kann es tun:

$ mkfifo outside 
$ <outside calc | square >outside & 
$ echo "1" >outside ## Trigger the loop to start 
+0

Können Sie die Zeile " outside &" erklären? Ich bin unsicher über draußen. –

+0

Sie sind Standard-Shell-Weiterleitungen - von 'außerhalb' einlesen und nach 'außen' ausgeben. Draußen ist ein Fifo, und alles, was darauf geschrieben steht, kommt auf der Leseseite heraus. –

+0

Ich habe diesen Code versucht, aber es funktioniert nicht. Es scheint, dass diese Zeile: ' außen & 'endet sofort. – RnMss

24

Nachdem ich gestern einige Zeit damit verbracht habe, stdout zu stdin umzuleiten, endete ich mit der folgenden Methode. Es ist nicht wirklich nett, aber ich denke, ich bevorzuge es gegenüber der Named Pipe/Fifo-Lösung.

read | { P0 | ... | P(n-1); } >/dev/fd/0 

Die { ... } >/dev/fd/0 ist stdout Umleitung für die Rohrfolge als Ganzes stdin (d.h. sie leitet die Ausgabe von P (n-1) mit dem Eingang der P0). Mit >&0 oder etwas Ähnliches funktioniert nicht; Dies liegt wahrscheinlich daran, dass bash annimmt, dass 0 schreibgeschützt ist, während es nichts dagegen hat, an /dev/fd/0 zu schreiben.

Der ursprüngliche read -pipe ist notwendig, weil ohne es sowohl der Eingabe- als auch der Ausgabedateideskriptor das gleiche PTS-Gerät sind (zumindest auf meinem System) und die Weiterleitung keine Wirkung hat. (Das PTS-Gerät funktioniert nicht wie eine Pipe; das Schreiben auf es bringt die Dinge auf den Bildschirm.) Durch die Eingabe der { ... } einer normalen Pipe hat die Redirect den gewünschten Effekt.

Um mit meinem calc/square Beispiel zu illustrieren:

function calc() { 
    # calculate sum of squares of numbers 0,..,10 

    sum=0 
    for ((i=0; i<10; i++)); do 
    echo $i     # "request" the square of i 

    read ii     # read the square of i 
    echo "got $ii" >&2   # debug message 

    let sum=$sum+$ii 
    done 

    echo "sum $sum" >&2   # output result to stderr 
} 

function square() { 
    # square numbers 

    read j       # receive first "request" 
    while [ "$j" != "" ]; do 
    let jj=$j*$j 
    echo "square($j) = $jj" >&2 # debug message 

    echo $jj      # send square 

    read j      # receive next "request" 
    done 
} 

read | { calc | square; } >/dev/fd/0 

der obige Code Ausführen der folgenden Ausgabe gibt:

square(0) = 0 
got 0 
square(1) = 1 
got 1 
square(2) = 4 
got 4 
square(3) = 9 
got 9 
square(4) = 16 
got 16 
square(5) = 25 
got 25 
square(6) = 36 
got 36 
square(7) = 49 
got 49 
square(8) = 64 
got 64 
square(9) = 81 
got 81 
sum 285 

Natürlich ist diese Methode durchaus ein bisschen wie ein Hack ist. Vor allem der read Teil hat einen unerwünschten Nebeneffekt: Die Beendigung der "echten" Pipe-Schleife führt nicht zur Beendigung des Ganzen. Ich konnte nichts besseres als read denken, da es scheint, dass Sie nur feststellen können, dass die Pipe-Schleife beendet wurde, indem Sie versuchen, etwas zu schreiben.

+1

Schöne Lösung. Ich musste etwas Ähnliches tun, indem ich Netcat in einer Schleife verwendete und den "Lese" -Seiteffekt umging, indem ich seine Eingabe mit einem "Echo" schloss. Am Ende war es so: echo | lesen | {P0 | ... | P (n-1); }>/dev/fd/0 –

+0

Statt 'echo | read' könnte man einen Befehl verwenden, der sofort endet, wie': '(' true'), z. ': | {cmd | cmd>/dev/fd/0} '.Beispiel: ': | {nc -lp 5000>/dev/fd/0; } 'ist ein einfacher Echo-Server, der korrekt auf dem Client-EOF endet. – regnarg

-2

Ein Befehlsstapel kann als String aus einem Array beliebiger Befehle zusammengesetzt und mit eval ausgewertet werden. Das folgende Beispiel gibt das Ergebnis 65536.

function square() 
{ 
    read n 
    echo $((n*n)) 
} # ---------- end of function square ---------- 

declare -a commands=('echo 4' 'square' 'square' 'square') 

#------------------------------------------------------------------------------- 
# build the command stack using pipes 
#------------------------------------------------------------------------------- 
declare  stack=${commands[0]} 

for ((COUNTER=1; COUNTER<${#commands[@]}; COUNTER++)); do 
    stack="${stack} | ${commands[${COUNTER}]}" 
done 

#------------------------------------------------------------------------------- 
# run the command stack 
#------------------------------------------------------------------------------- 
eval "$stack" 
+1

Ich glaube nicht, dass Sie die Frage beantworten. – reinierpost

3

Meine Lösungen verwendet pipexec (Die meisten der Implementierung Funktion von Ihrer Antwort kommt):

square.sh

function square() { 
    # square numbers 

    read j       # receive first "request" 
    while [ "$j" != "" ]; do 
    let jj=$j*$j 
    echo "square($j) = $jj" >&2 # debug message 

    echo $jj      # send square 

    read j      # receive next "request" 
    done 
} 

square [email protected] 

calc.sh

function calc() { 
    # calculate sum of squares of numbers 0,..,10 

    sum=0 
    for ((i=0; i<10; i++)); do 
    echo $i     # "request" the square of i 

    read ii     # read the square of i 
    echo "got $ii" >&2   # debug message 

    let sum=$sum+$ii 
done 

echo "sum $sum" >&2   # output result to stderr 
} 

calc [email protected] 

Der Befehl

pipexec [ CALC /bin/bash calc.sh ] [ SQUARE /bin/bash square.sh ] \ 
    "{CALC:1>SQUARE:0}" "{SQUARE:1>CALC:0}" 

Der Ausgang (wie in Ihrer Antwort)

square(0) = 0 
got 0 
square(1) = 1 
got 1 
square(2) = 4 
got 4 
square(3) = 9 
got 9 
square(4) = 16 
got 16 
square(5) = 25 
got 25 
square(6) = 36 
got 36 
square(7) = 49 
got 49 
square(8) = 64 
got 64 
square(9) = 81 
got 81 
sum 285 

Kommentar: pipexec wurde entwickelt, Prozesse zu starten und beliebige Rohre dazwischen zu bauen. Da bash-Funktionen nicht als Prozesse behandelt werden können, müssen die Funktionen in separaten Dateien vorliegen und eine separate bash verwendet werden.

Verwandte Themen