Logo for e-Solutions

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ć.