it-swarm.com.de

Warum beendet (exit 1) das Skript nicht?

Ich habe ein Skript, das nicht beendet wird, wenn ich es möchte.

Ein Beispielskript mit demselben Fehler lautet:

#!/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
:~$

Tut das () Befehlsverkettung irgendwie einen Bereich erstellen? Was beendet exit, wenn nicht das Skript?

51
Minix

() Führt Befehle in der Subshell aus. Mit exit verlassen Sie die Subshell und kehren zur übergeordneten Shell zurück. Verwenden Sie geschweifte Klammern {}, Wenn Sie Befehle in der aktuellen Shell ausführen möchten.

Aus dem Bash-Handbuch:

(list) list wird in einer Subshell-Umgebung ausgeführt. Variablenzuweisungen und integrierte Befehle, die sich auf die Shell-Umgebung auswirken, bleiben nach Abschluss des Befehls nicht wirksam. Der Rückgabestatus ist der Beendigungsstatus der Liste.

{list;} list wird einfach in der aktuellen Shell-Umgebung ausgeführt. Die Liste muss mit einem Zeilenumbruch oder einem Semikolon abgeschlossen werden. Dies wird als Gruppenbefehl bezeichnet. Der Rückgabestatus ist der Beendigungsstatus der Liste. Beachten Sie, dass {und} im Gegensatz zu den Metazeichen (und) reservierte Wörter sind und dort auftreten müssen, wo ein reserviertes Wort erkannt werden darf. Da sie keine Word-Unterbrechung verursachen, müssen sie durch Leerzeichen oder ein anderes Shell-Metazeichen von der Liste getrennt werden.

Es ist erwähnenswert, dass die Shell-Syntax ziemlich konsistent ist und die Subshell auch an den anderen () - Konstrukten wie der Befehlssubstitution (auch mit der alten `..` - Syntax) oder der Prozesssubstitution beteiligt ist wird auch nicht aus der aktuellen Shell beendet:

echo $(exit)
cat <(exit)

Während es offensichtlich sein mag, dass Subshells beteiligt sind, wenn Befehle explizit in () Platziert werden, ist die weniger sichtbare Tatsache, dass sie auch in diesen anderen Strukturen erzeugt werden:

  • befehl im Hintergrund gestartet

    exit &
    

    beendet die aktuelle Shell nicht, weil (nach man bash)

    Wenn ein Befehl vom Steueroperator & beendet wird, führt die Shell den Befehl im Hintergrund in einer Subshell aus. Die Shell wartet nicht auf den Abschluss des Befehls und der Rückgabestatus ist 0.

  • die Pipeline

    exit | echo foo
    

    verlässt immer noch nur die Unterschale.

    Unterschiedliche Schalen verhalten sich in dieser Hinsicht jedoch unterschiedlich. Beispiel: bash fügt alle Komponenten der Pipeline in separate Unterschalen ein (es sei denn, Sie verwenden die Option lastpipe in Aufrufen, in denen die Jobsteuerung nicht aktiviert ist), aber AT & T ksh und zsh führt den letzten Teil in der aktuellen Shell aus (beide Verhaltensweisen sind von POSIX zulässig). Somit

    exit | exit | exit
    

    macht im Grunde nichts in bash, sondern verlässt das zsh wegen dem letztenexit.

  • coproc exit Führt auch exit in einer Subshell aus.

87
jimmij

Das Ausführen von exit in einer Subshell ist eine Gefahr:

#!/bin/bash
function calc { echo 42; exit 1; }
echo $(calc)

Das Skript druckt 42, verlässt die nterschale mit dem Rückkehrcode 1 Und fährt mit dem Skript fort. Selbst das Ersetzen des Aufrufs durch echo $(CALC) || exit 1 hilft nicht, da der Rückkehrcode von echo unabhängig vom Rückkehrcode von calc 0 ist. Und calc wird vor echo ausgeführt.

Noch rätselhafter ist es, den Effekt von exit zu vereiteln, indem Sie ihn wie im folgenden Skript in local einbinden. Ich bin über das Problem gestolpert, als ich eine Funktion zum Überprüfen eines Eingabewerts geschrieben habe. Beispiel:

Ich möchte eine Datei mit dem Namen "Jahr Monat Tag.log" erstellen, d. H. 20141211.log Für heute. Das Datum wird von einem Benutzer eingegeben, der möglicherweise keinen angemessenen Wert angibt. Daher überprüfe ich in meiner Funktion fname den Rückgabewert von date, um die Gültigkeit der Benutzereingabe zu überprüfen:

#!/bin/bash

doit ()
    {
    local FNAME=$(fname "$1") || exit 1
    touch "${FNAME}"
    }

fname ()
    {
    date +"%Y%m%d.log" -d"$1" 2>/dev/null
    if [ "$?" != 0 ] ; then
        echo "fname reports \"Illegal Date\"" >&2
        exit 1
    fi
    }

doit "$1"

Sieht gut aus. Lassen Sie das Skript s.sh Nennen. Wenn der Benutzer das Skript mit ./s.sh "Thu Dec 11 20:45:49 CET 2014" Aufruft, wird die Datei 20141211.log Erstellt. Wenn der Benutzer jedoch ./s.sh "Thu hec 11 20:45:49 CET 2014" Eingebt, gibt das Skript Folgendes aus:

fname reports "Illegal Date"
touch: cannot touch ‘’: No such file or directory

Die Zeile fname… Zeigt an, dass die fehlerhaften Eingabedaten in der Subshell erkannt wurden. Die exit 1 Am Ende der Zeile local … Wird jedoch niemals ausgelöst, da die Direktive local immer 0 Zurückgibt. Dies liegt daran, dass local ausgeführt wird nach$(fname) und somit seinen Rückkehrcode überschreibt. Aus diesem Grund fährt das Skript fort und ruft touch mit einem leeren Parameter auf. 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 klar zu machen: Ohne local wird das Skript wie erwartet abgebrochen, wenn ein ungültiges Datum eingegeben wird.

Die Lösung besteht darin, die Linie wie zu teilen

local FNAME
FNAME=$(fname "$1") || exit 1

Das seltsame Verhalten entspricht der Dokumentation von local in der Manpage von bash: "Der Rückgabestatus ist 0, es sei denn, local wird außerhalb einer Funktion verwendet, ein ungültiger Name wird angegeben oder name ist eine schreibgeschützte Variable."

Obwohl ich kein Fehler bin, denke ich, dass das Verhalten von Bash nicht intuitiv ist. Mir ist die Reihenfolge der Ausführung bekannt, local sollte dennoch eine fehlerhafte Zuordnung nicht maskieren.

Meine erste Antwort enthielt einige Ungenauigkeiten. Nach einer aufschlussreichen und eingehenden Diskussion mit mikeserv (danke dafür) habe ich mich daran gemacht, sie zu reparieren.

13
hermannk

Die eigentliche Lösung:

#!/bin/bash

function bla() {
    return 1
}

bla || { echo '1'; exit 1; }

echo '2'

Die Fehlergruppierung wird nur ausgeführt, wenn bla einen Fehlerstatus zurückgibt und exit sich nicht in einer Subshell befindet, sodass das gesamte Skript gestoppt wird.

2
Walf

Die Klammern beginnen mit nterschale und der Ausgang verlässt nur diese Unterschale.

Sie können den Exitcode mit $? Lesen und in Ihr Skript einfügen, um das Skript zu beenden, wenn die Subshell beendet wurde:

#!/bin/bash

function bla() {
    return 1
}

bla || ( echo '1' ; exit 1 )

exitcode=$?
if [ $exitcode != 0 ]; then exit $exitcode; fi

echo '2'
1
rubo77