Setzen Klammern den Befehl wirklich in eine Unterschale?

94

Nach dem, was ich gelesen habe, sollte ein Befehl in Klammern in einer Subshell ausgeführt werden, ähnlich wie bei der Ausführung eines Skripts. Wenn dies zutrifft, wie wird die Variable x angezeigt, wenn x nicht exportiert wird?

x=1

Das Ausführen (echo $x)in der Befehlszeile führt zu 1

Das Ausführen echo $xeines Skripts führt erwartungsgemäß zu nichts

Igorio
quelle

Antworten:

134

Eine Subshell beginnt als nahezu identische Kopie des ursprünglichen Shell-Prozesses. Unter der Haube ruft die Shell den forkSystemaufruf 1 auf , der einen neuen Prozess erstellt, dessen Code und Speicher Kopien 2 sind . Wenn die Subshell erstellt wird, gibt es nur sehr wenige Unterschiede zwischen ihr und ihrem übergeordneten Element. Insbesondere haben sie die gleichen Variablen. Sogar die $$Spezialvariable behält in Subshells den gleichen Wert: Es ist die Prozess-ID der Original-Shell. Ebenso $PPIDist die PID des übergeordneten Elements der ursprünglichen Shell.

Einige Shells ändern einige Variablen in der Subshell. Bash setzt BASHPIDauf die PID des Shell-Prozesses, die sich in Subshells ändert. Bash, zsh und mksh sorgen dafür $RANDOM, dass im Parent und in der Subshell unterschiedliche Werte ausgegeben werden. Abgesehen von solchen Sonderfällen haben alle Variablen in der Subshell denselben Wert wie in der ursprünglichen Shell, denselben Exportstatus, denselben Nur-Lese-Status usw. Alle Funktionsdefinitionen, Aliasdefinitionen, Shell-Optionen und andere Einstellungen werden ebenfalls übernommen.

Eine von erstellte Subshell (…)hat dieselben Dateideskriptoren wie ihr Ersteller. Einige andere Methoden zum Erstellen von Subshells ändern einige Dateideskriptoren, bevor Benutzercode ausgeführt wird. Beispielsweise verläuft die linke Seite einer Rohrleitung in einer Unterschale 3, deren Standardausgang mit der Rohrleitung verbunden ist. Die Subshell startet auch mit demselben aktuellen Verzeichnis, derselben Signalmaske usw. Eine der wenigen Ausnahmen ist, dass Subshells keine benutzerdefinierten Traps erben: Ignorierte Signale ( ) bleiben in der Subshell ignoriert, andere Traps ( SIGNAL ) werden jedoch zurückgesetzt zur Standardaktion 4 .trap '' SIGNALtrap CODE

Eine Subshell unterscheidet sich somit von der Ausführung eines Skripts. Ein Skript ist ein separates Programm. Dieses separate Programm kann zufällig auch ein Skript sein, das vom selben Interpreter wie das übergeordnete Programm ausgeführt wird, aber dieses Zusammentreffen verleiht dem separaten Programm keine besondere Sichtbarkeit für interne Daten des übergeordneten Programms. Nicht exportierte Variablen sind interne Daten. Wenn der Interpreter für das untergeordnete Shell-Skript ausgeführt wird , werden diese Variablen nicht angezeigt. Exportierte Variablen, dh Umgebungsvariablen, werden an ausgeführte Programme übertragen.

Somit:

x=1
(echo $x)

wird gedruckt, 1weil die Subshell eine Replikation der Shell ist, die sie erzeugt hat.

x=1
sh -c 'echo $x'

zufällig wird eine Shell als untergeordneter Prozess einer Shell ausgeführt, aber die xin der zweiten Zeile hat keine größere Verbindung mit der xin der zweiten Zeile als in

x=1
perl -le 'print $x'

oder

x=1
python -c 'print x'

1 Eine Ausnahme bildet die ksh93Schale, in der das Gabeln optimiert und die meisten Nebenwirkungen emuliert werden.
2 Semantisch handelt es sich um Kopien. Aus Sicht der Implementierung wird viel geteilt.
3 Für die rechte Seite kommt es auf die Schale an.
4 Wenn Sie dies testen, beachten Sie, dass Dinge wie$(trap) die Fallen der ursprünglichen Shell melden können. Beachten Sie auch, dass viele Muscheln in Eckfällen mit Fallen Fehler aufweisen. Zum Beispiel stellt Ninjalj fest, dass ab Bash 4.3 bash -x -c 'trap "echo ERR at \$BASH_SUBSHELL \$BASHPID" ERR; set -E; false; echo one subshell; (false); echo two subshells; ( (false) )'die ERRTrap von der verschachtelten Subshell in dem Fall "Zwei Subshells" ausgeführt wird, aber nicht die ERRTrap von der Zwischen-Subshell - set -EOption sollte die Propagierung durchführenERRTrap auf alle Subshells, aber die Zwischen-Subshell ist optimiert und daher nicht dazu da, den ERRTrap auszuführen .

Gilles
quelle
2
@ Kusalananda No. ( x=out; (x=in; echo $x))
Gilles
2
@ flow2k Dies ist die Erweiterungsreihenfolge für Dinge, die auf derselben Ebene geschehen. Sie müssen aber auch berücksichtigen, wie sich Expansion und Evaluierung vermischen. Wenn für die Erweiterung die Bewertung eines verschachtelten Konstrukts erforderlich ist, wird zuerst das innere Konstrukt bewertet. So muss zum Beispiel zur Auswertung echo $(x=2; echo $x)das Fragment $(x=2; echo $x)erweitert werden. Dies erfordert die Auswertung des Befehls x=2; echo $x. Die Erweiterung von $xgeschieht während dieser Auswertung, nachdem das Teil ausgewertet wurde x=2.
Gilles
2
@ flow2k Es gibt keine Reihenfolge zwischen Parametererweiterung und Befehlssubstitution. Beachten Sie, dass in diesem Satz Semikolons verwendet werden, um die Erweiterungsschritte zu trennen. Parametererweiterung und Befehlssubstitution befinden sich jedoch in derselben durch Semikolons getrennten Klausel (ja, sie ist subtil). Die Reihenfolge ist wichtig , wenn eines der Teile eine Nebenwirkung hat , die den anderen Teil wirkt, beispielsweise (mit xungesetzt) echo $(echo foo >somefile)${x-$(cat somefile)}oder echo $(echo $x),${x=1}.
Gilles
1
@ Gilles; Ich bin verwirrt. Wenn sich eine Subshell von der Ausführung eines Skripts unterscheidet, warum heißt es dann so: Wenn Sie ein Shell-Skript ausführen, wird ein neuer Prozess gestartet, eine Subshell. ? Außerdem soll eine Subshell-Umgebung als Duplikat der Shell-Umgebung erstellt werden . Daher wird ./file in einer Subshell-Umgebung ausgeführt und sollte daher Shell-Parameter erben, die durch Variablenzuweisung festgelegt werden.
Haccks
2
@haccks Die Definition im ABS ist eine Annäherung und keine sehr gute. Die Beispiele sind gut, aber die ersten beiden Zeilen dieser Seite sind so stark vereinfacht, dass sie falsch sind. Wenn Sie ein Skript von einem anderen Skript aus ausführen, wird ein neuer Prozess gestartet , bei dem es sich nicht um eine Subshell handelt. In der SUS sind die Definitionen korrekt (aber nicht immer leicht zu verstehen). ./filewird nicht in einer Subshell ausgeführt. Siehe auch unix.stackexchange.com/q/261638 und unix.stackexchange.com/a/157962
Gilles
15

Ja, natürlich wird, wie in der gesamten Dokumentation angegeben, ein Befehl in Klammern in einer Subshell ausgeführt.

Die Subshell erbt eine Kopie aller übergeordneten Variablen. Der Unterschied besteht darin, dass alle Änderungen, die Sie in der Subshell vornehmen, nicht auch in der übergeordneten Shell vorgenommen werden.

Die ksh-Manpage macht dies etwas klarer als die bash-Manpage:

man ksh:

Ein Befehl in Klammern wird in einer Sub-Shell ausgeführt, ohne nicht exportierte Variablen zu entfernen.

man bash:

(Liste)

list wird in einer Subshell-Umgebung ausgeführt (siehe COMMAND EXECUTION ENVIRONMENT unten). Variablenzuweisungen und integrierte Befehle, die sich auf die Umgebung der Shell auswirken, bleiben nach Abschluss des Befehls nicht wirksam.

BEFEHL AUSFÜHRUNGSUMGEBUNG

Die Shell verfügt über eine Ausführungsumgebung, die aus folgenden Elementen besteht: [...] Shell-Parameter, die durch Variablenzuweisung festgelegt werden.
Die Befehlsersetzung, Befehle in Klammern und asynchrone Befehle werden in einer Subshell-Umgebung aufgerufen, die ein Duplikat der Shell-Umgebung ist. [...]

Mikel
quelle
3
Dies steht im Gegensatz zu When a simple command other than a builtin or shell function is to be executed, it is invoked in a separate execution environment that consists of the following.dem Punkt: · shell variables and functions marked for export, along with variables exported for the command, passed in the environment(aus demselben man bashAbschnitt), der erklärt, warum ein echo $x-script nichts druckt, wenn xes nicht exportiert wird.
Johan E