2016-04-27 3 views
4

Ich versuche, einen Unterschied zwischen Shell-Subprozessaufruf mit runden Klammern und geschweiften Klammern zu verstehen. Ich dachte, dass geschweifte Klammern einen Unterprozess nicht starten, aber es scheint, dass sie einen Unterprozess starten. Wie interagieren (...) und {...} mit der Subshell-Erstellung mit Pipes in bash?

#!/bin/sh 

a=1 
b=1 

(a=2;) | (a=3;) 
{ b=2; } | { b=3; } 

echo "a=$a" 
echo "b=$b" 

Dieses Skript druckt

a=1 
b=1 

So scheint es, dass alle Anrufungen innerhalb Subprozesse ausgeführt werden. Gibt es einen Unterschied zwischen ihnen in diesem Zusammenhang? Ich verstehe, dass, wenn ich && und || verwenden würde, dann {..} wird kein Subprozess starten, aber ich versuche zu verstehen, wie Pipes arbeiten.

+1

Nicht "alle Aufrufe", sondern "alle Pipelines". Und selbst das ist nicht ganz richtig - POSIX sh spezifiziert das Verhalten nicht, und einige Shells setzen unter Umständen eine Pipe-Komponente (erste, letzte oder solche) in den übergeordneten Prozess. –

+0

... schauen Sie sich zum Beispiel die Option 'lastpipe' in bash an, die bei aktivierter Pipeline' a '3' oder 'b = 3' Ihrer Pipelines in der Parent-Shell ausführt (nur unter bestimmten einschränkenden Umständen) in den Dokumenten definiert). –

+0

Aha, so '()' erzeugt immer einen Unterprozess und '{}' kann einen Unterprozess hervorbringen, wenn es erforderlich ist, oder kann den aktuellen Prozess wiederverwenden, wenn es möglich ist. Ist das korrekt? – vbezhenar

Antwort

5

Um zu zeigen, dass es die Pipeline selbst ist, der die Sub-Shell ist zu erzeugen, und dass die geschweiften Klammern werden diese so oder so nicht ändern:

#!/bin/bash 

echo "Base: $BASHPID" 
(echo "In(): $BASHPID") # This will differ from the base 
{ echo "In {}: $BASHPID"; } # This will match the base 

# In bash, these will both differ from the base 
echo "Pipeline, default config:" 
{ echo " X: $BASHPID" >&2; } | { echo " Y: $BASHPID" >&2; } 

# This is exactly the same without the {}s 
echo "Pipeline, no {}s, default config:" 
echo " X: $BASHPID" >&2 | echo " Y: $BASHPID" >&2 

# Only the former will differ from the base if running a new enough bash 
shopt -s lastpipe 
echo "Pipeline, lastpipe enabled:" 
{ echo " X: $BASHPID" >&2; } | { echo " Y: $BASHPID" >&2; } 

diese mit lokal laufenden bash 4.3, erhalte ich:

Base: 82811 
In(): 82812 
In {}: 82811 
Pipeline, default config: 
X: 82813 
Y: 82814 
Pipeline, no {}s, default config: 
X: 82815 
Y: 82816 
Pipeline, lastpipe enabled: 
Y: 82811 
X: 82817 

Beachten Sie, dass, da alle Pipelinekomponenten gleichzeitig ausgeführt werden, es keine definierte Reihenfolge gibt, bei der X oder Y zuerst ausgegeben wird; Bei aktivierter lastpipe wird jedoch die letzte Pipelinekomponente in einer Shell aufgerufen, die bereits ausgeführt wird (sie muss nicht vom Hauptprozess entfernt sein), wodurch die Wahrscheinlichkeit, dass zuerst in stdout geschrieben wird, geringfügig geändert wird.

1

Runde Klammern führen Befehle in einer Subshell aus. geschweiften Klammern führen Befehle in der aktuellen Shell:

$ echo $BASHPID; (TEST=test; echo "sub:$TEST;$BASHPID"; exit 1);\ 
> { echo "current:$TEST;$BASHPID;$?"; } 
2920 
sub:test;3700 
current:;2920;1 

Ein weiterer Unterschied besteht darin, dass die geschweiften Klammern erfordern Räume zwischen ihnen und den beiliegenden Befehle und ein Semikolon nach dem letzten, während runde Klammern nicht.

Dies ist alles dokumentiert unter der bash's man page, "Compound Befehle" Abschnitt.


Eine Ausnahme (oder besser gesagt, ein weiterer Aspekt) ist, wenn Sie Rohre verwenden:

command1 | command2 

In diesem Fall beiden Befehle in separat Subshells ausgeführt werden - unabhängig davon, dass sie sind (und das enthält zusammengesetzte Befehle). Der Abschnitt "Pipelines" der Manpage dokumentiert dies.

+0

"subshell" bedeutet in der Tat "subprocess" - wenn es keinen 'fork()' Aufruf gibt, ist es keine Subshell. (Zugegeben, es gibt keinen 'execv()' - Familienaufruf, was ein kritischer Unterschied zwischen Subshells und anderen, konventionelleren Arten von Subprozessen ist. –

+0

@CharlesDuffy Nun, Sie sehen, dass die gemeldete PID die gleiche ist, nicht wahr? Das hat mich auch überrascht. Die wichtige Sache von "subshell" ist, dass es eine separate Execution-Umgebung ist. –

+2

'$$' ist nicht deine wahre PID in einer Subshell. Verwenden Sie '$ BASHPID' dafür, und Sie werden sehen, dass die PIDs nicht ** tatsächlich ** gleich sind. –

3

{ ... } erzeugt keine Untershell. Was Sie sehen, ist aufgrund der Tatsache, dass Sie | zwischen 2 geschweiften Liste Befehle verwenden.

$> b=1 

$> echo $BASHPID 
4401 

$> { echo "X. $BASHPID"; b=2; } | { echo "Y. $BASHPID"; b=3; } 
Y. 46902 

$> echo $BASHPID 
4401 

$> declare -p b 
declare -- b="1" 

Sie können sehen, dass { echo "Y. $BASHPID"; b=3; } daher in einer anderen Unterschale ausgeführt wird, um b vorgenommenen Änderungen werden nicht in aktuell Shell reflektiert, wo b noch 1 ist:

Es wird mit diesem Test deutlich.

+0

Ich folge dir nicht. Sie schreiben, dass '{...}' keine Sub-Shell hervorbringt, aber Sie haben gezeigt, dass es eine Subshell hervorgebracht hat, weil $ BASHPID anders ist und die Variable b nicht geändert wurde. – vbezhenar

+1

@vbezhenar, es ist nicht die '{...}', die die Subshell hervorgebracht hat, es ist die Pipeline. '{...}' erzeugt selbst keine Subshell, verhindert aber auch nicht, dass Sie etwas anderes tun. –

+0

@vbezhenar: Ich habe es klar am Anfang geschrieben, dass ** Was Sie sehen, ist aufgrund der Tatsache, dass Sie '|' (Pipeline) zwischen 2 geschweiften Liste Befehle verwenden ** – anubhava

Verwandte Themen