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\""
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ę.
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.