
Passiert irgendetwas Symbolisches, wenn ich Bash-Befehle über eine Pipe verkette, oder läuft alles nach dem Prinzip „Berechnen-Weitergeben-Berechnen-Weitergeben“ ab?
Beispielsweise head t.txt -n 5 | tail -n 2
wird in head t.txt -n 5
berechnet und dann tail -n 2
darüber ausgeführt. Oder gibt es zuerst eine Abstraktion, um der Shell mitzuteilen, dass die Zeilen 3 bis 5 gelesen werden sollen? In diesem Beispiel macht das vielleicht keinen Unterschied, aber ich denke, in anderen Szenarien kann das der Fall sein.
Antwort1
Die Shell verwendet den pipe(2)
Systemaufruf, um im Kernel einen begrenzten Puffer mit zwei Dateideskriptoren zu erstellen, von denen einer es Prozessen ermöglicht, in den Puffer zu schreiben, und der andere es Prozessen ermöglicht, aus dem Puffer zu lesen.
Betrachten Sie einen einfachen Fall:
$ p1 | p2
In diesem Fall erstellt die Shell konzeptionell die oben erwähnte Pipe, fork()
s, das Kind verbindet seinen Standardausgabestrom mit dem Schreibende der Pipe, dann das Kind exec()
s p1
. Als nächstes fork()
s die Shell wieder, das Kind verbindet seinen Standardeingabestrom mit dem Lesende der Pipe, dann das Kind exec()
s p2
. (Ich sagekonzeptionellweil Shells die Dinge zwar in unterschiedlicher Reihenfolge erledigen, die Idee aber dieselbe ist.)
An diesem Punkt werden p1
und p2
gleichzeitig ausgeführt. p1
schreibt in die Pipe und der Kernel kopiert die geschriebenen Daten in den Puffer. p2
liest aus der Pipe und der Kernel kopiert die gelesenen Daten aus dem Puffer. Wenn die Pipe voll ist, blockiert der Kernel p1
seinen Aufruf von write()
, bis p2
etwas aus der Pipe gelesen wird und so Speicherplatz frei wird. Wenn die Pipe leer ist, blockiert der Kernel p2
seinen Aufruf von , read()
bis p1
weitere Daten in die Pipe geschrieben werden.
Antwort2
Von den beiden Modellen, die Sie vorschlagen, kommt compute-pass-compute-pass dem am nächsten. Die Shell verbindet lediglich die Prozesse. Sie weiß nicht, was sie tun.
Außer, Die Reihenfolge der Ausführung ist nicht definiert. Sie werden effektiv gleichzeitig ausgeführt. Allerdings muss der linke Befehl Bytes ausgeben, bevor der rechte sie eingibt. Daten fließen von links nach rechts. Daten fließen vom ersten Befehl aus seinem Standardausgang, dann fließen sie in den Standardeingang des nächsten Prozesses, wo sie verarbeitet werden, dann kommen sie aus ihrem Standardausgang heraus, wo sie an einen anderen Prozess weitergeleitet werden können, usw., usw., usw.
Wenn keine Umleitung >
, <
, usw. erfolgt oder aus einer Datei gelesen wird, sieht es so aus.
┌───────────┐ ┌───────────┐ ┌─────────────┐
Terminal⇨│Process one│⇨│Process two│⇨│Process Three│⇨Terminal
└───────────┘ └───────────┘ └─────────────┘