Ist das ein Bug in Bash? `return` beendet die Funktion nicht, wenn es von einer Pipe aufgerufen wird

16

Ich habe in letzter Zeit einige seltsame Probleme mit Bash. Während ich versuchte, mein Skript zu vereinfachen, kam ich auf dieses kleine Stück Code:

$ o(){ echo | while read -r; do return 0; done; echo $?;}; o
0
$ o(){ echo | while read -r; do return 1; done; echo $?;}; o
1

returnsollte die Funktion verlassen haben, ohne zu drucken $?, nicht wahr? Nun, dann habe ich geprüft, ob ich alleine von einer Pipe zurückkehren kann:

$ echo | while read -r; do return 1; done
bash: return: can only `return' from a function or sourced script

Das gleiche passiert ohne whileSchleife:

$ foo(){ : | return 1; echo "This should not be printed.";}
$ foo
This should not be printed.

Fehlt mir hier etwas? Eine Google-Suche hat dazu nichts gebracht! Meine Bash-Version ist 4.2.37 (1) -Release auf Debian Wheezy.

Teresa und Junior
quelle
Stimmt etwas nicht mit den Einstellungen überein, die ich in meiner Antwort vorgeschlagen habe, damit sich Ihr Skript so intuitiv verhält, wie Sie es erwartet haben?
Juli
@jlliagre Es ist ein ziemlich komplexes Skript mit Tausenden von Zeilen. Mit der Sorge, etwas anderes zu beschädigen, ziehe ich es vor, ein Rohr innerhalb der Funktion nicht zu führen, und ersetze es durch eine Prozessersetzung. Vielen Dank!
Teresa e Junior
Warum nicht die ersten beiden Beispiele entfernen, wenn das whilenicht für die Reproduktion benötigt wird? Es lenkt vom Punkt ab.
Leichtigkeit Rennen mit Monica
@LightnessRacesinOrbit Eine whileSchleife ist eine sehr häufige Verwendung für eine Pipe mit return. Das zweite Beispiel ist direkter, aber es ist etwas, von dem ich nicht glaube, dass es jemals jemand verwenden würde ...
Teresa e Junior
1
Leider wurde meine richtige Antwort gelöscht ... Sie befinden sich in einer Grauzone, während Sie etwas tun, das nicht angegeben ist. Das Verhalten hängt davon ab, wie die Shell Pipes interpretiert, und dies unterscheidet sich sogar zwischen der Bourne-Shell und der Korn-Shell, obwohl ksh von sh-Quellen abgeleitet wurde. In der Bourne-Shell befindet sich die while-Schleife in einer Subshell, daher sehen Sie das Echo wie bei bash. In ksh ist die while-Schleife der Vordergrundprozess und daher ruft ksh bei Ihrem Beispiel kein Echo auf.
Schily

Antworten:

10

Verwandte Themen: /programming//a/7804208/4937930

Es ist kein Fehler, dass Sie ein Skript nicht beenden oder von einer Funktion durch exitoder returnin Subshells zurückkehren können. Sie werden in einem anderen Prozess ausgeführt und wirken sich nicht auf den Hauptprozess aus.

Außerdem, nehme ich an, sehen Sie undokumentierte Verhaltensweisen von Bash auf (wahrscheinlich) undefinierten Spezifikationen. In einer Funktion werden returnauf oberster Ebene von Subshell-Befehlen keine Fehler gemeldet, und sie verhält sich einfach so exit.

IMHO ist es ein Bash-Bug für das inkonsistente Verhalten, returnje nachdem, ob sich die Hauptanweisung in einer Funktion befindet oder nicht.

#!/bin/bash

o() {
    # Runtime error, but no errors are asserted,
    # each $? is set to the return code.
    echo | return 10
    echo $?
    (return 11)
    echo $?

    # Valid, each $? is set to the exit code.
    echo | exit 12
    echo $?
    (exit 13)
    echo $?
}
o

# Runtime errors are asserted, each $? is set to 1.
echo | return 20
echo $?
(return 21)
echo $?

# Valid, each $? is set to the exit code.
echo | exit 22
echo $?
(exit 23)
echo $?

Ausgabe:

$ bash script.sh 
10
11
12
13
script.sh: line 20: return: can only `return' from a function or sourced script
1
script.sh: line 22: return: can only `return' from a function or sourced script
1
22
23
yaegashi
quelle
Der Mangel an Fehler Ausführlichkeit kann undokumentiert sein. Die Tatsache, dass returneine Befehlssequenz auf oberster Ebene in einer Subshell nicht funktioniert und insbesondere die Subshell nicht beendet, hat mich bereits zu den Erwartungen bestehender Dokumente veranlasst. Das OP kann dort eingesetzt werden, exit 1 || return 1wo es verwendet werden soll return, und sollte dann das erwartete Verhalten aufweisen. BEARBEITEN: Die Antwort von @ Herbert zeigt an, dass die oberste Ebene returnin der Unterschale als exit(aber nur von der Unterschale) fungiert.
dubiousjim
1
@dubiousjim Aktualisiert mein Skript. Ich meine, returnin einer einfachen Subshell-Sequenz sollte in jedem Fall als Laufzeitfehler behauptet werden , aber eigentlich ist es nicht, wenn es in einer Funktion auftritt. Dieses Problem wurde auch in gnu.bash.bug diskutiert , aber es gibt keine Schlussfolgerung.
Yaegashi
1
Ihre Antwort ist nicht korrekt, da nicht angegeben ist, ob sich die while-Schleife in einer Subshell befindet oder der Vordergrundprozess ist. Unabhängig von der Implementierung der eigentlichen Shell ist die returnAussage funktionsfähig und damit legal. Das resultierende Verhalten ist jedoch nicht spezifiziert.
Schily
Sie sollten nicht schreiben, dass es sich um ein undokumentiertes Verhalten handelt, während die Tatsache, dass sich Rohrleitungskomponenten in einer Subshell befinden, auf der bash-Handbuchseite dokumentiert ist. Sie sollten nicht schreiben, dass das Verhalten wahrscheinlich auf undefinierten Spezifikationen basiert, während POSIX das zulässige Verhalten angibt. Sie sollten keinen Bash-Fehler vermuten, während Bash dem POSIX-Standard folgt, indem Sie die Rückkehr in eine Funktion zulassen, jedoch nicht außerhalb.
Juli
17

Es ist kein Bug in, bashsondern das dokumentierte Verhalten :

Jeder Befehl in einer Pipeline wird in einer eigenen Subshell ausgeführt

Die returnAnweisung ist gültig, wenn sie sich in einer Funktionsdefinition befindet, aber auch in einer Subshell. Sie wirkt sich nicht auf die übergeordnete Shell aus, sodass die nächste Anweisung echounabhängig davon ausgeführt wird. Es handelt sich jedoch um eine nicht portierbare Shell-Konstruktion, da der POSIX-Standard die Ausführung der Befehle, aus denen eine Pipeline besteht, entweder in einer Subshell (Standardeinstellung) oder in der obersten Shell (zulässige Erweiterung) ermöglicht.

Darüber hinaus befindet sich jeder Befehl einer Multi-Command-Pipeline in einer Subshell-Umgebung. Als Erweiterung können jedoch einige oder alle Befehle in einer Pipeline in der aktuellen Umgebung ausgeführt werden. Alle anderen Befehle sollen in der aktuellen Shell-Umgebung ausgeführt werden.

Hoffentlich können Sie bashmit ein paar Optionen festlegen, wie Sie es erwarten:

$ set +m # disable job control
$ shopt -s lastpipe # do not run the last command of a pipeline a subshell 
$ o(){ echo | while read -r; do return 0; done; echo $?;}
$ o
$          <- nothing is printed here
jlliagre
quelle
1
Da returndie Funktion nicht beendet wird, ist es nicht sinnvoller, wenn die Shell nur gedruckt wird bash: return: can only `return' from a function or sourced script, anstatt dem Benutzer ein falsches Gefühl zu vermitteln, dass die Funktion möglicherweise zurückgegeben wurde.
Teresa e Junior
2
Ich sehe nirgendwo in der Dokumentation, dass die Rückgabe innerhalb der Subshell gültig ist. Ich wette, dass dieses Feature von ksh kopiert wurde, die return-Anweisung außerhalb der Funktion oder das Quellenskript sich wie exit verhalten . Ich bin mir nicht sicher, was die ursprüngliche Bourne-Shell angeht.
Donnerstag,
1
@jlliagre: Vielleicht ist Teresa verwirrt über die Terminologie dessen, wonach sie fragt, aber ich verstehe nicht, warum es für Bash "schwierig" wäre, eine Diagnose auszustellen, wenn Sie eine returnvon einer Subshell ausführen . Schließlich weiß es, dass es sich um eine Subshell handelt, wie die $BASH_SUBSHELLVariable belegt. Das größte Problem ist, dass dies zu Fehlalarmen führen kann. ein Benutzer, der versteht , wie Subshells Arbeit Skripte geschrieben haben könnte , dass die Verwendung returnvon anstelle exiteiner Sub - Shell zu beenden. (Und natürlich gibt es gültige Fälle, in denen man Variablen setzen oder cdin einer Subshell ausführen möchte.)
Scott
1
@Scott Ich glaube ich verstehe die Situation gut. Eine Pipe erstellt eine Subshell und returnkehrt von der Subshell zurück, anstatt fehlzuschlagen, da sie sich in einer tatsächlichen Funktion befindet. Das Problem ist, dass help returnFolgendes konkret angegeben wird: Causes a function or sourced script to exit with the return value specified by N.Jeder Benutzer erwartet, dass die Dokumentation mindestens fehlschlägt oder eine Warnung ausgibt, sich jedoch niemals so verhält exit.
Teresa e Junior
1
Es scheint mir, dass jeder, der erwartet, dass a return in einer Subshell in einer Funktion von der Funktion zurückkehrt (im Haupt-Shell-Prozess), Subshells nicht sehr gut versteht. Umgekehrt würde ich von einem Leser, der Subshells versteht, erwarten, dass er return in einer Subshell eine Funktion zum Beenden der Subshell erwartet, genau wie exitdies der Fall wäre.
Scott
6

Laut POSIX-Dokumentation ist die Verwendung returnvon Skripten außerhalb der Funktionen oder Quellen nicht spezifiziert . Es hängt also von Ihrer Shell ab, wie Sie damit umgehen.

Die SystemV-Shell meldet einen Fehler, während sich innerhalb ksh, returnaußerhalb der Funktion oder des Quellenskripts wie verhalten exit. Die meisten anderen POSIX-Shells und Schilys verhalten sich ebenfalls so:

$ for s in /bin/*sh /opt/schily/bin/osh; do
  printf '<%s>\n' $s
  $s -c '
    o(){ echo | while read l; do return 0; done; echo $?;}; o
  '
done
</bin/bash>
0
</bin/dash>
0
</bin/ksh>
</bin/lksh>
0
</bin/mksh>
0
</bin/pdksh>
0
</bin/posh>
0
</bin/sh>
0
</bin/yash>
0
</bin/zsh>
</opt/schily/bin/osh>
0

kshund zshwurde nicht ausgegeben, weil der letzte Teil der Pipe in diesen Shells in der aktuellen Shell anstelle der Subshell ausgeführt wurde. Die return-Anweisung wirkte sich auf die aktuelle Shell-Umgebung aus, die die Funktion aufrief, und veranlasste die Funktion, sofort zurückzukehren, ohne etwas zu drucken.

In der interaktiven Sitzung bashnur den Fehler melden, die Shell jedoch nicht beendet, schily's oshden Fehler gemeldet und die Shell beendet:

$ for s in /bin/*sh; do printf '<%s>\n' $s; $s -ci 'return 1; echo 1'; done
</bin/bash>
bash: return: can only `return' from a function or sourced script
1
</bin/dash>
</bin/ksh>
</bin/lksh>
</bin/mksh>
</bin/pdksh>
</bin/posh>
</bin/sh>
</bin/yash>
</bin/zsh>
</opt/schily/bin/osh>
$ cannot return when not in function

( zshIn der interaktiven Sitzung und Ausgabe wird das Terminal nicht beendet bash, yashund schily's oshder Fehler wurde gemeldet, die Shell wurde jedoch nicht beendet.)

cuonglm
quelle
1
Es kann argumentiert werden, dass returnhier innerhalb einer Funktion verwendet wird.
Juli
1
@jlliagre: Nicht sicher , was du meinst, returnwar die Verwendung innerhalb Subshell innen Funktion , mit der Ausnahme kshund zsh.
Donnerstag,
2
Ich meine, sich in einer Subshell zu befinden, die sich selbst in einer Funktion befindet, bedeutet nicht notwendigerweise, sich außerhalb dieser Funktion zu befinden. Dies hätte es verdient, von der Offenen Gruppe geklärt zu werden.
Juli
3
Ich denke nicht. Das liegt außerhalb der Funktion. Die Shell, die die Funktion aufgerufen hat, und die Subshell, die return ausgeführt hat , sind unterschiedlich.
Dienstag,
Ich verstehe Ihre Argumentation, die das Problem zu Recht erklärt. Mein Punkt entspricht der im POSIX-Standard beschriebenen Shell-Grammatik. Die Pipeline ist Teil der Compound-Liste, die Teil des Compound-Befehls ist, der den Hauptteil der Funktion darstellt. Nirgends wird angegeben, dass Pipelinekomponenten außerhalb der Funktion zu berücksichtigen sind. Genau wie wenn ich in einem Auto bin und dieses Auto in einer Garage geparkt ist, kann ich davon ausgehen, dass ich auch in dieser Garage bin
;-)
4

Ich denke, dass Sie das erwartete Verhalten haben, in der Bash wird jeder Befehl in einer Pipeline in einer Subshell ausgeführt. Sie können sich überzeugen, indem Sie versuchen, eine globale Variable Ihrer Funktion zu ändern:

foo(){ x=42; : | x=3; echo "x==$x";}

Übrigens funktioniert die Rückkehr, aber sie kehrt von der Unterschale zurück. Auch hier können Sie Folgendes überprüfen:

foo(){ : | return 1; echo$?; echo "This should not be printed.";}

Gibt Folgendes aus:

1
This should not be printed.

Die return-Anweisung hat die Subshell also korrekt verlassen

.

Herbert
quelle
2
Verwenden foo(){ : | return 1 || return 2; echo$?; echo "This should not be printed.";}; foo; echo $?Sie daher, um die Funktion zu beenden, und Sie erhalten das Ergebnis von 2. Aber der Klarheit halber würde ich das return 1sein lassen exit 1.
dubiousjim
Gibt es übrigens eine Begründung dafür, dass alle Mitglieder einer Pipeline (nicht alle außer einem) in Subshells ausgeführt werden?
Incnis Mrsi
@IncnisMrsi: Siehe die Antwort von jlliagre .
Scott
1

Die allgemeinere Antwort ist, dass Bash und einige andere Shells normalerweise alle Elemente einer Pipeline in separate Prozesse einteilen. Dies ist sinnvoll, wenn die Befehlszeile ist

Programm 1 | Programm 2 | Programm 3

da Programme normalerweise sowieso in getrennten Prozessen ausgeführt werden (sofern Sie nicht sagen ). Aber es kann eine Überraschung seinexec program

befehl 1 | befehl 2 | befehl 3

Dabei sind einige oder alle Befehle integrierte Befehle. Triviale Beispiele sind:

$ a=0
$ echo | a=1
$ echo "$a"
0
$ cd /
$ echo | cd /tmp
$ pwd
/

Ein etwas realistischeres Beispiel ist

$ t=0
$ ps | while read pid rest_of_line
> do
>     : $((t+=pid))
> done
$ echo "$t"
0

wobei das gesamte while... do... doneSchleife in eine Unterprozess gesetzt wird, und so seine Änderungen tan den Hauptschale , nachdem die Schleifenenden sind nicht sichtbar. Und genau das tun Sie, indem Sie in eine whileSchleife leiten, die Schleife als Subshell ausführen und dann versuchen, von der Subshell zurückzukehren.

Scott
quelle