Best Practice für die Verwendung von $? in Bash?

10

Wenn ich diese Antwort über $ lese ? Eine andere Frage kommt mir in den Sinn.

Gibt es eine bewährte Methode für die Verwendung von $? in Bash?


Lassen Sie uns ein Beispiel haben:

Wir haben ein lineares Skript und ich würde gerne wissen, dass der gesamte Befehl in Ordnung ausgeführt wurde. Denken Sie, es ist in Ordnung, eine kleine Funktion aufzurufen (nennen wir sie "did_it_work"), den Fehlercode zu überprüfen und zu brechen, wenn dies nicht der Fall ist.

#!/bin/bash 

function did_it_work {
    code=$1
    if [ "$code" -ne "0" ]
    then
        echo "Error failure: code $code "
        exit 1
    fi
}

dir=some/path

mkdir -p $dir
did_it_work $? 

cd $dir
did_it_work $? 

run_some_command
did_it_work $? 

Dieser Ansatz bedeutet natürlich, dass ich das Problem manuell lösen muss, falls vorhanden, und das Skript erneut ausführen muss.

Halten Sie dies für eine gute Idee oder gibt es eine andere bewährte Methode, um dies zu tun?

/Vielen Dank

Johan
quelle

Antworten:

15

Ein üblicher Weg ist:

die() {
    IFS=' ' # make sure "$*" is joined with spaces

    # output the arguments if any on stderr:
    [ "$#" -eq 0 ] || printf '%s\n' "$*" 1>&2
    exit 1
}

dann benutzt du es so:

mkdir -p some/path || die "mkdir failed with status $?"

Wenn Sie möchten, dass der Exit-Status angezeigt wird, können Sie ihn in Folgendes ändern:

die() {
    last_exit_status=$?
    IFS=' '
    printf '%s\n' "FATAL ERROR: $* (status $last_exit_status)" 1>&2
    exit 1
}

und dann ist es ein bisschen einfacher:

mkdir -p some/path || die "mkdir failed"

Wenn dies fehlschlägt, wurde mkdirwahrscheinlich bereits eine Fehlermeldung ausgegeben, sodass die zweite möglicherweise als redundant angesehen wird, und Sie können einfach Folgendes tun:

mkdir -p some/path || exit   # with the same (failing) exit status as mkdir's
mkdir -p some/path || exit 1 # with exit status 1 always

(oder verwenden Sie die erste Variante von dieoben ohne Argument)

Nur für den Fall, dass Sie es noch nicht gesehen command1 || command2haben, wird es ausgeführt command1, und wenn es command1fehlschlägt, wird es ausgeführt command2.

Sie können es also wie "Make the Directory or Die" lesen.

Ihr Beispiel würde folgendermaßen aussehen:

mkdir -p some/path || die "mkdir failed"
cd some/path || die "cd failed"
some_command || die "some_command failed"

Oder Sie können die diesweiter rechts ausrichten , damit der Hauptcode offensichtlicher wird.

mkdir -p some/path         || die "mkdir failed"
cd some/path               || die "cd failed"
some_command               || die "some_command failed"

Oder in der folgenden Zeile, wenn die Befehlszeilen lang sind:

mkdir -p some/path ||
  die "mkdir failed"

cd some/path ||
  die "cd failed"

some_command ||
  die "some_command failed"

Wenn Sie den Namen some/pathmehrmals verwenden möchten, speichern Sie ihn in einer Variablen, damit Sie ihn nicht weiter eingeben müssen, und können ihn bei Bedarf problemlos ändern. Wenn Sie variable Argumente an Befehle übergeben, stellen Sie sicher, dass Sie das --Optionsbegrenzer verwenden, damit das Argument nicht als Option verwendet wird, wenn es mit beginnt -.

dir=some/path
mkdir -p -- "$dir"         || die "Cannot make $dir"
cd -P -- "$dir"            || die "Cannot cd to $dir"
some_command               || die "Cannot run some_command"
Mikel
quelle
9

Sie können Ihren Code folgendermaßen umschreiben:

#!/bin/bash
function try {
    "$@"
    code=$?
    if [ $code -ne 0 ]
    then
        echo "$1 did not work: exit status $code"
        exit 1
    fi
}

try mkdir -p some/path
try cd some/path
try run_some_command

Wenn Sie den Fehlercode nicht protokollieren müssen, sondern nur, ob der Befehl erfolgreich war oder nicht, können Sie ihn try()wie folgt weiter verkürzen :

function try {
    if ! "$@"
    then
        echo "$1 did not work"
        exit 1
    fi
}
Warren Young
quelle
Sie können den Code auch in diesem Format verwenden. <pre> Funktion versuchen {
BillThor
Wenn Sie diese Funktionalität inline verwenden und nicht von der wahren Seite zurückkehren möchten, können Sie sie durch return $?die integrierte :ersetzen.
BillThor
8

Wenn Sie wirklich exiteinen Fehler melden möchten und Bash verwenden, sollten Sie dies ebenfalls berücksichtigen set -e. Von help set:

-e Wird sofort beendet, wenn ein Befehl mit einem Status ungleich Null beendet wird.

Dies gibt Ihnen natürlich nicht die Flexibilität einer did_it_work () -Funktion, aber es ist eine einfache Möglichkeit, sicherzustellen, dass Ihr Bash-Skript bei einem Fehler stoppt, ohne Ihrer neuen Funktion viele Aufrufe hinzuzufügen.

Steven D.
quelle
set -eist nützlich. Es gibt einige Befehle, die unter normalen Umständen ungleich Null zurückgeben (z. B. diff). Wenn ich set -e in einem Skript verwende, in dem ich eine Rückkehr ungleich Null erwarte, tue ich das command || true.
Shawn J. Goff
2
Darüber hinaus können Sie auch bei Verwendung set -eeinen „Ausnahmebehandler“ festlegen, mit dem alle Fehler abgefangen werden trap did_it_work EXIT.
Gilles 'SO - hör auf böse zu sein'
1
Dies ist Teil von POSIX und keine Bash-spezifische Funktion. Verwenden Sie es, aber beachten Sie einige Fallstricke mywiki.wooledge.org/BashFAQ/105
kmkaplan
@ ShawnJ.Goff mache ich lieber command && true. Auf diese Weise wird der Rückgabewert nicht geändert.
kmkaplan
@kmkaplan Dein letzter Kommentar ergibt für mich keinen Sinn. Der Zweck von command || truebesteht darin, das set -eBeenden des Skripts zu verhindern, wenn commandein Exit-Code ungleich Null zurückgegeben wird. Es ändert den Exit-Code, weil wir müssen. Das einzige, was Sie tun command && truemüssen, ist auszuführen true(einen Null-Exit-Code zurückgeben), wenn der Befehl `erfolgreich war (einen Null-Exit-Code zurückgegeben) - es ist ein vollständiges No-Op.
Tripleee