Regel zum Aufrufen von Subshell in Bash?

24

Ich scheine die Bash-Regel zum Erstellen einer Subshell falsch zu verstehen. Ich dachte, Klammern erzeugen immer eine Subshell, die als eigener Prozess abläuft.

Dies scheint jedoch nicht der Fall zu sein. In Code-Snippet A (siehe unten) wird der zweite sleepBefehl nicht in einer separaten Shell ausgeführt (wie von pstreeeinem anderen Terminal festgelegt). In Code-Snippet B wird der zweite sleepBefehl jedoch in einer separaten Shell ausgeführt. Der einzige Unterschied zwischen den Snippets besteht darin, dass das zweite Snippet zwei Befehle in Klammern enthält.

Könnte jemand bitte die Regel erklären, wann Subshells erstellt werden?

CODE SNIPPET A:

sleep 5
(
sleep 5
)

CODE SNIPPET B:

sleep 5
(
x=1
sleep 5
)
schüchtern
quelle

Antworten:

20

Die Klammern beginnen immer eine Subshell. Was passiert ist, dass Bash erkennt, dass sleep 5es sich um den letzten Befehl handelt, der von dieser Subshell ausgeführt wird, und execstatt fork+ aufruft exec. Der sleepBefehl ersetzt die Subshell im selben Prozess.

Mit anderen Worten ist der Basisfall:

  1. ( … )Erstellen Sie eine Unterschale. Der ursprüngliche Prozess ruft forkund auf wait. Im Subprozess, der eine Subshell ist:
    1. sleepist ein externer Befehl, der einen Unterprozess des Unterprozesses erfordert. Die Subshell ruft forkund auf wait. Im Unterprozess:
      1. Der Unterprozess führt den externen Befehl aus → exec.
      2. Schließlich wird der Befehl beendet → exit.
    2. wait wird in der Unterschale vervollständigt.
  2. wait wird im ursprünglichen Prozess abgeschlossen.

Die Optimierung ist:

  1. ( … )Erstellen Sie eine Unterschale. Der ursprüngliche Prozess ruft forkund auf wait. Im Subprozess, der eine Subshell ist, bis er aufruft exec:
    1. sleep ist ein externer Befehl und das Letzte, was dieser Prozess tun muss.
    2. Der Unterprozess führt den externen Befehl aus → exec.
    3. Schließlich wird der Befehl beendet → exit.
  2. wait wird im ursprünglichen Prozess abgeschlossen.

Wenn Sie nach dem Aufruf von noch etwas hinzufügen sleep, muss die Subshell beibehalten werden, damit diese Optimierung nicht durchgeführt werden kann.

Wenn Sie vor dem Aufruf von etwas anderes hinzufügen sleep, könnte die Optimierung durchgeführt werden (und ksh tut es), aber bash tut es nicht (es ist bei dieser Optimierung sehr konservativ).

Gilles 'SO - hör auf böse zu sein'
quelle
Die Subshell wird durch Aufrufen erstellt, forkund der untergeordnete Prozess wird durch Aufrufen erstellt (um externe Befehle auszuführen)fork + exec . Aber Ihr erster Absatz legt nahe, dass dies auch fork + execals Subshell bezeichnet wird. Was mache ich hier falsch?
Haccks
1
@haccks fork+ execwird nicht für die Subshell aufgerufen, sondern für den externen Befehl. Ohne Optimierung gibt es einen forkAufruf für die Subshell und einen weiteren für den externen Befehl. Ich habe meiner Antwort eine detaillierte Ablaufbeschreibung hinzugefügt.
Gilles 'SO- hör auf böse zu sein'
Vielen Dank für das Update. Jetzt erklärt es besser. Daraus kann ich schließen, dass im Fall von (...)(im Basisfall) ein Aufruf execvon möglicherweise vorliegt oder nicht, abhängig davon, ob die Subshell einen externen Befehl ausführen muss, während im Fall der Ausführung eines externen Befehls ein Aufruf vorliegen muss fork + exec.
Haccks
Noch eine Frage: Funktioniert diese Optimierung nur für Subshell oder kann sie für einen Befehl wie datein einer Shell durchgeführt werden?
Haccks
@haccks Ich verstehe die Frage nicht. Bei dieser Optimierung wird als letztes, was ein Shell-Prozess tut, ein externer Befehl aufgerufen. Es ist nicht auf Subshells beschränkt: Vergleichen strace -f -e clone,execve,write bash -c 'date'undstrace -f -e clone,execve,write bash -c 'date; true'
Gilles '
4

Im Advanced Bash Programming Guide :

"Im Allgemeinen wird durch einen externen Befehl in einem Skript ein Unterprozess abgebrochen, während dies bei einem integrierten Bash-Befehl nicht der Fall ist. Aus diesem Grund werden integrierte Befehle schneller ausgeführt und benötigen weniger Systemressourcen als die entsprechenden externen Befehle."

Und etwas weiter unten:

Msgstr "Eine in Klammern eingebettete Befehlsliste wird als Subshell ausgeführt."

Beispiele:

[root@talara test]# echo $BASHPID
10792
[root@talara test]# (echo $BASHPID)
4087
[root@talara test]# (echo $BASHPID)
4088
[root@talara test]# (echo $BASHPID)
4089

Beispiel mit OPs-Code (mit kürzeren Schlafzeiten, weil ich ungeduldig bin):

echo $BASHPID

sleep 2
(
    echo $BASHPID
    sleep 2
    echo $BASHPID
)

Die Ausgabe:

[root@talara test]# bash sub_bash
6606
6608
6608
Tim
quelle
2
Danke für die Antwort Tim. Ich bin mir nicht sicher, ob es meine Frage vollständig beantwortet. Da "Eine in Klammern eingebettete Befehlsliste als Subshell ausgeführt wird", würde ich davon ausgehen, dass die zweite sleepin einer Subshell ausgeführt wird (möglicherweise während des Subshell-Prozesses, da es sich um einen integrierten und nicht um einen Subprozess der Subshell handelt). In jedem Fall hätte ich jedoch erwartet, dass eine Subshell existiert, dh ein Bash-Unterprozess unter dem übergeordneten Bash-Prozess. Für Snippet B oben scheint dies nicht der Fall zu sein.
schüchtern
Korrektur: Da sleepdies kein sleepintegrierter Vorgang zu sein scheint, würde ich erwarten, dass der zweite Aufruf in beiden Snippets in einem Unterprozess des Subshell-Prozesses ausgeführt wird.
schüchtern
@bashful Ich habe mir erlaubt, Ihren Code mit meiner $BASHPIDVariablen zu hacken . Leider hat Ihnen die Art und Weise, wie Sie es taten, nicht die ganze Geschichte erzählt, wie ich glaube. Siehe meine zusätzliche Ausgabe in der Antwort.
Tim
4

Eine zusätzliche Anmerkung zu @Gilles Antwort.

Wie Gilles sagte: The parentheses always start a subshell.

Die Zahlen, die eine solche Unterschale hat, könnten sich jedoch wiederholen:

$ (echo "$BASHPID and $$"; sleep 1)
2033 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2040 and 31679
$ (echo "$BASHPID and $$"; sleep 1)
2047 and 31679

Wie Sie sehen, wiederholt sich das $$ immer wieder, und das ist wie erwartet, weil (führen Sie diesen Befehl aus, um die richtige man bashZeile zu finden ):

$ LESS=+/'^ *BASHPID' man bash

BASHPID Wird
auf die Prozess-ID des aktuellen Bash-Prozesses erweitert. Dies unterscheidet sich von $$ unter bestimmten Umständen, z. B. bei Subshells, für die keine erneute Bash-Initialisierung erforderlich ist.

Das heißt: Wenn die Shell nicht neu initialisiert wird, ist das $$ dasselbe.

Oder damit:

$ LESS=+/'^ *Special Parameters' man bash

Spezielle Parameter
$ Wird auf die Prozess-ID der Shell erweitert. In einer () - Subshell wird sie auf die Prozess-ID der aktuellen Shell erweitert, nicht auf die Subshell.

Das $$ist die ID der aktuellen Shell (nicht der Subshell).


quelle
1
Ein
guter