Jednym z najważniejszych i najbardziej czasochłonnych zadań w pisaniu skryptów jest budowanie wszystkich instrukcji warunkowych, których potrzebujemy, aby skrypt był użyteczny i przede wszystkim niezawodny. Przy tej okazji często wspomina się o zasadzie 80-20, gdzie wskazuje się, że 20 procent swojego czasu spędza się na pisaniu głównego scenariusza, a całą resztę poświęca na zapewnienie obsługę wszystkich możliwych ewentualności. W tym kontekście mówi się o integralności proceduralnej scenariusza.
Nie wiem ile w tym jest prawdy, ale na szczęście w przypadku bash instrukcje warunkowe mają dość “nudną” (czytaj: typową) składnię, choć podczas nauki skryptowania problemowi testowania trzeba poświęcić nieco więcej czasu, tak by następnie tworzyć poprawne rozwiązania (“poprawne” w tym sensie, że sprawdzają to co trzeba i jak trzeba).
Test listy poleceń
Zaczniemy jednak od przyjrzenia się testowi z listami poleceń, które na ogół są pierwszym wyborem w przypadku tych najbardziej trywialnych ścieżek decyzyjnych, ponieważ są one jednymi z najprostszych instrukcji warunkowych, jakie możemy stworzyć. Listy wiersza poleceń to dwie lub więcej instrukcji, które są połączone za pomocą notacji AND lub OR:
- &&: AND
- ||: OR
Gdy dwie instrukcje są połączone za pomocą AND , drugie polecenie jest wykonywane tylko wtedy, gdy pierwsze z nich zakończy się sukcesem (w pewnym uproszczeniu chodzi o brak błędu). Natomiast w przypadku OR drugie polecenie zostanie wywołane tylko wtedy, gdy pierwsze zakończy się niepowodzeniem. Decyzja o tym, z którym scenariuszem mamy do czynienia (powodzenie lub niepowodzenie), podejmowana jest poprzez odczytanie kodu wyjścia z aplikacji (polecenia). Zero oznacza pomyślne zakończenie, a wszystko inne niż zero oznacza niepowodzenie. Możemy sprawdzić powodzenie lub niepowodzenie aplikacji, odczytując status wyjścia za pomocą zmiennych systemowych $? (polecenie to musi zostać bezpośrednio po wywoła, wystarczy wówczas klepnąć w terminalu: echo $?
Coby zobaczyć jak to wygląda w akcji, można spróbować uruchomić następujące sekwencje poleceń:
ls /bin
echo $?
ls /bean
echo $?
Z pierwszym razem polecenie echo $? powinno zwrócić zero a za drugim 2, ponieważ nie ma katalogu “bean”.
Samo działanie testu listy poleceń możemy prześledzić posługując się poniższą instrukcją, która ma na celu sprawdzenie czy znajdujemy się obecnie w katalogu domowych. Jeśli ten warunek jest spełniony (scenariusz AND), wówczas w terminalu wyświetli się ścieżka do katalogu domowego. W przeciwnym razie nic się nie zadzieje.
$ test $PWD == $HOME || cd $HOME
Używanie wbudowanego polecenia test
W ostatnim przykładzie pojawiło się “tajemnicze” polecenie test. Dobre opanowanie działania tego polecenia jest warunkiem koniecznym, aby efektywnie posługiwać się instrukcjami warunkowymi w bash.
Polecenie test zawsze zwróci jedną z dwóch wartości, tj. True lub False (odpowiednio jest to wartość 0 lub 1). Podstawowa składnia testu to:
test TESTOWANE_WYRAŻENIE
Umieszczając znak wykrzyknika (!) przed argumentami testu, możemy zanegować jego wynik:
test ! WYRAŻENIE
Gdy polecenie test zostanie uruchomione bez żadnych wyrażeń do oceny, test zawsze zwróci fałsz. Jeśli musimy zawrzeć w naszym teście wiele wyrażeń (warunków), można użyć “spójników” AND lub OR razem, używając odpowiednio atr -a i -o :
test WYRAŻENIE1 -a WYRAŻENIE2
test WYRAŻENIE1 -o WYRAŻENIE2
Możemy również napisać skróconą wersję zastępującą test w nawiasy kwadratowe otaczające wyrażenie, jak pokazano w poniższym przykładzie:
[ TESTOWANE_WYRAŻENIE ]
Należy jednak pamiętać by między wyrażeniem a nawiasami kwadratowymi zostawić spację. W praktyce jest to zdecydowanie częściej stosowane rozwiązanie niż forma “regularna”.
Dostępnych jest wiele operatorów testów podzielonych na trzy główne kategorie: testy plików, testy ciągów znaków i testy arytmetyczne. Pełna dokumentacja na temat tych operatorów znajduje się na stronach podręcznika, natomiast w kolejnych podpunktach opisywane będą najważniejsze czy też właściwie najczęściej używane testy (piszę to oczywiście z mojego punktu widzenia, ale wydaje mi się, że w tym przypadku jestem dość typowym użytkownikiem bash).
Testowanie typów plików
Większość operatorów testów plików to operatory jednoargumentowe, ponieważ – jak nazwa wskazuje – do działania potrzebują tylko jednego argumentu, czyli nazwy pliku do sprawdzenia. Oto dwa bardzo ważne testy plików.
-e zwraca wartość prawdy (zero), jeżeli plik istnieje.
-s zwraca wartość prawdy (zero), jeżeli plik nie jest pusty.
Inne operatory pozwalają na sprawdzenie typu pliku, czyli określenie, czy podana nazwa opisuje zwykły plik, katalog lub jakieś urządzenie specjalne. Dla przykładu 4 z nich:
-f sprawdza czy dany plik jest “zwykłym” plikiem
-d sprawdza czy dany plik jest katalogiem
-h sprawdza czy dany plik jest dowiązaniem symbolicznym
-b sprawdza czy dany plik jest urządzeniem blokowym
Jeszcze inne operatory mogą dotyczyć innych “właściwości” plików:
-r sprawdza czy plik można odczytywać
-w sprawdza czy plikmożna zapisywać
-x sprawdza czy plik można uruchamiać
Porównywanie ciągów znaków
Możemy sprawdzić równość lub nierówność dwóch ciągów znaków. Na przykład, jednym ze sposobów przetestowania użytkownika root jest użycie następującego polecenia:
test $USER = root
Oczywiście możemy to również zapisać za pomocą notacji w nawiasach kwadratowych:
[ $USER = root ]
Jak widzieliśmy wcześniej, sprawdzanie wartości łańcucha zerowego jest przydatne przy podejmowaniu decyzji, czy zmienna jest ustawiona:
test -z $1
[ -z $1 ]
Prawdziwy wynik dla tego zapytania oznacza, że parametry wejściowe zostały dostarczone do skryptu.
Testowanie liczb całkowitych
Oprócz testowania wartości łańcuchowych skryptów bash można testować liczby całkowite. Dla mnie osobiście swego czasu było to najmniej intuicyjna kwestia do opanowania albo jeszcze inaczej: w przypadku porównań liczbowych w powłoce bash trzeba porzucić dotychczasowe doświadczenia (no chyba, że wcześniej kodowało się w golang). Bierze się to z tego, że chociaż operator równości (=) kontroluje równość (identyczność) ciągów znaków, to w przypadku liczb się nie sprawdza. Dlatego w to miejsce trzeba używać odpowiednich opcji, które widać niżej:
-eq sprawdza czy pierwszy argument porównania jest równy drugiemu argumentowi
-ne sprawdza czy pierwszy argument porównania jest nierówny drugiemu argumentowi
-lt sprawdza czy pierwszy argument porównania jest mniejszy niż drugi argument
-gt sprawdza czy pierwszy argument porównania jest większy niż drugi argument
-le sprawdza czy pierwszy argument porównania jest mniejszy lub równy drugiemu argumentowi
-ge sprawdza czy pierwszy argument porównania jest większy lub równy drugiemu argumentowi
Tworzenie instrukcji warunkowych za pomocą if
Powłoka Bourne’a pozwala na konstruowanie wyrażeń warunkowych, takich jak instrukcje if-then-else. Mimo że zajmie to więcej niż jedną linię w skrypcie, za pomocą if możemy osiągnąć więcej i uczynić skrypt bardziej czytelnym.
if warunek; następnie
instrukcja
elif warunek; then
instrukcja
else
instrukcja
fi
Jest to na tyle typowa konstrukcja, że nie ma specjalnie sensu poświęcać jej więcej miejsca w niniejszym wpisie.
Używanie instrukcji case
Słowo kluczowe case dotyczy kolejnej instrukcji warunkowej, która wyjątkowo dobrze sprawdza się przy porównywaniu ciągów znaków. Instrukcja case nie wywołuje żadnych poleceń, a co za tym idzie, nie wykorzystuje kodów wyjścia, ale może jednak sprawnie za jej pomocą spróbować dopasowywać wzorzec.
Podstawowy układ case na przykładzie sprawdzenia wartości przekazanej w pierwszym argumencie:
#!/bin/sh
case $1 in
Rafał)
echo “Witam szanownego Pana!”.
;;
boro|borciugner)
echo “Siema Mistrzu!”.
;;
*)
echo “A ty kto?”
;;
esac
Powłoka wykona powyższą instrukcję:
1. Wartość zmiennej $1 porównywana jest z każdą wartością testową oznaczoną znakiem nawiasu zamykającego “)”.
2. Jeżeli dana wartość testowa jest równa wartości zmiennej $1, powłoka wykonuje znajdujące się poniżej polecenia aż do napotkania wiersza zawierającego dwa znaki średnika “;;”.
3. Instrukcja warunkowa kończy się słowem kluczowym esac (odwrotność słowa “case”).
Każda z wartości testowych może składać się z jednego ciągu znaków lub kilku rozdzielonych znakiem “|” (zapis boro|borciugner zwróci wartość prawdy, jeżeli w zmiennej $1 znajduje się słowo “boro” lub “borciugner”), ale możliwe jest też zastosowanie znaków nazw wieloznacznych, takich jak gwiazdka lub znak zapytania. Jeżeli chcemy przygotować też domyślny warunek, do którego pasują wszystkie możliwe wartości oprócz wcześniej wymienionych w instrukcji case, wystarczy wpisać wartość testową składającą się z samego znaku gwiazdki, tak jak zrobiono to w poprzednim przykładzie.
***
W ramach niniejszego wprowadzenia do skryptowania w powłoce bash pozostały do omówienia jeszcze dwa tematy (no może 3, o ile zdecyduje się poświęcić osobny wpis tablicom). Jedno i drugie zagadnienie wydają się prostsze (czytaj: wymagają mniej pisaniny) niż instrukcje warunkowe czy zmienne. Tym niemniej do połowy lutego będę wyjątkowy zajęty innymi sprawami, więc niewykluczone, że dopiero w okolicach marca zakończymy tę serię.