Skrypty w bash z obsługą błędów

Osoby przyzwyczajone do pisania w językach takich jak C, Java, JavaScript itp. gdy zaczynają pisać skrypty w Bashu, zapominają często o innym zachowaniu gdy pojawi się błąd. Zwykle, gdy np. odwołamy się do niezainicjalozowanej zmiennej, przerywane jest wykonywanie, oraz zrzucany jest błąd. W bashu skrypt będzie wykonywany dalej, o ile nie określimy by było inaczej. W tym artykule omówię w jaki sposób sterować sposobem zachowania skryptu, przy pomocy wbudowanej komendy set. Komenda set pozwala zmieniać opcje powłoki i wyświetlać nazwy i wartości jej zmiennych. Gdy wywołamy
set
bez parametrów, pojawi się lista zdefiniowanych zmiennych oraz funkcji.
set -e
Opcja -e spowoduje, że skrypt przerwie swoje działanie, gdy tylko pojawi się błąd. W zasadzie jest to najważniejsza opcja, która umożliwi nam poprawne zachowanie się skryptu, uniemożliwiając wykonywania dalszych instrukcji po błędzie. Domyślnie błąd jest ingorowany i skrypt dalej wykonywany. Przyjrzyjmy się następującemy skryptowi.
#!/bin/bash
blad
echo "A to się wykona"
Po jego wykonaniu pojawi się
./test.sh: linia 2: blad: nie znaleziono polecenia
A to się wykona
Porawiamy nasz skrypt
#!/bin/bash
set -e
blad
echo "A to się nie wykona"
Dzięki czemu jego wykonanie zostanie przerwane w odpowiednim momencie.
./test.sh: linia 3: blad: nie znaleziono polecenia
Oczywiście, gdy pojawi się dowolna komenda, która zwróci do środowiska wartość inną niż 0 nastąpi przerwanie wykonywania. Przykład:
#!/bin/bash
set -e
ls nieistniejacy_plik
echo "A to się nie wykona"
ls: nie ma dostępu do 'nieistniejacy_plik': Nie ma takiego pliku ani katalogu
set -o pipefail
Powłoka bash sprawdza tylko kod zwracany przez ostatnią komendę w pipelinie. Czyli, jeśli wykonujemy kilka komend, gdzie dla następnej wejście pochodzi z wyjścia poprzedniej, to nawet jeśli wcześniejsza zwróci błąd, to domyślnie zostanie on pominięty. Samo dodanie opcji -e nie pomoże. Rozszerzymy nasze ustawienia o opcję -o pipefail. Opcja ta ustawi kod wyjścia na taki, jaki komenda ustawi w przypadku błędu, czyli niezerowy, a jeśli wszystkie w pipelinie wykonają się z sukcesem to na 0. Przyjrzyjmy się przykładowi, jak to wygląda gdy nie ustawimy opcji -o pipefail.
#!/bin/bash
set -e
ls nieistniejacy_plik | wc -l
echo "A to się wykona"
ls: nie ma dostępu do 'nieistniejacy_plik': Nie ma takiego pliku ani katalogu
0
A to się wykona
Po naprawieniu skryptu
#!/bin/bash
set -eo pipefail
ls nieistniejacy_plik | wc -l
echo "A to nie wykona się"
ls: nie ma dostępu do 'nieistniejacy_plik': Nie ma takiego pliku ani katalogu
0
Widać tutaj, że wykonanie skryptu zostało przerwane w odpowiednim miejscu. Wykonany został jedynie do końca pipeline.
set -u
Opcja -u powoduje, że powłoka w przypadku natrafienia na nieustawioną zmienną, traktuje to jako błąd i przerywa przetwarzanie skrypt. Bardzo często zdarza się błąd polegający właśnie na nie ustawieniu wartości zmiennej, a potem próbie jej odczytania. Przyjrzyjmy się następującemu skryptowi.
#!/bin/bash
set -eo pipefail
echo $a
echo "A to się wykona"
w efekcie czego
A to się wykona
Natomiast, gdy dodamy opcję -u
#!/bin/bash
set -euo pipefail
echo $a
echo "A to się nie wykona"
otrzymamy stosowny komunikat błędu
./test.sh: linia 3: a: nieustawiona zmienna
Warto też stosować wartości domyślne. Konstrukcja jest następująca ${parameter:-defaultValue}
#!/bin/bash
set -euo pipefail
DEFAULT=1
echo ${a:-$DEFAULT}
echo "A to się wykona"
wynik:
1
A to się wykona
Gdybyśmy chcieli sprawdzać, czy zmienna jest ustawiona, a jeśli nie to wykonać odpowiedni kod, można zrobić to w następujący sposób.
#!/bin/bash
set -euo pipefail
if [ -z "${a:-}" ]; then
echo "Zmienna 'a' nie jest ustawiona"
fi
Należy zwrócić uwagę na zapis ${a:-}. Opcja _-z_sprawdza czy długość łańcucha jest równa 0. W tym przypadku, działa to tak, że ustawiamy wartość domyślną, więc nie dostajemy błędu, ale ta wartość ustawiana jest na pusty łańcuch, więc test zadziała poprawnie.
set -E
Według instrukcji jeśli opcja -E jest ustawiona, to, pułapka (trap) ERR jest dziedziczona przez funkcje powłoki, uzupełnienia poleceń i polecenia wykonywane w środowisku podpowłoki. Normalnie pułapka ERR nie jest dziedziczona w takich przypadkach. Przyjrzyjmy się następującemu kodowi.
#!/bin/bash
set -euo pipefail
trap "echo Pułapka odpalona!" ERR
func()
{
blad
}
func
echo "To się nie wykona"
./test.sh: linia 6: blad: nie znaleziono polecenia
Jak widać pułapka nie odpaliła się. Dodajmy zatem opcję -E. W efekcie czego druga linijka będzie wyglądała taL
set -Eeuo pipefail
A po uruchomieniu skryptu mamy:
./test.sh: linia 6: blad: nie znaleziono polecenia
Pułapka odpalona!
set -x
Bardzo przydatną opcją podczas debugowania skryptu jest opcja -x. Służy ona do drukowania komendy wraz z jej argumentami tuż przed wykonaniem. Jak wygląda wynik skryptu powłoki bash, gdy ta opcja jest ustawiona pozostawiam czytelnikowi do samodzielnego sprawdzenia.
Podsumowanie
Teraz już wiesz, w jaki sposób używać kilku najważniejszych opcji powłoki, tak aby skrypty były bardziej profesjonalnie napisane, oraz dlaczego warto z nich korzystać.