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
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
echo you screwed up at ... | mail -s BUG $bugtrackeremailaddress?
Odpowiedzi:
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 .
1>&2rade
exitsama używa statusu wyjścia ostatnio wykonanego polecenia, który może wynosić 0.
exit 1, co zgodnie z konwencją oznacza nieokreślony błąd.
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; }
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:
ifbloków, które testują kody wyjścia pod kątem błędówOto pełna implementacja obsługi błędów i rejestrowania:
https://github.com/codeforester/base/blob/master/lib/stdlib.sh
__FILE__, __LINE__w bashIstnieje 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
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
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.
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 survivedPolecenia 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 trapprzy wyjściuWerdykt 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 survivedTo samo dzieje się traprównież z . Poniższa logika nie zadziała z powodów wymienionych powyżej.
trap 'echo error' ERR
(false)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.
Masz 2 opcje: Przekieruj dane wyjściowe skryptu do pliku, Wprowadź plik dziennika do skryptu i
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.
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.
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"