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ądź

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ądź 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.