Shell-Skript aus einer Subshell beenden

30

Betrachten Sie dieses Snippet:

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if false; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

Normalerweise funcführt der Aufruf dazu, dass das Skript beendet wird. Dies ist das beabsichtigte Verhalten. Wenn es jedoch in einer Sub-Shell wie in ausgeführt wird

result=`func`

Das Skript wird nicht beendet. Dies bedeutet, dass der aufrufende Code jedes Mal den Beendigungsstatus der Funktion überprüfen muss. Gibt es eine Möglichkeit, dies zu vermeiden? Ist das, was set -eist für?

Ernest AC
quelle
1
Ich möchte eine Funktion "stop", die eine Nachricht an stderr ausgibt und das Skript stoppt, aber sie stoppt nicht, wenn die Funktion, die stop aufruft, in einer Sub-Shell ausgeführt wird, wie im Beispiel
Ernest AC
2
Natürlich, weil es aus der Subshell nicht die aktuelle gibt. Rufen Sie einfach die Funktion direkt: func.
1
Ich kann es nicht direkt aufrufen, da es eine Zeichenfolge zurückgibt, die in einer Variablen gespeichert werden muss
Ernest AC
1
@ErnestAC Bitte geben Sie alle Details in der ursprünglichen Frage an. Die obige Funktion gibt keine Zeichenfolge zurück.
1
@ htor Ich habe das Beispiel geändert
Ernest AC

Antworten:

10

Sie könnten die ursprüngliche Shell ( kill $$) töten, bevor Sie anrufen exit, und das würde wahrscheinlich funktionieren. Aber:

  • es kommt mir ziemlich hässlich vor
  • Es wird unterbrochen, wenn Sie eine zweite Unterschale darin haben, dh eine Unterschale in einer Unterschale verwenden.

Stattdessen können Sie eine der verschiedenen Möglichkeiten verwenden, um einen Wert in den Bash-FAQ zurückzugeben . Die meisten von ihnen sind leider nicht so toll. Es kann sein, dass Sie nach jedem Funktionsaufruf nicht mehr auf Fehler überprüfen können ( -eviele Probleme ). Entweder das, oder zu Perl wechseln.

derobert
quelle
5
Vielen Dank. Ich würde jedoch lieber zu Python wechseln.
Ernest AC
2
Wie ich schreibe, ist es das Jahr 2019. Jemandem zu sagen, dass er zu Perl wechseln soll, ist lächerlich. Entschuldigen Sie den Streit, aber würden Sie jemandem, der mit "C" frustriert ist, sagen, dass er zu Cobol wechseln soll, was IMO-Äquivalent ist? Wie Ernest betont, ist Python eine viel bessere Wahl. Meine Vorliebe wäre Ruby. Wie auch immer, alles andere als Perl.
Graham Nicholls
38

Sie können beispielsweise festlegen, dass der Beendigungsstatus 77 das Beenden einer beliebigen Subshell-Ebene bedeutet

set -E
trap '[ "$?" -ne 77 ] || exit 77' ERR

(
  echo here
  (
    echo there
    (
      exit 12 # not 77, exit only this subshell
    )
    echo ici
    exit 77 # exit all subshells
  )
  echo not here
)
echo not here either

set -E in Kombination mit ERR Traps ist ein bisschen wie eine verbesserte Version von, set -ein der Sie Ihre eigene Fehlerbehandlung definieren können.

In zsh werden ERR-Traps automatisch vererbt, sodass Sie sie nicht benötigen set -E. Sie können auch Traps als TRAPERR()Funktionen definieren und sie $functions[TRAPERR]wie folgt ändernfunctions[TRAPERR]="echo was here; $functions[TRAPERR]"

Stéphane Chazelas
quelle
1
Interessante Lösung! Deutlich eleganter als kill $$.
3
Eine Sache, auf die Sie achten müssen, ist, dass diese Falle keine interpolierten Befehle verarbeiten kann, z echo "$(exit 77)". Das Skript wird echo ""
fortgesetzt,
Interessant! Gibt es Glück bei einer (ziemlich älteren) Bash, die -E nicht hat? Vielleicht müssen wir darauf zurückgreifen, eine Falle für ein USER-Signal zu definieren und einen Kill für dieses Signal zu verwenden? Ich werde auch ein paar Nachforschungen anstellen ...
Olivier Dulac
Wie kann man wissen, wenn man sich nicht in einer Unterschalenfalle befindet, um 1 statt 77 zurückzugeben?
9.
7

Alternativ dazu kill $$können Sie auch versuchen kill 0, dass es bei verschachtelten Subshells funktioniert (alle Anrufer und Nebenprozesse erhalten das Signal)… aber es ist immer noch brutal und hässlich.

Stéphane Gimenez
quelle
2
Wäre das nicht Kill-Prozess ID 0?
Ernest AC
5
Das wird die gesamte Prozessgruppe töten. Sie können Dinge treffen, die Sie nicht wollen (wenn Sie zum Beispiel einige Dinge im Hintergrund gestartet haben).
Derobert
2
@ErnestAC Siehe die kill (2) Manpage, Pids ≤0 haben eine besondere Bedeutung.
Derobert
0

Versuche dies ...

stop () {
    echo "${1}" 1>&2
    exit 1
}

func () {
    if $1; then
        echo "foo"
    else
        stop "something went wrong"
    fi
}

echo "shell..."
func $1

echo "subshell..."
result=`func $1`

echo "shell..."
echo "result=$result"

Die Ergebnisse, die ich bekomme, sind ...

# test_exitsubshell true
shell...
foo
subshell...
shell...
result=foo
# test_exitsubshell false
shell...
something went wrong

Anmerkungen

  • Parametrisiert, um den ifTest zu ermöglichen, trueoder false(siehe die 2 Läufe)
  • Wenn der ifTest ist false, erreichen wir nie die Unterschale.
DocSalvager
quelle
Dies ist sehr ähnlich zu der ursprünglichen Idee, über die der Benutzer geschrieben hat und die nicht funktioniert hat. Ich denke nicht, dass dies für den Subshell-Fall funktioniert. Ihr Test, der falsche Exits verwendet, wird nach dem "Shell" -Fall beendet und gelangt nie zum "Subshell" -Testfall. Ich glaube, dass es in diesem Fall fehlschlagen würde, da die Subshell den "exit 1" -Aufruf beendet, aber den Fehler nicht an die äußere Shell weitergibt.
stuckj
0

(Bash-spezifische Antwort) Bash kennt keine Ausnahmen. Wenn sich jedoch set -o errexit (oder das Äquivalent: set -e) auf der äußersten Ebene befindet, führt der fehlgeschlagene Befehl dazu, dass die Subshell mit einem Beendigungsstatus ungleich Null beendet wird. Wenn dies ein Satz verschachtelter Subshells ohne Bedingungen für die Ausführung dieser Subshells ist, wird das gesamte Skript effektiv "gerollt" und beendet.

Dies kann schwierig sein, wenn Sie versuchen, Bits verschiedener Bash-Codes in ein größeres Skript aufzunehmen. Ein Teil der Bash mag für sich alleine gut funktionieren, aber wenn es unter einem errexit (oder ohne errexit) ausgeführt wird, verhält es sich auf unerwartete Weise.

[192.168.13.16 (f0f5e19e) ~ 22:58:22] # bash -o errexit / tmp / foo
etwas ist schief gelaufen
[192.168.13.16 (f0f5e19e) ~ 22:58:31] # bash / tmp / foo
etwas ist schief gelaufen
Aber wir sind trotzdem hier angekommen
[192.168.13.16 (f0f5e19e) ~ 22:58:37] # cat / tmp / foo
#! / bin / bash
halt () {
    Echo "$ {1}"
    Ausfahrt 1
}

wenn falsch; dann
    Echo "foo"
sonst
    (
        halt "irgendwas ist schief gelaufen"
    )
    echo "Aber wir sind trotzdem gekommen"
fi
[192.168.13.16 (f0f5e19e) ~ 22:58:40] #
Brian Chrisman
quelle
-2

Mein Beispiel um in einem Liner auszusteigen:

COMAND || ( echo "ERROR – executing COMAND, exiting..." ; exit 77 );[ "$?" -eq 77 ] && exit
vicente
quelle
1
Dies scheint eigentlich keine Antwort zu sein, die mit dem von OP angeforderten Befehl in einer Unterschale funktionieren würde. Abwahlen ohne Kommentar oder Grund sind ebenso wenig hilfreich wie schlechte Antworten.
DVS