2010-11-23 9 views
9

Hier ist ein Shell-Skript:Bash: Zuwachs globaler Variable

globvar=0 

function myfunc { 
    let globvar=globvar+1 
    echo "myfunc: $globvar" 
} 

myfunc 
echo "something" | myfunc 

echo "Global: $globvar" 

Wenn sie aufgerufen wird, druckt es aus dem folgenden:

$ sh zzz.sh 
myfunc: 1 
myfunc: 2 
Global: 1 
$ bash zzz.sh 
myfunc: 1 
myfunc: 2 
Global: 1 
$ zsh zzz.sh 
myfunc: 1 
myfunc: 2 
Global: 2 

Die Frage ist: warum dies geschieht und welches Verhalten ist richtig?

P.S. Ich habe das seltsame Gefühl, dass die Funktion hinter der Pipe in einer gegabelten Shell aufgerufen wird ... Kann es also einen einfachen Workaround geben?

P.P.S. Diese Funktion ist ein einfacher Test-Wrapper. Es führt eine Testanwendung aus und analysiert seine Ausgabe. Dann werden die Variablen $ PASSED oder $ FAILED erhöht. Schließlich erhalten Sie eine Anzahl von bestandenen/fehlgeschlagenen Tests in globalen Variablen. Die Nutzung ist wie:

test-util << EOF | myfunc 
input for test #1 
EOF 
test-util << EOF | myfunc 
input for test #2 
EOF 
echo "Passed: $PASSED, failed: $FAILED" 

Danke, Serge

+0

Haben Sie gelesen: http://tldp.org/LDP/abs/html/localvar.html? –

+0

Nun, meiner Meinung nach $ globvar ist keine lokale Variable, weil das Aufrufen von myfunc() ohne eine Pipe die globale Variable $ globvar ziemlich gut erhöht. Das Problem ist, dass der Aufruf von Funktion über Pipe in bash/sh nicht funktioniert. – zserge

Antwort

7

Korn-Shell die gleichen Ergebnisse liefert als zsh, nebenbei bemerkt.

Bitte siehe BashFAQ/024. Pipes erstellen Subshells in Bash und Variablen gehen verloren, wenn Subshells beendet werden.

Basierend auf Ihrem Beispiel, würde ich es so etwas wie dieses restrukturieren:

globvar=0 

function myfunc { 
    echo $(($1 + 1)) 
} 

myfunc "$globvar" 
globalvar=$(echo "something" | myfunc "$globalvar") 
+0

Danke für den Link! Die Frage ist, wie man zwei Variablen zurückgibt: $ PASSED und $ FAILED. Ich verstehe, dass ich nach $? Suchen kann, wenn es gleich Null ist - Inkrement $ PASSED von mir selbst, sonst - inkrementiere $ FAILED. Ich möchte nur so wenig Code wie möglich außerhalb der myfunc(). – zserge

+0

@zserge: Ein Weg wäre so: 'myfunc() {echo" zwei Worte "; }; lese word1 word2 <<< $ (myfunc) 'oder' array = ($ (myfunc)) '. –

3

Piping etwas in myfunc in sh oder bash verursacht eine neue Shell laichen. Sie können dies bestätigen, indem Sie einen langen Schlaf in myfunc hinzufügen. Während Sie schlafen, rufen Sie ps auf und Sie sehen einen Unterprozess. Wenn die Funktion zurückkehrt, wird diese Subshell beendet, ohne den Wert im übergeordneten Prozess zu ändern.

Wenn Sie wirklich diesen Wert müssen geändert werden, müssen Sie einen Wert aus der Funktion zurück und prüfen Sie nach $ PIPESTATUS, ich denke, wie folgt aus:

globvar=0 

function myfunc { 
    let globvar=globvar+1 
    echo "myfunc: $globvar" 
    return $globvar 
} 

myfunc 
echo "something" | myfunc 
globvar=${PIPESTATUS[1]} 

echo "Global: $globvar" 
+0

Kühl. Ich wusste vorher nicht von PIPESTATUS. – dennycrane

+0

Das einzige Problem bei dieser Technik ist, dass der Rückgabewert zwischen 0 und 255 liegen muss. –

0

Verwenden export statt let, sonst ist die Variable lokale (Verwendung $ (()) als auch arithmetische Operation zu tun)

+0

aber exportierte Variablen kehren nicht zur Aufrufer-Shell zurück. Wenn Sie also Ihre Funktion über Pipe aufrufen, bleibt Ihre globale Variable unverändert (getestet auf bash) – zserge

+0

Mein Fehler, ich habe das | nicht gesehen Problem. Warum müssen Sie eine neue Shell aufrufen, anstatt nur das Skript aufzurufen? – mb14

+0

Eigentlich nicht. Ich muss nur eine Ausgabe vom Testprogramm zur Shell-Funktion umleiten, um herauszufinden, ob der Test erfolgreich war. Eine klare Möglichkeit zum Umleiten von Ausgaben ist eine Pipe, aber Pipe gibt eine Subshell ab. – zserge

0

Das Problem ist 'welches Ende einer Pipeline mit eingebauten verwendet wird durch den ursprünglichen Prozess ausgeführt?'

In zsh sieht es so aus, als ob der letzte Befehl in der Pipeline vom Haupt-Shell-Skript ausgeführt wird, wenn der Befehl eine Funktion oder eine integrierte Funktion ist.

In Bash (und sh ist wahrscheinlich eine Verbindung zu Bash, wenn Sie unter Linux sind), dann entweder beide Befehle in einer Untershell ausgeführt werden oder der erste Befehl wird vom Hauptprozess ausgeführt und die anderen sind Ausführen von Sub-Shells.

Wenn die Funktion in einer Untershell ausgeführt wird, hat dies natürlich keinen Einfluss auf die Variable in der übergeordneten Shell (nur die globale in der Untershell).

Betrachten wir einen zusätzlichen Test ergänzt:

echo Something | { myfunc; echo $globvar; } 
echo $globvar