Gibt es eine Möglichkeit, eine Bash-Funktion zu schreiben, die die gesamte Ausführung abbricht, egal wie sie aufgerufen wird?

81

Ich habe die Anweisung "exit 1" in meinen Bash-Funktionen verwendet, um das gesamte Skript zu beenden, und es hat gut funktioniert:

function func()
{
   echo "Goodbye"
   exit 1
}
echo "Function call will abort"
func
echo "This will never be printed"

Aber dann wurde mir klar, dass es nicht funktioniert, wenn es wie folgt aufgerufen wird:

res=$(func)

Ich verstehe, dass ich eine Unterschale erstellt habe und "exit 1" diese Unterschale abbricht und nicht die primäre ...

Aber gibt es eine Möglichkeit, eine Funktion zu schreiben, die die gesamte Ausführung abbricht, egal wie sie aufgerufen wird? Ich muss nur den tatsächlichen Rückgabewert ermitteln (von der Funktion bestätigt).

LiMar
quelle

Antworten:

89

Sie können die Shell der obersten Ebene registrieren, damit das TERMSignal beendet wird, und dann eine TERMShell an die Shell der obersten Ebene senden :

#!/bin/bash
trap "exit 1" TERM
export TOP_PID=$$

function func()
{
   echo "Goodbye"
   kill -s TERM $TOP_PID
}

echo "Function call will abort"
echo $(func)
echo "This will never be printed"

Ihre Funktion sendet also ein TERMSignal zurück an die Shell der obersten Ebene, die in diesem Fall mit dem angegebenen Befehl abgefangen und verarbeitet wird "exit 1".

Fataler Fehler
quelle
1
Ich habe an die C- setsid()Funktion gedacht , aber sie funktioniert nicht ganz so. Aktualisiert, um den setsidBefehl nicht zu verwenden , da wir einen neuen Prozess starten müssten.
FatalError
3
Funktioniert. Jede Ebene der Funktionsverschachtelung, jeder Fluss ... Funktioniert einfach.
LiMar
Ist es möglich, daraus eine allgemeine abort () - Funktion zu machen, die mit dem Code des ersten Arguments beendet wird, z. B. abort 2würde das trap "exit 2" TERMvorher tun kill?
Neil C. Obremski
@ NeilC.Obremski: Sicher. Es geht hauptsächlich darum, wie der Wert zurück in die Shell der obersten Ebene übertragen werden kann. Verwenden Sie wahrscheinlich einen Befehl tempfile, um einen Ort zum Speichern des Codes in der oberen Shell auszuwählen. Lassen Sie dann die Shell, die gerade beendet wird, den Code in diese Datei schreiben und lesen Sie dann einfach den Handler des übergeordneten Elements ein.
FatalError
2
Scheint nicht zu funktionieren, wenn dazwischenliegende Unterschalen vorhanden sind. Siehe alte Lösung für Bash mit set -E hier
Ron Burk
41

Sie können set -edie Exits verwenden, wenn ein Befehl mit einem Status ungleich Null beendet wird :

set -e 
func
set +e

Oder holen Sie sich den Rückgabewert:

(func) || exit $?
brice
quelle
3
Interessanterweise sehe ich eine Lösung. "set -e" im Hauptskript bewirkt, dass jede Funktion, die 1 zurückgibt (oder damit beendet wird), die gesamte Ausführung abbricht. Leider ändert "set -e" das gesamte Verhalten drastisch und ich kann es nicht verwenden.
LiMar
1
Sie sollten (()) für numerische Vergleiche verwenden. Innerhalb von [] -gt, -eq usw. sollte verwendet werden.
Jordan
14
oder sogarres=$(func) || exit
Glenn Jackman
@glennjackman bekommt den Cookie, denke ich. Das ist viel sauberer als der Müll, den ich habe!
Brice
2
diese Art trotzt dem Geist der Frage. Wenn jeder Funktionsaufruf den Fehler selbst abfangen muss, macht es keinen Sinn, ihn überhaupt zu verwenden exit . Die Funktion könnte einfach zurückkehren. / Wenn set -edies jedoch global festgelegt ist, ist dies sinnvoll
phil294
1

Aber gibt es eine Möglichkeit, eine Funktion zu schreiben, die die gesamte Ausführung abbricht, egal wie sie aufgerufen wird?

Nein.

Ich muss nur den tatsächlichen Rückgabewert ermitteln (von der Funktion bestätigt).

Sie können

res=$(func)
echo $?
Luca
quelle
1

Ich denke besser ist

#!/bin/bash
set -e
trap "exit 1" ERR

myfunc() {
     set -x # OPTIONAL TO SHOW ERROR
     echo "Exit with failure"
     set +x # OPTIONAL
     exit 1
}
echo "BEFORE..."
myvar="$(myfunc)"
echo "AFTER..But not shown"
AmazingAlex
quelle
0

Ein untergeordneter Prozess kann den übergeordneten Prozess nicht implizit zum Schließen zwingen. Sie müssen eine Art Signalmechanismus verwenden. Zu den Optionen kann ein spezieller Rückgabewert gehören oder ein Signal mit killetwas wie gesendet werden

function child() {
    local parent_pid="$1"
    local other="$2"
    ...
    if [[ $failed ]]; then
        kill -QUIT "$parent_pid"
    fi
}
Daenyth
quelle