Jak wyłapać błędy w skryptach Bash w systemie Linux?
Opublikowany: 2022-08-27Domyślnie skrypt Bash w systemie Linux zgłosi błąd, ale będzie działał dalej. Pokażemy Ci, jak samemu radzić sobie z błędami, abyś mógł zdecydować, co musi się wydarzyć dalej.
Obsługa błędów w skryptach
Obsługa błędów jest częścią programowania. Nawet jeśli piszesz bezbłędny kod, nadal możesz napotkać błędy. Środowisko na komputerze zmienia się w czasie, gdy instalujesz i odinstalowujesz oprogramowanie, tworzysz katalogi oraz przeprowadzasz uaktualnienia i aktualizacje.
Na przykład skrypt, który działał bez problemów, może napotkać trudności, jeśli zmienią się ścieżki katalogów lub zmienią się uprawnienia do pliku. Domyślną akcją powłoki Bash jest wydrukowanie komunikatu o błędzie i kontynuowanie wykonywania skryptu. To jest niebezpieczne domyślnie.
Jeśli akcja, która się nie powiodła, jest krytyczna dla innego przetwarzania lub akcji, która ma miejsce później w skrypcie, ta krytyczna akcja nie powiedzie się. Jak katastrofalne okazuje się to, zależy od tego, co próbuje zrobić twój skrypt.
Bardziej niezawodny schemat wykrywałby błędy i pozwalał skryptowi działać, gdyby musiał się zamknąć lub spróbować naprawić stan błędu. Na przykład, jeśli brakuje katalogu lub pliku, wystarczy, że skrypt je odtworzy.
Jeśli skrypt napotkał problem, którego nie może rozwiązać, może się zamknąć. Jeśli skrypt musi zostać zamknięty, może mieć możliwość wykonania dowolnego wymaganego czyszczenia, takiego jak usunięcie plików tymczasowych lub zapisanie stanu błędu i przyczyny zamknięcia w pliku dziennika.
Wykrywanie statusu wyjścia
Polecenia i programy generują wartość, która jest wysyłana do systemu operacyjnego po ich zakończeniu. Nazywa się to ich statusem wyjścia. Ma wartość zero, jeśli nie było błędów, lub pewną wartość niezerową, jeśli wystąpił błąd.
Możemy sprawdzić status wyjścia — znany również jako kod powrotu — poleceń używanych przez skrypt i określić, czy polecenie zakończyło się powodzeniem, czy nie.
W Bash zero równa się prawdzie. Jeśli odpowiedź z polecenia jest inna niż prawda, wiemy, że wystąpił problem i możemy podjąć odpowiednie działania.
Skopiuj ten skrypt do edytora i zapisz go w pliku o nazwie „bad_command.sh”.
#!/kosz/bash if ( ! bad_command ); następnie echo "bad_command oznaczyło błąd." wyjście 1 fi
Będziesz musiał uczynić skrypt wykonywalnym za pomocą polecenia chmod
. Jest to krok, który jest wymagany, aby każdy skrypt był wykonywalny, więc jeśli chcesz wypróbować skrypty na własnym komputerze, pamiętaj, aby zrobić to dla każdego z nich. W każdym przypadku zastąp nazwę odpowiedniego skryptu.
chmod +x bad_command.sh
Po uruchomieniu skryptu widzimy oczekiwany komunikat o błędzie.
./złe_polecenie.sh
Nie ma takiego polecenia jak „złe_polecenie”, ani nie jest to nazwa funkcji w skrypcie. Nie można go wykonać, więc odpowiedź nie jest równa zero. Jeśli odpowiedź nie jest równa zero — wykrzyknik jest tutaj używany jako logiczny operator NOT
— wykonywana jest treść instrukcji if
.
W rzeczywistym skrypcie może to zakończyć skrypt, co robi nasz przykład, lub może spróbować naprawić stan błędu.
Może wyglądać na to, że linia exit 1
jest zbędna. W końcu w skrypcie nie ma nic więcej, a i tak się zakończy. Ale użycie polecenia exit
pozwala nam przekazać kod wyjścia z powrotem do powłoki. Jeśli nasz skrypt zostanie kiedykolwiek wywołany z drugiego skryptu, ten drugi skrypt będzie wiedział, że ten skrypt napotkał błędy.
Możesz użyć logicznego operatora OR
z kodem zakończenia polecenia i wywołać inne polecenie lub funkcję w swoim skrypcie, jeśli odpowiedź od pierwszego polecenia jest niezerowa.
polecenie_1 || polecenie_2
Działa to, ponieważ albo pierwsze polecenie działa OR
drugie. Pierwsze polecenie jest uruchamiane po lewej stronie. Jeśli się powiedzie, drugie polecenie nie zostanie wykonane. Ale jeśli pierwsze polecenie się nie powiedzie, wykonywane jest drugie polecenie. Możemy więc ustrukturyzować kod w ten sposób. To jest „logiczne-lub./sz”.
#!/kosz/bash obsługa_błędów() { echo "Błąd: ($?) $1" wyjście 1 } złe_polecenie || error_handler "bad_command nie powiodło się, wiersz: ${LINENO}"
Zdefiniowaliśmy funkcję o nazwie error_handler
. To wypisuje kod zakończenia nieudanego polecenia, przechowywanego w zmiennej $?
oraz wiersz tekstu, który jest do niego przekazywany, gdy funkcja jest wywoływana. Odbywa się to w zmiennej $1
. Funkcja kończy skrypt z kodem zakończenia jeden.
Skrypt próbuje uruchomić bad_command
, co oczywiście kończy się niepowodzeniem, więc polecenie na prawo od logicznego operatora OR
, ||
, jest wykonywany. Wywołuje to funkcję error_handler
i przekazuje ciąg, który nazywa polecenie, które się nie powiodło, i zawiera numer wiersza polecenia, które się nie powiodło.
Uruchomimy skrypt, aby zobaczyć komunikat obsługi błędu, a następnie sprawdzimy status wyjścia skryptu za pomocą echo.
./logiczny-lub.sh
echo $?
Nasza mała funkcja error_handler
podaje kod zakończenia próby uruchomienia bad_command
, nazwę polecenia i numer wiersza. Jest to przydatna informacja podczas debugowania skryptu.
Kod wyjścia skryptu to jeden. Status wyjścia 127 zgłoszony przez error_handler
oznacza „nie znaleziono polecenia”. Gdybyśmy chcieli, moglibyśmy użyć tego jako kodu wyjścia skryptu, przekazując go do komendy exit
.
Innym podejściem byłoby rozwinięcie error_handler
, aby sprawdzić różne możliwe wartości statusu wyjścia i odpowiednio wykonać różne akcje, używając tego typu konstrukcji:
kod_wyjścia=$? if [ $ kod_wyjścia -eq 1 ]; następnie echo "Operacja niedozwolona" elif [ $ kod_wyjścia -eq 2 ]; następnie echo "Niewłaściwe użycie wbudowanych powłok" . . . elif [ $status -eq 128 ]; następnie echo "Nieprawidłowy argument" fi
Używanie zestawu do wymuszenia wyjścia
Jeśli wiesz, że chcesz, aby skrypt kończył się za każdym razem, gdy wystąpi błąd, możesz go do tego wymusić. oznacza to, że rezygnujesz z szansy na jakiekolwiek czyszczenie — lub dalsze szkody — ponieważ twój skrypt kończy działanie, gdy tylko wykryje błąd.
Aby to zrobić, użyj polecenia set
z opcją -e
(błąd). To mówi skryptowi, aby zakończył działanie, gdy polecenie nie powiedzie się lub zwróci kod zakończenia większy niż zero. Ponadto użycie opcji -E
zapewnia, że wykrywanie błędów i wyłapywanie działa w funkcjach powłoki.
Aby przechwycić również niezainicjowane zmienne, dodaj opcję -u
(unset). Aby upewnić się, że błędy są wykrywane w sekwencjach potokowych, dodaj opcję -o pipefail
. Bez tego kod zakończenia sekwencji poleceń w potoku jest kodem zakończenia ostatniego polecenia w sekwencji. Błędne polecenie w środku sekwencji potoku nie zostanie wykryte. Opcja -o pipefail
musi znajdować się na liście opcji.
Sekwencja do dodania na początek skryptu to:
set -Eeuo pipefail
Oto krótki skrypt o nazwie „unset-var.sh”, zawierający nieustawioną zmienną.
#!/kosz/bash set -Eeou pipefail echo "$unset_variable" echo "Czy widzimy tę linię?"
Kiedy uruchamiamy skrypt, unset_variable jest rozpoznawana jako niezainicjowana zmienna i skrypt zostaje zakończony.
./unset-var.sh
Drugie polecenie echo
nigdy nie jest wykonywane.
Korzystanie z pułapki z błędami
Polecenie Bash trap pozwala wyznaczyć polecenie lub funkcję, która powinna zostać wywołana, gdy zostanie podniesiony określony sygnał. Zwykle służy to do przechwytywania sygnałów, takich jak SIGINT
, które są wywoływane po naciśnięciu kombinacji klawiszy Ctrl+C. Ten skrypt to „signint.sh”.
#!/kosz/bash trap "echo -e '\nZakończone przez Ctrl+c'; wyjście" SIGINT licznik=0 podczas gdy prawda robić echo "Numer pętli:" $((++counter)) spać 1 Gotowe
Polecenie trap
zawiera polecenie echo
i polecenie exit
. Zostanie wywołany, gdy zostanie podniesiony SIGINT
. Reszta skryptu to prosta pętla. Jeśli uruchomisz skrypt i naciśniesz Ctrl+C, zobaczysz komunikat z definicji trap
, a skrypt zakończy działanie.
./signt.sh
Możemy użyć trap
z sygnałem ERR
, aby wyłapać pojawiające się błędy. Można je następnie wprowadzić do polecenia lub funkcji. To jest „trap.sh”. Wysyłamy powiadomienia o błędach do funkcji o nazwie error_handler
.
#!/kosz/bash pułapka 'obsługa_błędów $? $LINENO'BŁĄD obsługa_błędów() { echo "Błąd: ($1) wystąpił w dniu $2" } Główny() { echo "Wewnątrz funkcji main()" złe_polecenie druga trzeci wyjść $? } druga() { echo "Po wywołaniu funkcji main()" echo "Wewnątrz funkcji second()" } trzeci () { echo "Wewnątrz funkcji trzeciej()" } Główny
Większość skryptu znajduje się wewnątrz funkcji main
, która wywołuje second
i third
funkcję. W przypadku napotkania błędu — w tym przypadku, ponieważ bad_command
nie istnieje — instrukcja trap
kieruje błąd do funkcji error_handler
. Przekazuje kod wyjścia z polecenia zakończonego niepowodzeniem i numer wiersza do funkcji error_handler
.
./trap.sh
Nasza funkcja error_handler
po prostu wyświetla szczegóły błędu w oknie terminala. Jeśli chcesz, możesz dodać polecenie exit
do funkcji, aby skrypt się zakończył. Możesz też użyć serii instrukcji if/elif/fi
, aby wykonać różne działania dla różnych błędów.
Niektóre błędy można naprawić, inne mogą wymagać zatrzymania skryptu.
Ostatnia wskazówka
Wyłapywanie błędów często oznacza uprzedzanie rzeczy, które mogą pójść nie tak, i wprowadzanie kodu, aby poradzić sobie z tymi ewentualnościami, jeśli się pojawią. To oprócz upewnienia się, że przepływ wykonywania i wewnętrzna logika skryptu są poprawne.
Jeśli użyjesz tego polecenia do uruchomienia skryptu, Bash wyświetli wynik śledzenia podczas wykonywania skryptu:
bash -x twój-skrypt.sh
Bash zapisuje dane wyjściowe śledzenia w oknie terminala. Pokazuje każde polecenie z jego argumentami — jeśli jakieś ma. Dzieje się tak po rozwinięciu poleceń, ale przed ich wykonaniem.
Może to być ogromna pomoc w tropieniu nieuchwytnych błędów.
POWIĄZANE: Jak sprawdzić poprawność składni skryptu Linux Bash przed jego uruchomieniem?