Najlepsza praktyka używania $? w bash?


10

Kiedy czytam tę odpowiedź na temat $? przychodzi mi na myśl kolejne pytanie.

Czy jest jakaś najlepsza praktyka korzystania z $? w bash?


Oto przykład:

Mamy skrypt liniowy i chcielibyśmy wiedzieć, że wszystkie polecenia zostały wykonane poprawnie. Czy uważasz, że można wywołać małą funkcję (nazwijmy ją „did_it_work”), aby sprawdzić kod błędu i przerwać, jeśli nie jest.

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

To podejście oczywiście oznacza, że ​​muszę ręcznie rozwiązać problem, jeśli taki istnieje, i ponownie uruchomić skrypt.

Czy uważasz, że to dobry pomysł, czy jest jakaś inna najlepsza praktyka, aby to zrobić?

/Dzięki

Odpowiedzi:


15

Jednym z powszechnych sposobów jest:

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

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

następnie używasz go w następujący sposób:

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

Lub jeśli chcesz, aby zawierał status wyjścia, możesz zmienić to na:

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

a następnie korzystanie z niego jest nieco łatwiejsze:

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

Gdy zawiedzie, mkdirprawdopodobnie już wydał komunikat o błędzie, więc drugi może być postrzegany jako zbędny i możesz po prostu:

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

(lub użyj pierwszego wariantu diepowyżej bez argumentów)

Na wypadek, gdybyś nie widział command1 || command2wcześniej, działa command1, a jeśli command1zawiedzie, działa command2.

Możesz więc przeczytać to tak: „utwórz katalog lub umrzyj”.

Twój przykład wyglądałby następująco:

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

Lub możesz wyrównać diesdalej po prawej stronie, aby główny kod był bardziej oczywisty.

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

Lub w następującym wierszu, gdy wiersze poleceń są długie:

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

cd some/path ||
  die "cd failed"

some_command ||
  die "some_command failed"

Ponadto, jeśli zamierzasz używać nazwy some/pathwiele razy, przechowuj ją w zmiennej, abyś nie musiał jej ciągle wpisywać, i możesz ją łatwo zmienić, jeśli zajdzie taka potrzeba. Podczas przekazywania argumentów zmiennych do poleceń należy używać --ogranicznika opcji, aby argument nie był traktowany jako opcja, jeśli zaczyna się od -.

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"

9

Możesz przepisać swój kod w następujący sposób:

#!/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

Jeśli tak naprawdę nie musisz rejestrować kodu błędu, ale po prostu czy komenda się powiodła, czy nie, możesz try()dalej skrócić :

function try {
    if ! "$@"
    then
        echo "$1 did not work"
        exit 1
    fi
}

Możesz także użyć kodu w tym formacie. <pre> funkcja try {
BillThor

Jeśli korzystasz z tej funkcji bezpośrednio i nie chcesz wracać z prawdziwej strony, możesz zastąpić return $?:wbudowanym.
BillThor

8

Jeśli naprawdę chcesz exitpopełnić błąd i używasz Bash, powinieneś również rozważyć set -e. Od help set:

-e Wyjdź natychmiast, jeśli polecenie zakończy się ze statusem niezerowym.

To oczywiście nie daje elastyczności funkcji did_it_work (), ale jest to prosty sposób, aby upewnić się, że skrypt bash zatrzymuje się na błędzie bez dodawania wielu wywołań do nowej funkcji.


set -ejest przydatne. Niektóre polecenia zwracają wartość niezerową w normalnych okolicznościach (na przykład diff). Kiedy używam zestawu -e w skrypcie, w którym oczekuję niezerowego zwrotu, robię to command || true.
Shawn J. Goff,

2
Ponadto, nawet jeśli używasz set -e, możesz ustawić „moduł obsługi wyjątków”, aby wychwytywał wszystkie błędy trap did_it_work EXIT.
Gilles „SO- przestań być zły”

1
Jest to część POSIX, a nie funkcja specyficzna dla bash. Użyj go, ale pamiętaj o niektórych pułapkach mywiki.wooledge.org/BashFAQ/105
kmkaplan

@ ShawnJ.Goff Wolę to zrobić command && true. W ten sposób zwracana wartość nie jest zmieniana.
kmkaplan

@kmkaplan Twój ostatni komentarz nie ma dla mnie żadnego sensu. Celem command || truejest zapobieganie set -ewychodzeniu ze skryptu, jeśli commandzwraca niezerowy kod wyjścia. Zmienia kod wyjścia, ponieważ musimy. Jedyne, co command && truerobi, to uruchomić true(zwrócić zerowy kod wyjścia), jeśli polecenie się powiedzie (zwróciło zerowy kod wyjścia) - to kompletny brak operacji.
tripleee
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.