W wykładzie 4 przedstawimy zaawansowane aspekty programowania skryptów interpretera poleceń, takie jak pętle, podrzędne interpretery, podprogramy oraz elementy losowości.
Dotychczas nie wiemy jednak jak zapisać warunek1
,
warunek2
, ..., warunek_else
z instrukcji warunkowej.
Otóż te warunki są po prostu wywołaniami programów. Jeśli taki program zakończy się
powodzeniem (kod powrotu 0
), to warunek jest prawdziwy. Jeśli program
zakończy się błędem (kod powrotu jest niezerowy), to warunek jest fałszywy. To wyjaśnia,
dlaczego za każdym warunkiem występuje średnik. Wskazuje on, gdzie kończą się argumenty
programu-warunku. Bez tego średnika słowo kluczowe then
zostałoby
potraktowane jako argument programu warunku.
Do obliczania wartości wyrażeń logicznych najczęściej używa się programu
test
. Jego idea jest podobna do konstrukcji programu expr
służącego do obliczania wartości wyrażeń arytmetycznych. Argumenty
programu test
są wyrażeniem logicznym, którego wartość je obliczana.
Jeśli jest ono prawdziwe, program test
kończy się kodem powrotu 0
. Jeśli
jest ono fałszywe, program test
kończy się kodem powrotu 1
. Oto przykład:
bash$ if test 1 == 1; then echo PRAWDA; else echo FAŁSZ; fi PRAWDA bash$ i=9 bash$ if test $[3 * 3] == $i; then echo PRAWDA; fi PRAWDA bash$: if test $i == 4; then echo PRAWDA; fi bash$ _
Zamiast wywołania programu test
można używać nawiasów kwadratowych
[...]
. Jest to po prostu skrótowy sposób wywołania programu
test
, np.
bash$ if [ 1 == 1 ] ; then echo PRAWDA; else echo FAŁSZ; fi PRAWDA bash$ _
Napisać skrypt comp
, który ma dwa argumenty i wypisuje
-1
, gdy pierwszy argument jest mniejszy niż drugi, 0
gdy są równe a -1
gdy pierwszy argument jest większy niż drugi.
Skrypt ma sprawdzić, czy ma dobrą liczbę argumentów.
Skrypt będzie wywoływany następująco:
comp arg1 arg2Zamiast operatorów
<
, >
, !=
w
argumentach programu test
należy użyć odpowiednio
-lt
, -gt
, -ne
.
Rozwiązanie.
W punkcie Przekierowanie wejścia-wyjścia poznaliśmy
znaczenie symboli <
, >
i >>
.
Prawdopodobnie zastanowiło cię, dlaczego nie wymieniono
<<
. Otóż taka para znaków również ma specjalne znaczenie w
interpreterze poleceń, jednak jest ono diametralnie odmienne od znaczenia
tych trzech wymienionych na początku symboli. Ciąg znaków <<
służy do umieszczania w skrypcie danych, które będą podawane na standardowe
wejście wykonywanych programów. Ten ciąg znaków ma praktyczne zastosowanie jedynie w
skryptach.
Gdy chcemy, aby fragment skryptu stanowił dane wejściowe dla jakiegoś programu,
umieszczamy na końcu jego wywołania <<
a potem dowolne słowo.
W kolejnych liniach wpisujemy to, co ma być podane temu programowi na
standardowe wejście. Linia złożona tylko ze słowa podanego za
<<
kończy te dane wejściowe. Rozważmy na przykład skrypt:
cat > plik2.txt << KONIEC ala ma kota KONIECTaki skrypt powoduje stworzenie pliku o nazwie
plik2.txt
, który
składa się z trzech linii ze słowami odpowiednio ala
,
ma
i kota
. W tym pliku nie ma słowa KONIEC
.
W skrypcie to słowo było znacznikiem końca danych wejściowych.
Napisać skrypt wywoływany w następujący sposób:
wyslij plik adresat1 adresat2 ...Ma on spowodować wysłanie listu o treści zadanej w 'pliku' do 'adresatów'. Na końcu listu ma znaleźć się podpis i data nadania listu. Do wysyłania listów elektronicznych służy program
mail
, którego argumentami są adresy
odbiorców; treść listu jest czytana ze standardowego wejścia. Do wygenerowania
podpisu skorzystaj z programu whoami
, który wypisuje identyfikator
bieżącego użytkownika.
Rozwiązanie.
W języku skryptowym interpretera poleceń zdefiniowano również pętle
while
i for
. Oto składnia pętli while
:
while warunek; do instrukcje doneJeśli
warunek
jest prawdziwy,
to wewnętrzne instrukcje
są wykonywane. Następnie ponownie sprawdza się prawdziwość warunku
i ewentualnie znów wykonuje instrukcje
. To postępowanie jest
kontynuowane do chwili, w której warunek
stanie się fałszywy, np.
bash$ i=4 bash$ while [ $i -gt 0 ] ; do echo $i; i=$[i - 1]; done 4 3 2 1 bash$ _
Pętla for
ma bardzo podobną składnię:
for zmienna in lista_słów; do instrukcje doneNa początku wylicza się wyrażenie
lista_słów
(jeśli jest to na przykład
gwiazdka, to jej wyliczenie polega znalezieniu nazw wszystkich plików w bieżącym
katalogu). Potem wykonuje się wewnętrzne instrukcje tyle razy, ile było słów na liście,
przy czym za każdym wykonaniem na zmienną
przypisuje się kolejne
słowo z listy, np.
bash$ ls *.txt *.doc szkic.txt wyklad.doc cwicz.doc bash$ for x in *.txt *.doc ; do echo Plik $x to dokument. ; done Plik szkic.txt to dokument. Plik wyklad.doc to dokument. Plik cwicz.doc to dokument. bash$ _
Pętla for
ma też specjalną, ale bardzo często wykorzystywaną
postać, z której można korzystać w skryptach:
for zmienna; do instrukcje doneW takiej pętli
zmienna
przebiega listę argumentów skryptu. Przyjmuje
więc kolejno wartości $1
, $2
itd.
Istnieje jeszcze pętla until
, która wykonuje instrukcje
do chwili, w której warunek stanie się prawdziwy (czyli odwrotnie niż while).
a=0 until [ $a -eq ${#tab[@]} ] do echo tab[$a] = "${tab[$[a++]]}" donePowyższy skrypt wypisze wszystkie elementy tablicy tab. Wyrażenie
a++
, podobnie jak w C i C++, zwraca aktualną wartość
zmiennej a
, po czym zwiększa ją o 1.
Tego rodzaju wyrażenia często stosuje się przy różnego rodzaju iteracjach.
who
.
Do uśpienia skryptu na n
sekund służy polecenie
sleep n
. Do wyszukiwania wierszy, które zawierają podane
słowo służy program grep
. Zajrzyj do systemowego podręcznika
użytkownika, żeby poznać działanie grep
(wydaj polecenie
man grep
).
Rozwiązanie.
Instrukcja wyboru (case
) umożliwia dopasowywanie wartości wyrażenia
do kolejnych wzorców i wykonanie instrukcji, które są skojarzone z pierwszym
wzorcem, do którego pasuje wyrażenie. Oto składnia:
case wyrażenie in wzorzec1) instrukcje1 ;; wzorzec2) instrukcje2 ;; ... esacWzorce w
case
tworzy się tak, jak wzorce nazw plików, np. do t*y
pasują wszystkie słowa zaczynające się na literę t
a kończące się na
literę y
np. (ty
, tygrysy
, trytytytyty
etc.).
Na początku dopasowuje się wyrażenie
do wzorca1
. Jeśli
pasuje, to wykonywane są instruckje1
, po czym sterowanie przechodzi
za słowo kluczowe esac
. Jeśli nie
pasuje, to dopasowuje się je do wzorca2
i ewentualnie wykonuje instrukcje2
itd.
Widać więc, że bardziej ogólne wzorce należy umieszczać za bardziej szczegółowymi.
Co więcej, najbardziej ogólny wzorzec (gwiazdka) umieszczony na końcu instrukcji
odpowiada gałęzi else
polecenia if
.
Zauważmy, że umieszczanie jakiegokolwiek wzorca za wzorcem-gwiazką nie ma sensu, bo
wszystko pasuje do gwiazdki.
Wiedząc, że wzorzec ?
odpowiada jednemu dowolnemu znakowi (*
pasuje do dowolnego, także pustego, ciągu znaków), rozważmy następujący przykładowy
skrypt:
case $1 in ?) echo $1 ma jeden znak ;; ??) echo $1 ma dwa znaki ;; ???) echo $1 ma trzy znaki ;; *) echo $1 ma więcej niż trzy znaki ;; esacWypisuje on komunikat o ilości znaków w pierwszym argumencie skryptu. Jeśli wzorzec
*
znalazłby się na pierwszej pozycji, to ten skrypt zawsze wypisywałby
komunikat ma więcej niż trzy znaki
.
Napisać skrypt, który w zależności od wartości swojego argumentu wypisuje
bieżącą datę, dzień tygodnia i nazwę miesiąca po polsku
(gdy pierwszy argument to p
)
albo po angielsku (gdy pierwszy argument to a
).
Rozwiązanie.
Umieszczenie zestawu instrukcji w zwykłych nawiasach powoduje uruchomienie nowego interpretera poleceń i wykonanie w nim tego zestawu instrukcji. To pozwala na przykład przechwycić standardowe wejście całego zestawu, a nie pojedynczej instrukcji, np.
bash$ (for k in *.txt; do wc -c "$k"; done) | sort -nNa standardowe wejście programu
sort
podawany jest wynik instrukcji
for
. Wynikiem tej instrukcji jest lista rozmiarów plików z
rozszerzeniem txt
posortowana rosnąco (w porządku numerycznym).
W skrypcie interpretera można definiować podprogramy. Oto składnia definicji podprogramu:
nazwa () { instrukcje }Podprogram może korzystać z argumentów chociaż ich się nie deklaruje. Argumenty wywołania podprogramu są dostępne poprzez symbole
$1
, $2
, ..., $9
(tak samo odczytuje się argumenty samego skryptu). Analogicznie można też
korzystać z symboli $0
, $@
, $#
oraz polecenia
shift
. Podprogram wywołujemy tak, jakbyśmy wywoływali dowolny program albo skrypt.
Należy po prostu podać jego nazwę i argumenty. Oto skrypt wyliczający rekurencyjnie silnię ze
swego argumentu.
silnia () { # treść funkcji; tu $1 jest argumentem funkcji if [ $1 == 0 ] ; then wynik=1 else silnia $[$1 - 1] wynik=$[wynik * $1] fi } # wywołanie funkcji; tu $1 jest argumentem skryptu silnia $1 echo $wynikProcedury mogą być również rekurencyjne:
silnia () { # treść funkcji; tu $1 jest argumentem funkcji if [ $1 == 0 ] ; then echo 1 else echo $[$1 * `silnia $[$1 - 1]`] fi } # wywołanie funkcji; tu $1 jest argumentem skryptu silnia $1
Napisać skrypt, który wypisze drzewo katalogów i plików. Katalog ma być oznaczony plusem a zwykły plik minusem. Katalogi podrzędne mają być wcięte względem katalogów nadrzędnych. Oto przykładowy wynik działania tego skryptu:
+ bd + PL\SQL + 20002001 - index.htmll - procs.sql - triggers.sql + bydlo - dane.sql - index.htmll - procedura.sql - tabele.sql - wyzwalacz.sql + cursor - index.htmll - usundziury.sql + dump_table - dump.sql - dumpTable.sql - index.htmllPrzed przystąpieniem do rozwiązywania tego zadania warto dokładniej zapoznać się z programem
test
. Zajrzyj do systemowego podręcznika
użytkownika (wydaj polecenie man test
).
Rozwiązanie.
poczatek=(ładny "bardzo ładny" śliczny piękny) koniec=("wystrój łazienki" "widok z okna" "dywan" "widok z klatki schodowej" "dojazd" "trawnik") i=$[$RANDOM%${#poczatek[@]}] j=$[$RANDOM%${#koniec[@]}] echo "${poczatek[$i]} ${koniec[$j]}"
Powyższy program za każdym wykonaniem zwraca dość losowe zdanie zachwalające mieszkanie.
Za każdym razem, gdy następuje odwołanie do zmiennej $RANDOM, generowana jest całkowita liczba losowa z zakresu od 0 do 32767.
UWAGA: powyższa procedura nie daje rozkładu jednostajnego. Jeśli ${#poczatek[@]}=30000, to $RANDOM%${#poczatek[@]} wylosuje elementy od 0 do 2767 dwa wazy częściej niż pozostałe elementy.
Za pomocą konstrukcji interpretera można zrobić bardzo wiele. Z czasem, gdy oswoisz się z jego składnią i możliwościami, będziesz za jego pomocą wykonywał większość czynności w swoim systemie operacyjnym. Interfejs graficzny jest wygodny, ale nie jest tak elastyczny, jak interfejs linii poleceń. W graficznym interfejsie użytkownika poruszasz myszą i naciskasz jej guziki, a system na tej podstawie dedukuje, co chciałeś uzyskać, i robi to dla ciebie. Gdy wydajesz polecenia bezpośrednio, to ty jestem panem i władcą, a system robi dokładnie to, co zlecisz. Pisząc skrypty możesz łatwo zautomatyzować rutynowe czynności.
$1
, $2
, ..., $9
,
$@
i $#
.
~
(np. plik.txt~
), skopiuje (jeżeli takie są) do katalogu BACKUP
w bieżącym katalogu. Jeżeli katalog BACKUP
nie istnieje, skrypt powinien go założyć. Jeżeli jest już plik (lub inny nie-katalog) o nazwie BACKUP
, skrypt powinien zgłosić błąd.
n
-tą liczbę Fibbonacciego.