Ich habe ein Skript, das nicht beendet wird, wenn ich es möchte.
Ein Beispielskript mit demselben Fehler ist:
#!/bin/bash
function bla() {
return 1
}
bla || ( echo '1' ; exit 1 )
echo '2'
Ich würde davon ausgehen, die Ausgabe zu sehen:
:~$ ./test.sh
1
:~$
Aber ich sehe tatsächlich:
:~$ ./test.sh
1
2
:~$
Erzeugt die ()
Befehlsverkettung irgendwie einen Bereich? Woraus wird exit
das Skript beendet, wenn nicht?
shell-script
exit
subshell
Minix
quelle
quelle
Antworten:
()
führt Befehle in der Subshell aus, sodassexit
Sie die Subshell verlassen und zur übergeordneten Shell zurückkehren. Verwenden Sie geschweifte Klammern,{}
wenn Sie Befehle in der aktuellen Shell ausführen möchten.Aus dem Bash-Handbuch:
Es ist erwähnenswert, dass die Shell-Syntax ziemlich konsistent ist und die Subshell auch an anderen
()
Konstrukten wie der Befehlssubstitution (auch mit der alten`..`
Syntax) oder der Prozesssubstitution beteiligt ist, sodass die folgende Shell auch nicht verlassen wird:Während es offensichtlich sein mag, dass Subshells involviert sind, wenn Befehle explizit darin platziert werden
()
, ist die weniger sichtbare Tatsache, dass sie auch in diesen anderen Strukturen erzeugt werden:Befehl wurde im Hintergrund gestartet
verlässt die aktuelle Shell nicht, weil (nach
man bash
)die Pipeline
verlässt immer noch nur die Unterschale.
Unterschiedliche Schalen verhalten sich diesbezüglich jedoch unterschiedlich.
bash
Versetzt beispielsweise alle Komponenten der Pipeline in separate Subshells (es sei denn, Sie verwenden dielastpipe
Option in Aufrufen, in denen die Jobsteuerung nicht aktiviert ist), aber AT & Tksh
undzsh
führen den letzten Teil in der aktuellen Shell aus (beide Verhalten sind von POSIX zulässig). Somittut im Grunde nichts in bash, verlässt aber die zsh wegen der letzten
exit
.coproc exit
läuft auchexit
in einer Unterschale.quelle
{
und}
sind nicht Syntax, sie sind reservierte Wörter und müssen durch Leerzeichen umgeben sein, und die Liste muss mit einem Befehlsabschlussende (Semikolon, Newline, Ampersand)(echo $$)
ID der übergeordneten Shell$$
ausgibt, da diese bereits erweitert wird, bevor eine untergeordnete Shell erstellt wird. Tatsächlich kann das Drucken der Subshell-Prozess-ID schwierig sein (siehe stackoverflow.com/questions/9119885/…$$
das erweitert wird, bevor die Subshell erstellt wird, und dennoch$BASHPID
den richtigen Wert für eine Subshell anzeigt?Das Ausführen
exit
in einer Subshell ist eine Falle:Das Skript druckt 42, beendet die Subshell mit dem Rückkehrcode
1
und fährt mit dem Skript fort. Selbst das Ersetzen des Anrufs durchecho $(CALC) || exit 1
hilft nicht, da der Rückgabecodeecho
0 ist, unabhängig vom Rückgabecode voncalc
. Undcalc
wird vor ausgeführtecho
.Noch rätselhafter ist es, den Effekt zu vereiteln,
exit
indem Sielocal
ihn wie im folgenden Skript in Built- in einschließen. Ich bin über das Problem gestolpert, als ich eine Funktion zur Überprüfung eines Eingabewerts geschrieben habe. Beispiel:Ich möchte eine Datei mit dem Namen "year month day.log"
20141211.log
für heute erstellen . Das Datum wird von einem Benutzer eingegeben, der möglicherweise keinen angemessenen Wert angibt. Daherfname
überprüfe ich in meiner Funktion den Rückgabewert vondate
, um die Gültigkeit der Benutzereingabe zu überprüfen:Sieht gut aus. Lassen Sie das Skript benannt werden
s.sh
. Wenn der Benutzer das Skript mit aufruft./s.sh "Thu Dec 11 20:45:49 CET 2014"
, wird die Datei20141211.log
erstellt. Wenn der Benutzer jedoch Folgendes eingibt./s.sh "Thu hec 11 20:45:49 CET 2014"
, gibt das Skript Folgendes aus:Die Zeile
fname…
besagt, dass in der Subshell fehlerhafte Eingabedaten erkannt wurden. Dasexit 1
am Ende derlocal …
Zeile wird jedoch nie ausgelöst, da dielocal
Direktive immer zurückkehrt0
. Dies liegt daranlocal
, dass nach ausgeführt wird$(fname)
und somit seinen Rückkehrcode überschreibt. Aus diesem Grund wird das Skript fortgesetzt undtouch
mit einem leeren Parameter aufgerufen. Dieses Beispiel ist einfach, aber das Verhalten von bash kann in einer realen Anwendung ziemlich verwirrend sein. Ich weiß, echte Programmierer benutzen keine Einheimischen. «Um es deutlich zu machen: Ohne
local
wird das Skript wie erwartet abgebrochen, wenn ein ungültiges Datum eingegeben wird.Das Update ist, die Linie wie zu teilen
Das seltsame Verhalten entspricht der Dokumentation
local
in der Manpage von bash: "Der Rückgabestatus ist 0, sofern local nicht außerhalb einer Funktion verwendet wird, ein ungültiger Name angegeben wird oder name eine schreibgeschützte Variable ist."Obwohl dies kein Fehler ist, habe ich das Gefühl, dass das Verhalten von bash nicht intuitiv ist. Mir ist die Ausführungsreihenfolge bekannt,
local
sollte aber eine fehlerhafte Zuordnung nicht maskieren.Meine erste Antwort enthielt einige Ungenauigkeiten. Nach einer aufschlussreichen und eingehenden Diskussion mit mikeserv (danke dafür) habe ich sie repariert.
quelle
doit()
.Die Klammern beginnen eine Subshell und der Exit beendet nur diese Subshell.
Sie können den Exitcode mit lesen
$?
und diesen in Ihr Skript einfügen, um das Skript zu beenden, wenn die Subshell beendet wurde:quelle
Die eigentliche Lösung:
Die Fehlergruppierung wird nur ausgeführt, wenn
bla
ein Fehlerstatus zurückgegeben wird. Sieexit
befindet sich nicht in einer Subshell, sodass das gesamte Skript angehalten wird.quelle