Bash-Subshell-Erstellung mit geschweiften Klammern

31

Nach dieser , eine Liste von Befehlen zwischen geschweiften Klammern Platzieren bewirkt , dass die Liste in dem aktuellen Shell Kontext ausgeführt werden. Es wird keine Subshell erstellt .

Verwenden Sie ps, um dies in Aktion zu sehen

Dies ist die Prozesshierarchie für eine Prozesspipeline, die direkt in der Befehlszeile ausgeführt wird. 4398 ist die PID für die Login-Shell:

sleep 2 | ps -H;
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29696 pts/23   00:00:00   sleep
   29697 pts/23   00:00:00   ps

Es folgt nun die Prozesshierarchie für eine Prozesspipeline zwischen geschweiften Klammern, die direkt in der Befehlszeile ausgeführt wird. 4398 ist die PID für die Login-Shell. Dies ähnelt der obigen Hierarchie, um zu beweisen, dass alles im aktuellen Shell-Kontext ausgeführt wird :

{ sleep 2 | ps -H; }
   PID TTY          TIME CMD
    4398 pts/23   00:00:00 bash
    29588 pts/23   00:00:00   sleep
    29589 pts/23   00:00:00   ps

Dies ist nun die Prozesshierarchie, wenn das sleepin der Pipeline befindliche Element selbst in geschweiften Klammern steht (also insgesamt zwei Ebenen von Klammern).

{ { sleep 2; } | ps -H; }
  PID TTY          TIME CMD
   4398 pts/23   00:00:00 bash
   29869 pts/23   00:00:00   bash
   29871 pts/23   00:00:00     sleep
   29870 pts/23   00:00:00   ps

Warum muss basheine Subshell erstellt werden, um sleepim dritten Fall ausgeführt zu werden, wenn in der Dokumentation angegeben ist, dass Befehle zwischen geschweiften Klammern im aktuellen Shell-Kontext ausgeführt werden?

iruvar
quelle
Interessant, ich würde es vermuten, weil im dritten Fall die innere Gruppe Teil der Pipeline ist und daher in einer Unterschale ausgeführt wird, zB wie jeder andere Funktionsaufruf, der Teil der Pipeline ist. Macht das Sinn?
Miroslav Koškár
2
Ich würde nicht sagen "Die Shell muss", nur weil es tut ... Pipelines werden nicht im Shell-Kontext ausgeführt. Wenn die Pipeline nur aus externen Befehlen besteht, reicht es aus, Unterprozesse zu erstellen. { sleep 2 | command ps -H; }
Hauke ​​Laging

Antworten:

26

In einer Pipeline werden alle Befehle gleichzeitig ausgeführt (wobei ihre stdout / stdin durch Pipes verbunden sind), also in unterschiedlichen Prozessen.

Im

cmd1 | cmd2 | cmd3

Alle drei Befehle werden in unterschiedlichen Prozessen ausgeführt, sodass mindestens zwei von ihnen in einem untergeordneten Prozess ausgeführt werden müssen. Einige Shells führen eine von ihnen im aktuellen Shell-Prozess aus (wenn sie wie readdie Pipeline erstellt wurden oder der letzte Befehl des Skripts ist), führen bashsie jedoch alle in einem eigenen separaten Prozess aus (mit Ausnahme der lastpipeOption in neueren bashVersionen und unter bestimmten Bedingungen) ).

{...}gruppen befehle. Wenn diese Gruppe Teil einer Pipeline ist, muss sie wie ein einfacher Befehl in einem separaten Prozess ausgeführt werden.

Im:

{ a; b "$?"; } | c

Wir brauchen eine Shell, um zu bewerten, dass a; b "$?"es sich um einen separaten Prozess handelt, also brauchen wir eine Subshell. Die Shell kann optimiert werden, indem nicht nachgefragt wird, bda dies der letzte Befehl ist, der in dieser Gruppe ausgeführt wird. Einige Muscheln tun es, aber anscheinend nicht bash.

Stéphane Chazelas
quelle
" Alle drei Befehle werden in unterschiedlichen Prozessen ausgeführt, sodass mindestens zwei von ihnen in einer Subshell ausgeführt werden müssen. " Warum ist in diesem Fall eine Subshell erforderlich? Kann die übergeordnete Shell keine Baumprozesse erzeugen? Oder, wenn Sie sagen " müssen in einer Subshell laufen ", bedeutet dies, dass die Shell sich selbst verzweigt und dann für jeden cmd1 cmd2 und cmd3 ausführt? Wenn ich dies ausführe, bash -c "sleep 112345 | cat | cat "sehe ich nur eine Bash erstellt und dann 3 Kinder dazu, ohne irgendwelche anderen verschachtelten Sub-Bashs.
Hakan Baba
" Wir brauchen eine Shell, um zu bewerten, dass a; b" $? "Ein separater Prozess ist, also brauchen wir eine Subshell. " Könnten Sie auch die Argumentation erweitern? Warum brauchen wir ein Unterrad, um das zu verstehen? Was braucht es, um das zu verstehen? Ich gehe davon aus, dass ein Parsing erforderlich ist, aber was noch? . Kann die übergeordnete Shell nicht analysieren a; b "$?"? Gibt es wirklich ein grundlegendes Bedürfnis nach einem Unterrad, oder handelt es sich möglicherweise um eine Entwurfsentscheidung / -implementierung auf Basis von Bash?
Hakan Baba
@ HakanBaba, ich habe das zu "Kind-Prozess" geändert, um mögliche Verwirrung zu vermeiden.
Stéphane Chazelas
1
@HakanBaba, das Parsen wird im übergeordneten Objekt durchgeführt (der Prozess, der den Code liest, der den Interpreter ausgeführt hat, sofern dieser Code nicht übergeben wurde eval), aber die Auswertung (den ersten Befehl ausführen, darauf warten, den zweiten ausführen) getan in dem Kind, das stdout mit dem Rohr verbunden hat.
Stéphane Chazelas
in { sleep 2 | ps -H; }der übergeordneten bash sieht man sleep 2, dass ein fork / exec benötigt. Aber in { { sleep 2; } | ps -H; }der übergeordneten Bash sieht man { sleep 2; }mit anderen Worten Bash-Code. Es sieht so aus, als ob das übergeordnete Element die Abzweigung / Ausführung für verarbeiten kann, sleep 2aber rekursiv eine neue Bash erzeugt, um den ermittelten Bash-Code zu verarbeiten. Das ist mein Verständnis, macht es Sinn?
Hakan Baba
19

Das Verschachteln der geschweiften Klammern scheint darauf hinzudeuten, dass Sie eine zusätzliche Bereichsebene erstellen, die das Aufrufen einer neuen Sub-Shell erfordert. Sie können diesen Effekt mit der 2. Kopie von Bash in Ihrer ps -HAusgabe sehen.

Nur die in der ersten Ebene der geschweiften Klammern festgelegten Prozesse werden im Rahmen der ursprünglichen Bash-Shell ausgeführt. Alle geschachtelten geschweiften Klammern werden in ihrer eigenen Bash-Shell ausgeführt.

Beispiel

$ { { { sleep 20; } | sleep 20; } | ps -H; }
  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5012 pts/1    00:00:00   bash
 5014 pts/1    00:00:00     bash
 5016 pts/1    00:00:00       sleep
 5015 pts/1    00:00:00     sleep
 5013 pts/1    00:00:00   ps

Wenn | ps -Hwir die Mischung herausnehmen, damit wir die geschachtelten geschweiften Klammern sehen können, können wir ps auxf | lessin einer anderen Shell laufen .

saml     29190  0.0  0.0 117056  3004 pts/1    Ss   13:39   0:00  \_ bash
saml      5191  0.0  0.0 117056  2336 pts/1    S+   14:42   0:00  |   \_ bash
saml      5193  0.0  0.0 107892   512 pts/1    S+   14:42   0:00  |   |   \_ sleep 20
saml      5192  0.0  0.0 107892   508 pts/1    S+   14:42   0:00  |   \_ sleep 20
saml      5068  0.2  0.0 116824  3416 pts/6    Ss   14:42   0:00  \_ bash
saml      5195  0.0  0.0 115020  1272 pts/6    R+   14:42   0:00      \_ ps auxf
saml      5196  0.0  0.0 110244   880 pts/6    S+   14:42   0:00      \_ less

Aber warte, es gibt noch mehr!

Wenn Sie jedoch die Rohre herausnehmen und diese Form eines Befehls verwenden, sehen wir, was Sie tatsächlich erwarten würden:

$ { { { sleep 10; } ; { sleep 10; } ; sleep 10; } } | watch "ps -H"

Jetzt erhalten wir im daraufhin angezeigten Überwachungsfenster alle 2 Sekunden eine Aktualisierung der aktuellen Ereignisse:

Hier ist der erste sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5678 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5681 pts/1    00:00:00     watch
 5682 pts/1    00:00:00       ps

Hier ist der zweite sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5691 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5694 pts/1    00:00:00     watch
 5695 pts/1    00:00:00       ps

Hier ist der dritte sleep 10:

  PID TTY          TIME CMD
29190 pts/1    00:00:00 bash
 5676 pts/1    00:00:00   bash
 5704 pts/1    00:00:00     sleep
 5677 pts/1    00:00:00   watch
 5710 pts/1    00:00:00     watch
 5711 pts/1    00:00:00       ps

Beachten Sie, dass alle drei Schlafzustände, die bei verschiedenen Verschachtelungsstufen von geschweiften Klammern aufgerufen werden, tatsächlich innerhalb der PID 5676 von Bash bleiben. Ich glaube also, dass Ihr Problem durch die Verwendung von selbst verursacht wird| ps -H .

Schlussfolgerungen

Die Verwendung von | ps -H(dh der Pipe) führt zu einer zusätzlichen Unterschale. Verwenden Sie diese Methode also nicht, wenn Sie versuchen, die aktuellen Ereignisse abzufragen.

slm
quelle
"Nur die in der ersten Ebene der geschweiften Klammern festgelegten Prozesse werden im Rahmen der ursprünglichen Bash-Shell ausgeführt."
Xealits
@xealits - ist das ein Follow-up, das Sie mich fragen?
slm
@slm es ist nur die Betonung des Hauptpunktes der Antwort, wie ich es gesehen habe. Befehle in der ersten Ebene von geschweiften Klammern werden in der aktuellen Shell ausgeführt, geschachtelte geschweifte Klammern erstellen neue Shells. Klammern unterscheiden sich darin, auf der ersten Ebene sofort eine Unterschale zu erstellen. Wenn ich es falsch verstanden habe - korrigiere mich. Aber, wie andere sagen, hat die Ausgangsfrage auch Pipelines. So die Schaffung von getrennten Prozessen. Geschweifte Klammern müssen eine Muschel bilden, wenn sie für einen separaten Vorgang verwendet werden. Wahrscheinlich ist es der Grund für das fragliche Verhalten.
Xealits
Nach dem erneuten Lesen Ihres Beitrags habe ich mich geirrt. Die Verschachtelungsanweisung betrifft nur den Fall von Pipelines. Die geschweiften Klammern erzeugen also nie neue Muscheln, es sei denn, Sie wickeln einen separaten Prozess damit ein - dann müssen sie.
Xealits
@xealits - das stimmt.
slm
7

Ich werde Ergebnisse meiner Tests schreiben, die mich zu dem Schluss führt , dass bash eine Unterschale für eine Gruppe Befehl macht , wenn und nur wenn es ein Teil der Pipeline ist, die ähnlich ist , wie wenn man würde eine Funktion aufrufen , die auch genannt werden würde in Unterschale.

$ { A=1; { A=2; sleep 2; } ; echo $A; }
2

$ { A=1; { A=2; sleep 2; } | sleep 1; echo $A; }
1
Miroslav Koškár
quelle
Mein A zeigt das auch.
SLM