Uniwersytet w Białymstoku Wydział Matematyki i Informatyki Instytut Informatyki Materiały bazowe do zajęć z przedmiotu: Systemy operacyjne Laboratorium nr 08 Temat: Zmienne tablicowe, pętle for, while i until mgr Adam Bonda Białystok 2008 Cel laboratorium: Zapoznanie się z u\yciem pętli for, while, until w połączeniu z rozszerzoną arytmetyką parametrów ich wywołania i zastosowaniem zmiennych tablicowych. Podwójne nawiasy (( wyrażenie )) oraz podwójne nawiasy kwadratowe [[ wyrażenie ]] Podwójne nawiasy pozwalają na bezpośrednie wprowadzanie bardziej zaawansowanych formuł matematycznych na poziomie porównań komend strukturalnych (if-then) w odró\nieniu od polecenie test, które oferowało u\ycie tylko prostych operacji arytmetycznych. Składnia wyrażenie mo\e być zarówno matematycznym jak i porównawczym wyra\eniem. $ cat skrypt.sh #!/bin/bash var_1=5 if (( $var_1 ** 2 > 20 )) then (( var_2 = $var_1 ** 2 )) echo "Kwadratem liczby $var_1 jest $var_2" fi $ ./skrypt.sh Kwadratem liczby 5 jest 25 Dozwolone jest stosowanie następujących operatorów w większości znanych ju\ z poprzednich zajęć: i++, i--, ++i, --i, !, ~, **, <<, >>, &, |, &&, ||. Podwójne nawiasy kwadratowe pozwalają na u\ycie zaawansowanych porównań ciągów znaków. Porównywanie stringów zapisanych, jako wyra\enia otoczone tymi symbolami odbywa się podobnie jak w przypadku polecenia test. Symbol podwójnego nawiasu kwadratowego zapewnia jednak skorzystanie z dodatkowej opcji, której polecenie test nie posiada, porównywania wzorców. W porównywanym wzorcu mo\liwe jest zdefiniowanie wyra\enia regularnego, które porównywane jest z ciągiem znakowym. $ cat skrypt.sh #!/bin/bash if [[ $SHELL == xt* ]] then echo "Używasz terminala $SHELL" else echo "Hmm, a mówiłem, żeby nie instalować nowych \"wynalazków\"" echo "ale z drugiej strony, może ten terminal zjada mniej zasobów?" fi Zmienne tablicowe Powłoka bash umo\liwia korzystanie z jednowymiarowych tablic. Nazywane są one zmiennymi tablicowymi, poniewa\ mogą przechowywać więcej ni\ jedną wartość. Ka\da wartość indeksowana jest liczbą całkowitą, a indeks elementu tablicy umieszcza się w nawiasach kwadratowych, jak podano w przykładzie ni\ej: TABLICA1[0]="wartość zerowego" TABLICA1[1]=drugi TABLICA1[2]=3 TABLICA1[4]=iv Mo\liwa jest inicjacja wielu elementów tablicy naraz: TABLICA2=( "wartość zerowego" pierwszego [9]=numer iv ) Elementy tablicy rozdziela się spacjami i wylicza poczynając od elementu o indeksie 0. Jak widać powy\szym przykładzie mo\na tworzyć elementy o konkretnym indeksie, poprzez zadeklarowanie jego numeru w nawiasach klamrowych. Odwołanie się do wartości elementu zmiennej tablicowej mo\liwe jest poprzez zastosowanie wyra\enia: ${nazwa_zmiennej_tablicowej[indeks]} $ echo ${TABLICA2[0]} wartość zerowego Wyświetlony został element tablicy o indeksie zero $ echo ${TABLICA2[3]} Element o indeksie 3 nie istnieje, zwrócona więc została wartość pusta. $ echo ${TABLICA2[9]} numer Element o indeksie 9, przechowuje wartość numer . $ echo ${TABLICA2[10]} iv Jeśli w tablicy zadeklarowano wartość nie będącą kontynuacją poprzednich indeksów to tworzone dalej elementy (bez podania numeru) indeksowane są kolejnymi liczbami całkowitymi. W tym przypadku nie podanie indeksu wartości iv spowodowało automatyczne przydzielenie jej wartości (10) wy\szej o jeden ni\ indeks ją poprzedzający (9). U\ycie zmiennych specjalnych, jako indeksów jest równie\ dozwolone np.: ${zmienna[*]}, "${zmienna[*]}", ${zmienna[@]}. $ echo ${TABLICA2[*]} wartość zerowego pierwszego numer iv Wyświetlone zostały wszystkie elementy tablicy $ echo "${TABLICA2[*]}" wartość zerowego|pierwszego|numer|iv Wyświetlone zostały wszystkie elementy tablicy, a podwójne cytowania spowodowało u\ycie separatora zdefiniowanego zmienną systemową IFS (ang. internal field separator). Domyślnie powłoka bash, jako separatorów u\ywa: spacji, tabulatora, znaku nowej linii. Podobnie jak w przypadku zmiennych, zmienne tablicowe i ich elementy usuwa się poleceniem unset. unset TABLICA2[9] $ echo ${TABLICA2[*]} wartość zerowego pierwszego iv Usunięto element o indeksie 9. $ echo ${TABLICA2[9]} $ echo ${TABLICA2[10]} iv Wartość elementu indeksowanego liczbą 9 jest pusta. Nie nastąpiła jednak reindeksacja elementów i następny, w stosunku do usuniętego elementu, posiada w dalszym ciągu indeks o numerze 10. unset TABLICA2 $ echo ${TABLICA2[*]} Usunięta została zmienna tablicowa i wszystkie jej elementy. Instrukcja wyboru case Działanie komendy case jest podobne do konstrukcji warunkowej if-then. Jak sama nazwa wskazuje dotyczy ona wyborów, a więc określenia, który fragment kodu zostanie wykonany na zasadzie sprawdzania przynale\ności testowanej wartości do z góry określonego zbioru. Ogólna postać tej konstrukcji przedstawia się następująco: case zmienna in szablon1 | szablon2) komenda1;; szablon3) komenda2 komenda3 komenda3 ;; *) domyślne_komendy;; esac Po komendzie case, podawana jest wartość, która ma być oceniana. Mo\e to być zmienna, lub zdefiniowane słowo. Z oczywistych względów wstawia się tam zazwyczaj nazwę zmiennej zwiększa automatyczność konstrukcji. Je\eli w słowie występują spacje, to całość nale\y otoczyć znakami podwójnego cytowania. Kolejnymi elementami case są: słowo kluczowe in i lista opcji do wyboru. Ka\dą z opcji umieszcza się w nowej linii i kończy zamykającym nawiasem. Je\eli shell ustali zgodność zmiennej z którymś z szablonów to wykonana zostanie komenda, bądz lista wprowadzonych komend umieszczonych za danym ciągiem szablonów, po czym obsługa case jest kończona. W szablonach dozwolone jest stosowanie wyra\eń regularnych zawierających metaznaki: * (dowolny napis) ? (dowolny znak) [...] (określony zbiór znaków). Konstrukcja case nie sprawdza, czy wszystkich szablonów pasujących do testowanego słowa/zmiennej. Jeśli shell natrafi na pierwszy zgodny ze zmienną szablon to lista komend po nim występujących jest wykonywana. case "$1" in *) echo "zgodność 1";; *) echo "zgodność 2";; esac W powy\szym przykładzie zostanie więc wyświetlony tylko napis zgodność 1 . case "$1" in start|uruchom) echo "$0 został wywołany z pierwszym argumentem:" echo "\"start\" lub \"uruchom\"" stop|zatrzymaj) echo "$0 został wywołany z pierwszym argumentem:" echo "\"stop\" lub \"zatrzymaj\"" *) echo "$0 został wywołany z pierwszym argumentem" echo "nierozpoznawalnym przez składnię skryptu." esac Mo\liwe jest poddawanie kilku szablonów alternatywnych (poprzez u\ycie symbolu podziału potoku |), przy których zostanie wykonany ten sam ciąg instrukcji. Pętla for Powłoka bash oferuje dwa rodzaje pętli. Pierwszy z nich stanowi pętla for. Pobiera ona, jako parametr, listę napisów i dla ka\dego, jeden raz wykonuje zadeklarowaną składnię. Drugi rodzaj pętli stanowią polecenia while i until, które w odró\nieniu od for pobierają wyra\enie warunkowe, jako parametr i wykonują się tak długo, jak wyra\enie jest prawdziwe (w przypadku while), lub gdy jest fałszywe (w przypadku until). Bash poprzez strukturę for umo\liwia wykonywanie pętli iteracyjnej na zbiorze wartości. Ka\da iteracja (powtórzenie) wykonuje tylko jeden raz określony zestaw instrukcji dla ka\dej wartości zbioru przekazanej w postaci parametru. Ogólna konstrukcja pętli for, którą obsługuje powłoka bash ma następującą postać: for var in lista_słów do zestaw_instrukcji done lista_słów przekazywana jest pętli w formie parametru po słowie kluczowym in, poprzedzonym nazwą zmiennej var. Podczas ka\dej iteracji, zmienna var przechowuje aktualną wartość z listy. Pierwsza iteracja wykorzystuje pierwszy element listy, kolejna drugi, trzeci i tak, i tak a\ do ostatniego. Ciąg instrukcji zawierający zmienną var, jaki ma zostać wykonany dla ka\dego elementu listy umieszcza się pomiędzy znacznikami do i done. for i in $* do echo wartość argumentu: $i done Wywołanie powy\szego skryptu z kilkoma parametrami zwróci wartości tych parametrów poprzedzone ciągiem tekstowym. for i in `ls *.sh` do echo $i done Jak widać na powy\szych przykładach, lista_słów mo\e być reprezentowaną zmienną przechowującą zbiór wartości, lub mo\e być po prostu listą elementów wprowadzonych przez u\ytkownika, bądz te\ wyprowadzonych za pomocą zewnętrznej komendy. Ka\dy element mo\e być cyklicznie wywoływany tylko raz, wraz z zestawem komend, a\ do momentu, kiedy lista_słów zostanie wyczerpana. Powłoka bash umo\liwia tak\e u\ywanie konstrukcji pętli for w formie znanej z języka C: for (( i=0; <= 5; i++ )) do echo $i done Konstrukcja while i until Inny rodzaj pętli stanowią komendy while i until. Blok poleceń objęty działaniem pętli while jest wykonywany w kolejnych cyklach pętli tak długo, jak długo spełniony jest warunek określony parametrem, występującym bezpośrednio po słowie while. Ogólna konstrukcja pętli while, którą obsługuje powłoka bash ma następującą postać: while komendy1 do komendy2 done Opisana konstrukcja powoduje wykonanie listy poleceń, określonym w powy\szym zapisie jako komendy1 i warunkowe wykonanie poleceń komendy2, objętych działaniem pętli, które zawarte są pomiędzy słowami kluczowymi do i done. Wykonywane są więc najpierw polecenia komendy1,a gdy zwrócenie wartości 0 (true) warunkuje to wywołanie instrukcji zawartych pomiędzy do i done. Pętla jest wykonywana do momentu otrzymywania wartości 0, w przeciwnym razie jest kończona. while [ $1 ] do echo kod wyjścia: $? argumenty: $* shift done Je\eli podano niepustą wartość parametrów, to konstrukcja while wykonuje test ilości argumentów, po czym wyświetla aktualną ich liczbę. Dopóki zwracany z testu kod wyjścia będzie 0 (true) pętla będzie się wykonywała. Zastosowanie wewnętrznego polecenia powłoki shift w bloku instrukcji uruchamianych po spełnieniu warunku powodującego przesuwanie parametrów w lewo i ich kasowanie zmniejszającego z ka\dym cyklem liczbę parametrów o jeden i obni\ającego numerację zmiennych, powoduje, \e pętla będzie wykonywała się do momentu anihilacji ostatniego argumentu, tj. wtedy, gdy test zwróci wartość ró\ną od zera (w tym wypadku 1). Połączenie polecenia test z konstrukcją while równoznaczne jest u\yciu konstrukcji if- then. Podczas uruchamiania procesów tworzone są tzw. pidfile, czyli pliki informujące o tym, \e dany proces jest uruchomiony. Zazwyczaj przechowują te\ one numer tego procesu. Po zakończeniu działania programu, jego pidfile jest automatycznie kasowany. Przy pomocy konstrukcji while mo\emy sprawdzać czy dane polecenie jeszcze działa i warunkować wykonywanie kolejnych opartych o wyniki działania poprzednich aplikacji: while [ -f ~/program1/pidfile ] do sleep 10 done Pętla sprawdza istnienie pliku, jeśli zwrócona jest wartość 0 (true) to czeka 10 sek. i ponownie dokonuje sprawdzenia. Konstrukcja kończy działania w przypadku skasowania pliku. Je\eli potrzebna jest odwrotne działająca pętla, tzn. je\eli komenda ma być wykonana do momentu otrzymania wartości kodu wyjścia ró\nego od 0 stosujemy konstrukcję until: until komendy1 do komendy2 done Dopóki kod wyjścia komendy1 będzie zwracał wartość wartość ró\ną od 0 blok poleceń komendy2, będzie wykonywany. Po osiągnięciu wartości 0, pętla zakończy swoje działanie. until who | grep student1 do sleep 20 done write student1 << MSG -- zajecia z 21.01.2012 odwolane -- MSG Dopóki u\ytkownik nie zaloguje się do systemu pętlą będzie zawieszać działania na 20 sek. po czym uruchomiać się ponownie. Kiedy polecenie zwróci wartość 0 (true) działanie pętli zostanie przerwane, a znalezionemu w systemie u\ytkownikowi przesłana zostanie (za pomocą programu write) wiadomość znajdująca się pomiędzy znacznikami << MSG i MSG. Zadanie 1 Napisać skrypt, który w zale\ności od tego, czy uruchomił go u\ytkownik student, czy root wyświetli powitalny napis. Dla u\ytkownika jas i marysia zwróci dwa ró\ne komunikaty, a dla pozostałych u\ytkowników wypisany będzie napis, \e nie są oni znani skryptowi. Przetestować działanie skryptu. Zadanie 2 Napisać skrypt, który przy u\yciu pętli for wyświetli listę 5 dowolnych imion z poprzedzającym ich stałym tekstem. Zadanie 3 Napisać skrypt, który ka\de słowo zdania potraktuje, jako osobny element i wypisze go na standardowym wyjściu: "Lubie "programować" w bash'u". Zadanie 4 Napisać skrypt o działaniu, jak z zadania 2, z tą ró\nicą, \eby czytanie listy było ze zmiennej. Zadanie 5 Napisać skrypt, który przy pomocy pętli będzie wyświetlał wszystkie dane z pliku "im_nazw", który trzeba uprzednio stworzyć i wprowadzić dowolne dane trzech osób (ka\da w nowej linii) wg wzoru: Imię Nazwisko Zadanie 6 Napisać skrypt który sprawdzi i wypisze, które z plików katalogu domowego są katalogami, a które zwykłymi plikami. Zastosować tylko jedną pętlę. Zadanie 7 Napisać skrypt, który przy u\yciu pętli for wypisze dziesięć kolejnych wartości. Zadanie 8 Napisać skrypt, w którym dwie zmienne będą zmieniać wartości przeciwbie\nie: pierwsza od A do B, a druga od B do A (A i B są cyframi). Zmiana w ka\dym cyklu dla obydwu zmiennych. Zadanie 9 Przepisać działanie skryptu z zadania 7 z u\yciem pętli while Zadanie 10 Napisać skrypt wykonujący to samo zadanie przy u\yciu konstrukcji for while [ $1 ] do echo kod wyjścia: $? argumenty: $* shift done Zadanie 11 Za pomocą konstrukcji until wyliczyć wszystkie wartości z określonym krokiem, od zadeklarowanej do zera. Zadanie 12 Napisać skrypt, który dla ka\dej z wartości od 5 do 0 wykona mno\enie ka\dej z tych wartości przez cyfry od 1 do 3. Wszystkie dane wyświetlić na ekranie i zapisać do pliku z poziomu skryptu. Zadanie 13 Napisać skrypt, który dla ka\dej linii pliku /etc/passwd wypisze wszystkie jej elementy . Przykładowy wynik działania skryptów:: student:x:1000:1004:student:/home/student:/bin/bash student x 1000 .. itd. Zadanie 14 Napisać skrypt, który dla zmiennej tablicowej (stworzyć zmienną składającą się z 7 elementów) wyświetli liczbę elementów przechowujących przez tablicę, a następnie indeksy i przyporządkowane im wartości elementów, oraz wszystkie elementy nieparzyste. Zadanie 15 Napisać skrypt, który wypisze nazwy wszystkich plików katalogu domowego małymi literami, a katalogów du\ymi.