Podnieś błąd w skrypcie Bash

104

Chcę zgłosić błąd w skrypcie Bash z komunikatem „Przypadki testowe nie powiodły się !!!”. Jak to zrobić w Bash?

Na przykład:

if [ condition ]; then
    raise error "Test cases failed !!!"
fi
Naveen Kumar
źródło
1
Co chcesz zrobić z tym błędem? Jak nazywa się twój skrypt? Czy to tylko jeden skrypt czy wiele skryptów? Jakie będą zastosowania twojego skryptu?
Etan Reisner
tylko jeden skrypt. Nazwałem to używając terminala ubuntu, takiego jak ./script/test.sh
Naveen Kumar
unix.stackexchange.com
Seth McClaine
Nie ma miłości echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress?
infixed

Odpowiedzi:

121

Zależy to od tego, gdzie chcesz przechowywać komunikat o błędzie.

Możesz wykonać następujące czynności:

echo "Error!" > logfile.log
exit 125

Lub następujące:

echo "Error!" 1>&2
exit 64

Zgłaszając wyjątek, zatrzymujesz wykonywanie programu.

Możesz również użyć czegoś takiego, jak exit xxxgdzie xxxjest kod błędu, który możesz chcieć przywrócić do systemu operacyjnego (od 0 do 255). Tu 125i 64są tylko losowe kody można wyjść z. Kiedy trzeba, aby wskazać, że program OS zatrzymany nienormalnie (wystąpił np. Błąd), trzeba zdać niezerowy kod wyjścia do exit.

Jak zauważył @chepner , możesz to zrobić exit 1, co będzie oznaczać nieokreślony błąd .

ForceBru
źródło
12
lub możesz wysłać go na stderr, gdzie powinny pojawić się błędy.
jak wysłać to na stderr?
Naveen Kumar
2
@ user3078630, właśnie zredagowałem odpowiedź. 1>&2rade
ForceBru
Jeśli jest to błąd, również powinieneś wyjść z niezerowym kodem zakończenia. exitsama używa statusu wyjścia ostatnio wykonanego polecenia, który może wynosić 0.
chepner
3
Jeśli nie masz na myśli konkretnego znaczenia, powinieneś po prostu użyć exit 1, co zgodnie z konwencją oznacza nieokreślony błąd.
chepner
37

Podstawowa obsługa błędów

Jeśli moduł uruchamiający przypadek testowy zwraca niezerowy kod dla testów zakończonych niepowodzeniem, możesz po prostu napisać:

test_handler test_case_x; test_result=$?
if ((test_result != 0)); then
  printf '%s\n' "Test case x failed" >&2  # write error message to stderr
  exit 1                                  # or exit $test_result
fi

Albo jeszcze krócej:

if ! test_handler test_case_x; then
  printf '%s\n' "Test case x failed" >&2
  exit 1
fi

Lub najkrótszy:

test_handler test_case_x || { printf '%s\n' "Test case x failed" >&2; exit 1; }

Aby wyjść z kodem wyjścia test_handler:

test_handler test_case_x || { ec=$?; printf '%s\n' "Test case x failed" >&2; exit $ec; }

Zaawansowana obsługa błędów

Jeśli chcesz przyjąć bardziej kompleksowe podejście, możesz skorzystać z modułu obsługi błędów:

exit_if_error() {
  local exit_code=$1
  shift
  [[ $exit_code ]] &&               # do nothing if no error code passed
    ((exit_code != 0)) && {         # do nothing if error code is 0
      printf 'ERROR: %s\n' "$@" >&2 # we can use better logging here
      exit "$exit_code"             # we could also check to make sure
                                    # error code is numeric when passed
    }
}

następnie wywołaj go po uruchomieniu przypadku testowego:

run_test_case test_case_x
exit_if_error $? "Test case x failed"

lub

run_test_case test_case_x || exit_if_error $? "Test case x failed"

Zalety posiadania takiej obsługi błędów exit_if_errorto:

  • możemy ustandaryzować całą logikę obsługi błędów, taką jak logowanie , drukowanie śladu stosu , powiadamianie, czyszczenie itp. w jednym miejscu
  • sprawiając, że program obsługi błędów pobiera kod błędu jako argument, możemy oszczędzić wywołującemu bałaganu ifbloków, które testują kody wyjścia pod kątem błędów
  • jeśli mamy obsługę sygnału (używając pułapki ), możemy z niej wywołać procedurę obsługi błędów

Obsługa błędów i biblioteka logowania

Oto pełna implementacja obsługi błędów i rejestrowania:

https://github.com/codeforester/base/blob/master/lib/stdlib.sh


Powiązane posty

codeforester
źródło
9

Istnieje kilka innych sposobów rozwiązania tego problemu. Zakładając, że jednym z twoich wymagań jest uruchomienie skryptu / funkcji powłoki zawierającej kilka poleceń powłoki i sprawdzenie, czy skrypt został uruchomiony pomyślnie, i wyświetlenie błędów w przypadku awarii.

Polecenia powłoki generalnie opierają się na zwracanych kodach wyjścia, aby powiadomić powłokę o powodzeniu lub niepowodzeniu z powodu nieoczekiwanych zdarzeń.

Więc to, co chcesz zrobić, należy do tych dwóch kategorii

  • wyjście w przypadku błędu
  • wyjście i czyszczenie w przypadku błędu

W zależności od tego, który chcesz zrobić, dostępne są opcje powłoki. W pierwszym przypadku, powłoka zapewnia odpowiednią opcję z set -ei na sekundę można zrobić trapnaEXIT

Czy powinienem używać exitw moim skrypcie / funkcji?

Używanie exitogólnie poprawia czytelność W niektórych procedurach, gdy znasz odpowiedź, chcesz natychmiast wyjść z procedury wywoływania. Jeśli procedura jest zdefiniowana w taki sposób, że po wykryciu błędu nie wymaga dalszego czyszczenia, brak natychmiastowego zakończenia oznacza, że ​​musisz napisać więcej kodu.

Dlatego w przypadkach, gdy musisz wykonać czynności porządkowe w skrypcie, aby zakończyć działanie skryptu, lepiej nie używać exit.

Czy powinienem użyć set -edo błędu przy wyjściu?

Nie!

set -ebyła próbą dodania "automatycznego wykrywania błędów" do powłoki. Jego celem było spowodowanie przerwania działania powłoki za każdym razem, gdy wystąpi błąd, ale wiąże się z wieloma potencjalnymi pułapkami, na przykład

  • Polecenia wchodzące w skład testu if są odporne. W tym przykładzie, jeśli spodziewasz się, że przerwie on testsprawdzenie nieistniejącego katalogu, nie zrobi tego, przechodzi do warunku else

    set -e
    f() { test -d nosuchdir && echo no dir; }
    f
    echo survived
  • Polecenia w potoku innym niż poprzedni są odporne. W poniższym przykładzie, ponieważ uwzględniono kod zakończenia ostatnio wykonanego (najbardziej po prawej) polecenia ( cat) i zakończył się on pomyślnie. Można tego uniknąć, ustawiając set -o pipefailopcję, ale nadal jest to zastrzeżenie.

    set -e
    somecommand that fails | cat -
    echo survived 

Zalecane do użycia - trapprzy wyjściu

Werdykt jest taki, że jeśli chcesz być w stanie obsłużyć błąd zamiast ślepego zamykania, zamiast używać set -e, użyj a trapna ERRpseudo sygnale.

ERRPułapka nie jest do uruchomienia kodu, gdy sama powłoka kończy pracę z niezerowym kodem błędu, ale kiedy każdy run komenda tą skorupą, która nie jest częścią stanu (jak na razie cmd, czy cmd ||) wychodzi ze stanem wyjściowym niezerowej .

Ogólna praktyka jest taka, że ​​definiujemy procedurę obsługi pułapki, aby zapewnić dodatkowe informacje debugowania, która linia i co powoduje zakończenie. Pamiętaj, że kod zakończenia ostatniego polecenia, które spowodowało ERRsygnał, byłby nadal dostępny w tym momencie.

cleanup() {
    exitcode=$?
    printf 'error condition hit\n' 1>&2
    printf 'exit code returned: %s\n' "$exitcode"
    printf 'the command executing at the time of the error was: %s\n' "$BASH_COMMAND"
    printf 'command present on line: %d' "${BASH_LINENO[0]}"
    # Some more clean up code can be added here before exiting
    exit $exitcode
}

i po prostu używamy tego programu obsługi, jak poniżej, na górze skryptu, który nie działa

trap cleanup ERR

Łącząc to razem w prostym skrypcie, który zawierał falsew linii 15, informacje, które otrzymasz jako

error condition hit
exit code returned: 1
the command executing at the time of the error was: false
command present on line: 15

trapZapewnia również opcje niezależnie od błędu po prostu uruchomić oczyszczanie po zakończeniu powłoki (np twoje wyjść skrypt powłoki), na sygnale EXIT. Możesz także przechwytywać wiele sygnałów w tym samym czasie. Listę obsługiwanych sygnałów do pułapkowania można znaleźć na stronie trap.1p - podręcznika Linux

Inną rzeczą, na którą należy zwrócić uwagę, jest zrozumienie, że żadna z podanych metod nie działa, jeśli masz do czynienia z powłokami podrzędnymi, w którym to przypadku może być konieczne dodanie własnej obsługi błędów.

  • Na podpowłoce z set -enie zadziała. falseOgranicza się do sub-shell i nigdy nie pobiera propagowane do powłoki macierzystej. Aby wykonać tutaj obsługę błędów, dodaj własną logikę do wykonania(false) || false

    set -e
    (false)
    echo survived
  • To samo dzieje się traprównież z . Poniższa logika nie zadziała z powodów wymienionych powyżej.

    trap 'echo error' ERR
    (false)
Inian
źródło
5

Oto prosta pułapka, która wypisuje do STDERR ostatni argument dowolnego argumentu, który się nie powiódł, zgłasza wiersz, w którym wystąpił błąd, i zamyka skrypt z numerem linii jako kodem zakończenia. Pamiętaj, że nie zawsze są to świetne pomysły, ale pokazuje to kreatywne aplikacje, na których możesz zbudować.

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR

Umieściłem to w skrypcie z pętlą, aby to przetestować. Po prostu sprawdzam trafienie w jakieś losowe liczby; możesz użyć rzeczywistych testów. Jeśli muszę się wycofać, nazywam fałsz (co uruchamia pułapkę) z wiadomością, którą chcę rzucić.

Aby uzyskać bardziej rozbudowaną funkcjonalność, niech pułapka wywoła funkcję przetwarzania. Zawsze możesz użyć instrukcji case na swoim arg ($ _), jeśli chcesz zrobić więcej porządków itp. Przypisz do zmiennej, aby uzyskać trochę cukru syntaktycznego -

trap 'echo >&2 "$_ at $LINENO"; exit $LINENO;' ERR
throw=false
raise=false

while :
do x=$(( $RANDOM % 10 ))
   case "$x" in
   0) $throw "DIVISION BY ZERO" ;;
   3) $raise "MAGIC NUMBER"     ;;
   *) echo got $x               ;;
   esac
done

Przykładowe dane wyjściowe:

# bash tst
got 2
got 8
DIVISION BY ZERO at 6
# echo $?
6

Oczywiście, że możesz

runTest1 "Test1 fails" # message not used if it succeeds

Dużo miejsca na ulepszenia projektu.

Wady obejmują fakt, że falsenie jest ładna (a więc cukier), a inne rzeczy, w których wyzwolenie pułapki może wyglądać trochę głupio. Mimo to podoba mi się ta metoda.

Paul Hodges
źródło
4

Masz 2 opcje: Przekieruj dane wyjściowe skryptu do pliku, Wprowadź plik dziennika do skryptu i

  1. Przekierowywanie wyjścia do pliku :

Tutaj zakładasz, że skrypt wyświetla wszystkie niezbędne informacje, w tym ostrzeżenia i komunikaty o błędach. Następnie możesz przekierować dane wyjściowe do wybranego pliku.

./runTests &> output.log

Powyższe polecenie przekierowuje zarówno standardowe wyjście, jak i wyjście błędu do pliku dziennika.

Korzystając z tego podejścia, nie musisz wprowadzać pliku dziennika do skryptu, więc logika jest nieco łatwiejsza.

  1. Wprowadź plik dziennika do skryptu :

W swoim skrypcie dodaj plik dziennika albo przez zakodowanie go na stałe:

logFile='./path/to/log/file.log'

lub przekazując go przez parametr:

logFile="${1}"  # This assumes the first parameter to the script is the log file

Dobrym pomysłem jest dodanie sygnatury czasowej w momencie wykonywania do pliku dziennika w górnej części skryptu:

date '+%Y%-m%d-%H%M%S' >> "${logFile}"

Następnie możesz przekierować komunikaty o błędach do pliku dziennika

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
fi

Spowoduje to dołączenie błędu do pliku dziennika i kontynuowanie wykonywania. Jeśli chcesz zatrzymać wykonywanie, gdy wystąpią krytyczne błędy, możesz użyć exitskryptu:

if [ condition ]; then
    echo "Test cases failed!!" >> "${logFile}"; 
    # Clean up if needed
    exit 1;
fi

Należy zauważyć, że exit 1oznacza to, że program przestał działać z powodu nieokreślonego błędu. Możesz to dostosować, jeśli chcesz.

Korzystając z tego podejścia, możesz dostosować swoje dzienniki i mieć inny plik dziennika dla każdego składnika skryptu.


Jeśli masz stosunkowo mały skrypt lub chcesz wykonać skrypt innej osoby bez modyfikowania go, pierwsze podejście jest bardziej odpowiednie.

Jeśli zawsze chcesz, aby plik dziennika znajdował się w tej samej lokalizacji, jest to lepsza opcja niż 2. Również jeśli utworzyłeś duży skrypt z wieloma komponentami, możesz chcieć rejestrować każdą część inaczej, a drugie podejście jest jedyne opcja.

alamoot
źródło
3

Często uważam, że przydatne jest napisanie funkcji do obsługi komunikatów o błędach, aby kod był ogólnie bardziej przejrzysty.

# Usage: die [exit_code] [error message]
die() {
  local code=$? now=$(date +%T.%N)
  if [ "$1" -ge 0 ] 2>/dev/null; then  # assume $1 is an error code if numeric
    code="$1"
    shift
  fi
  echo "$0: ERROR at ${now%???}${1:+: $*}" >&2
  exit $code
}

Pobiera kod błędu z poprzedniego polecenia i używa go jako domyślnego kodu błędu podczas zamykania całego skryptu. Odnotowuje również czas, z mikrosekundami, w których jest obsługiwany (data GNU %Nto nanosekundy, które później skracamy do mikrosekund).

Jeśli pierwsza opcja to zero lub dodatnia liczba całkowita, staje się kodem zakończenia i usuwamy go z listy opcji. Następnie zgłaszamy wiadomość do błędu standardowego, z nazwą skryptu, słowem „ERROR” i czasem (używamy rozwijania parametrów, aby skrócić nanosekundy do mikrosekund, lub w przypadku czasów innych niż GNU, aby skrócić np. 12:34:56.%NDo 12:34:56). Dwukropek i spacja są dodawane po słowie ERROR, ale tylko wtedy, gdy pojawia się komunikat o błędzie. Na koniec wychodzimy ze skryptu za pomocą wcześniej określonego kodu wyjścia, wyzwalając wszelkie pułapki w normalny sposób.

Kilka przykładów (załóżmy, że kod istnieje script.sh):

if [ condition ]; then die 123 "condition not met"; fi
# exit code 123, message "script.sh: ERROR at 14:58:01.234564: condition not met"

$command |grep -q condition || die 1 "'$command' lacked 'condition'"
# exit code 1, "script.sh: ERROR at 14:58:55.825626: 'foo' lacked 'condition'"

$command || die
# exit code comes from command's, message "script.sh: ERROR at 14:59:15.575089"
Adam Katz
źródło