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.