%3F.png)
errexit ( set -e
) wird oft als Methode vorgeschlagen, um einfache Skripte robuster zu machen. Allerdings wird es von jedem, der gesehen hat, wie es sich in komplexeren Skripten verhält, insbesondere mit Funktionen, allgemein als schreckliche Falle angesehen. Und ein sehr ähnliches Problem kann bei Subshells beobachtet werden. Nehmen wir das folgende Beispiel, adaptiert vonhttps://stackoverflow.com/questions/29926013/exit-subshell-on-error
(
set -o errexit
false
true
) && echo "OK" || echo "FAILED";
Die Falle besteht hier darin, dass Shells „OK“ und nicht „FAILED“ anzeigen.[*]
Während das Verhalten set -e
in bestimmten Fällen im Laufe der Geschichte auf unglückliche Weise variiert hat, geben die in einer modernen Distribution verfügbaren Shells an, dem POSIX-Shell-Standard zu folgen, und Tests zeigten für alle der folgenden Fälle das gleiche Verhalten („OK“):
bash-4.4.19-2.fc28.x86_64
(Fedora)busybox-1.26.2-3.fc27.x86_64
(Fedora)dash 0.5.8-2.4
(Debian 9)zsh 5.3.1-4+b2
(Debian 9)posh 0.12.6+b1
(Debian 9)ksh 93u+20120801-3.1
(Debian 9).
Frage
Gibt es einen technischen Grund, warum Sie keine Shell mit ähnlichen Funktionen wie POSIX schreiben können
sh
, außer dass sieFAILED
für den obigen Code druckt?Ich hoffe, dass es sich auch so ändert,
set -e
dass Ausdrücke der obersten Ebene&&
, die „false“ zurückgeben, als fatal betrachtet werden. https://serverfault.com/a/847016/133475 Und hoffentlich verfügt es über eine schönere Ausdrucksweise für die Verwendung mit grep.set -e # print matching lines, but it's not an error if there are no matches in the file ( set +e grep pattern file.txt RET=$? [[ $RET == 0 || $RET == 1 ]] || exit $RET )
Ich nehme auch an, dass arithmetische Anweisungen (
let
integriert) auf eine Weise neu definiert würden, die vermeidet, dass ihr numerischer Wert implizit auf den Exit-Status „true“/„false“ gezwungen wird.Gibt es ein Beispiel für eine Shell, die irgendetwas davon kann?
Ich habe nichts dagegen, mir etwas mit einer anderen Syntax als Bourne anzusehen. Ich interessiere mich für etwas, das kompakt ist, wenn man kurze „Klebe“-Skripte schreibt, aber das auch eine kompakte Strategie zur Fehlererkennung hat. Ich verwende es nicht gern
&&
als Anweisungstrennzeichen, wenn das versehentliche Weglassen eines Anweisungstrennzeichens dazu führt, dass Fehler stillschweigend ignoriert werden.
[*] BEARBEITEN. Dies ist vielleicht kein perfektes Beispiel. Die Diskussion legt nahe, dass ein besseres Beispiel darin bestehen würde, die set -o errexit
über die Unterschale zu verschieben.
Soweit ich weiß, ist das dem Testfall (false; echo foo) || echo bar; echo \ $?
aus der Tabelle sehr ähnlich.Hier, das besagt, dass es in der ursprünglichen Bourne-Shell und den meisten ihrer Nachfolger „foo“ (und dann „0“) anzeigen würde. Die Ausnahmen sind „hist.ash“ und Bash 1.14.7.
Wenn Sie set -e
innerhalb der Untershell - hinzufügen (set -e; false; echo foo) || echo bar; echo \ $?
, gibt es zwei weitere Ausnahmen: „SVR4 sh sun5.10“ und dash-0.3.4.
Was den Sinn meiner Frage angeht, nehme ich an, dass die Anwesenheit set -e
in der Subshell eine Ablenkung war. Mein Hauptinteresse gilt einer Redewendung, die Sie set -e
am Anfang des Skripts verwenden und die auch für Subshells gilt (was bereits das POSIX-Verhalten ist).
Jörg Schilling weist darauf hin, dass die Bourne-Shell des ursprünglichen Unix für das von mir in dieser Frage verwendete Beispiel "FAILED" ausgeben würde, und dass er diese Shell auf POSIX portiert hat als"osh" in Schilytools, und dass dieses Ergebnis mit der Veröffentlichung vom 11.06.2018 bestätigt wurde. Vielleicht beruht dies darauf, dass 1) set -e
es sich innerhalb der Subshell befindet und 2) „SVR4 sh sun5.10“. „osh“ ist „basierend auf den OpenSolaris-Quellen und somit auf SVR4 und SVID3“. Oder vielleicht gibt es eine erschreckende zusätzliche Abweichung, die durch das Hinzufügen && echo "OK"
zwischen der Subshell und entsteht || echo "FAILED"
.
Ich glaube nicht, dass die Schily-Shell meine Frage beantwortet. Sie könnten Subshells für Funktionen verwenden (verwenden Sie (
anstelle von {
, um die Funktion in einer Subshell auszuführen) und jede Subshell mit starten set -e
. Die Verwendung von Subshells führt jedoch zu der Einschränkung, dass Sie globale Variablen nicht ändern können. Zumindest habe ich noch niemanden gesehen, der dies als allgemeinen Codierstil verteidigt.
Antwort1
DerPOSIX-Standard(vgl.-e) ist bezüglich der errexit
Shell-Option klarer als das Bash-Handbuch.
Wenn diese Option aktiviert ist und ein beliebiger Befehl fehlschlägt (aus einem der unter „Folgen von Shell-Fehlern“ aufgeführten Gründe oder durch Rückgabe eines Beendigungsstatus größer als Null), wird die Shell sofort beendet, als ob das spezielle integrierte Dienstprogramm „exit“ ohne Argumente ausgeführt würde. Dabei gelten die folgenden Ausnahmen:
Der Fehler eines einzelnen Befehls in einer Pipeline mit mehreren Befehlen darf nicht zum Beenden der Shell führen. Nur der Fehler der Pipeline selbst darf berücksichtigt werden.
Die Einstellung -e soll ignoriert werden, wenn die zusammengesetzte Liste nach dem reservierten Wort „while“, „until“, „if“ oder „elif“ ausgeführt wird, eine Pipeline, die mit dem reservierten Wort „!“ beginnt, oder ein beliebiger Befehl einer AND-OR-Liste außer dem letzten.
Wenn der Beendigungsstatus eines zusammengesetzten Befehls (mit Ausnahme eines Subshell-Befehls) das Ergebnis eines Fehlers war, während -e ignoriert wurde, dann gilt -e nicht für diesen Befehl.
Diese Anforderung gilt für die Shell-Umgebung und jede Subshell-Umgebung separat. Zum Beispiel in:
set -e; (false; echo one) | cat; echo two
Der Befehl „false“ bewirkt, dass die Subshell beendet wird, ohne „Echo 1“ auszuführen; „Echo 2“ wird jedoch ausgeführt, da der Beendigungsstatus der Pipeline (false; Echo 1) | cat null ist.
Offensichtlich ist der Beispielcode gleichwertig mit dem folgenden Pseudocode.
( LIST; ) && COMMAND || COMMAND
Der Exit-Status desListe ist Null, weil:
die
errexit
Shell-Option wird ignoriert.Der Rückgabestatus ist der Exit-Status des letzten im Befehl angegebenen Befehls.Liste, Hier
true
.
Daher wird der zweite Teil der UND-Liste ausgeführt: echo "OK"
.
Antwort2
Keine Antwort auf die Frage „Hat jemand versucht, … zu schreiben“, sondern einige Gedanken dazu, wie es gemacht werden könnte. Bin gespannt, ob jemand Feedback geben kann.
Der gängige Ansatz in aktuellen Skriptlösungen (Python, JavaScript und das ältere Perl und ähnliche) besteht in der Verwendung von try ... catch-Blöcken. Das erwartete Verhalten ist, dass jeder Fehler eine Ausnahme auslöst. Dieses Verhalten steht im Widerspruch zum *sh-Verhalten, bei dem Fehler in $? erfasst werden. Offensichtlich hat das '-e' es nicht so richtig hinbekommen, dass es wie von den meisten Entwicklern erwartet funktioniert. Es ist hier nicht nötig, näher darauf einzugehen, da dies bereits ausführlich dokumentiert ist.
Zwei mögliche Ansätze:
- Erstellen Sie ein besseres
set -e
: zBset -ocatcherr
mit besserer Fehlerbehandlung. - Fügen Sie einen neuen
try { list ; }
Befehl hinzu und folgen Sie dabei modernen Skriptlösungen
Das '-ofailerr' kann wie folgt definiert werden:
- Wenn ein Befehl außerhalb des Kontrollflusses (if/while/until) fehlschlägt, sollte der aktuelle Ausführungskontext 1 zurückgeben (oder beenden, wenn dies der Block der obersten Ebene ist). Diese Änderung zwingt den Entwickler effektiv dazu, für jeden fehlgeschlagenen Befehl oder jeden ausdrücklich markierten Befehl mit Fehlern, die ignoriert werden können, eine Fehlerbehandlung bereitzustellen.
- Funktioniert effektiv für einfache Skripte und bricht bei unbehandelten Fehlern ab.
Der try { list ; }
Ansatz ist ähnlich, nur die Syntax ist anders
Beispiel:
function foo {
cat $* > a.txt
wc -l a.txt
}
set -oerrfail
foo /non/existing/file # Will fail, without executing the "wc -l"
if foo /non/existing/file ; then
...
else
# This is the "catch" block
fi
Mit einem Try-Block:
try {
foo /non/existing/file
...
} || { catch-block }
Tatsächlich ist der Versuch gleichbedeutend mit „set -oerrofail“, wobei die Befehlsliste unter einem „if ! { ... } ; then { catch-block }“ ausgeführt wird.