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 xxx
gdzie xxx
jest kod błędu, który możesz chcieć przywrócić do systemu operacyjnego (od 0 do 255). Tu 125
i 64
są 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>&2
rade
exit
sama 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_error
to:
if
blokó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 -e
i na sekundę można zrobić trap
naEXIT
exit
w moim skrypcie / funkcji?Używanie exit
ogó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 -e
do błędu przy wyjściu?Nie!
set -e
był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 test
sprawdzenie 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 pipefail
opcję, ale nadal jest to zastrzeżenie.
set -e
somecommand that fails | cat -
echo survived
trap
przy 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 trap
na ERR
pseudo sygnale.
ERR
Puł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 ERR
sygnał, 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ł false
w 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
trap
Zapewnia 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 -e
nie zadziała. false
Ogranicza 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ę trap
ró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 false
nie 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ć exit
skryptu:
if [ condition ]; then
echo "Test cases failed!!" >> "${logFile}";
# Clean up if needed
exit 1;
fi
Należy zauważyć, że exit 1
oznacza 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 %N
to 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.%N
Do 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"