Programowanie w Bash
Spis treści |
Wstęp
Bash jest popularną powłoką oraz językiem skryptowym. Bash to język interpretowany, zatem jego uruchomienie nie wymaga kompilacji pliku źródłowego. Konieczny jest zatem interpreter skryptu (standardowo znajdujący się w /bin/bash). Skrypty powłoki, to nic innego, jak pliki zawierające polecenia danej powłoki. Komendy w skryptach, interpretowane są pojedynczo w kolejności, jaką ustalił programista.
Bash umożliwia pracę interaktywną, jak i wsadową. Związane jest z tym interpretowanie skryptu tak samo, jak by był wpisywany z konsoli, co w konsekwencji oznacza, że błąd któregoś polecenia (w odróżnieniu do języków kompilowanych), nie przerywa wykonywania reszty skryptu.
Znaki specjalne
Poniższe znaki są znakami specjalnymi w Bash:
- \
- #
- $
- |
- '
- "
- `
- !
- case
- do
- done
- elif
- else
- esac
- fi
- for
- function
- if
- in
- select
- then
- until
- while
- {
- }
- time
- [
- ]
Nie powinny być używane do tworzenia zmiennych, gdyż są traktowane specjalnie przez powłokę.
Aby anulować znaczenie niektórych znaków specjalnych (np. podczas cytowania), należy użyć znaku \.
Goodbye World
Aby stworzyć pierwszy skrypt, musimy najpierw utworzyć plik skryptplik.sh w którym będzie znajdował się kod a następnie nadać uprawnienia do wykonywania pliku. W systemie Linux rozszerzenie pliku nie ma znaczenia, ale przyjęło się używanie rozszerzenia sh dla skryptów Bash.
user:~ # touch skryptplik.sh user:~ # chmod +x skryptplik.sh
Po stworzeniu pliku i przydzieleniu uprawnień, musimy wyedytować go za pomocą ulubionego edytora i jako pierwszą linijkę, podać ścieżkę do interpretera powłoki.
#!/bin/bash
lub
#!/bin/sh
Standardowo znak # służy jako komentarz. Wyjątkiem jest pierwsza linia, zaczynająca się od #!. Linia ta jest szczególnie istotna, gdy skrypt będzie uruchamiany z innej powłoki niż Bash. Dzięki niej, zawsze zostanie wybrany poprawny interpreter.
Aby nasz skrypt "Pożegnał się za światem", użyjemy funkcji echo, która wypisuje podane jej argumenty na standardowe wyjście.
#!/bin/bash echo "Goodbye World!"
Teraz już tylko wystarczy uruchomić skrypt w podany poniżej sposób:
user:~ # ./skryptplik.sh
lub:
user:~ # sh skryptplik.sh
W obu sytuacjach zostanie wyświetlona informacja:
Goodbye World!
Zmienne
Wstęp
Zmienne w Bashu są słabo/dynamicznie typowane. Oznacza to, że przy tworzeniu zmiennych nie musimy deklarować ich typu (tak jak ma to miejsce w językach C/C++, Java, C#) a interpreter sam domyśla się jakiego typu jest zmienna i jak interpretować przeprowadzane na niej operacje. Zmienne tworzone przez programistę, nie muszą być wcześniej deklarowane.
Typy zmiennych
Programowe
Zmienne Programowe, tworzone są przez programistę. Nie mogą się one zaczynać od liczb oraz nie mogą być stosowane nazwy ze zbioru słów zastrzeżonych.
Wprowadzanie wartości do zmiennych, realizowane jest za pomocą znaku = (UWAGA! Po obu stronach tego znaku nie może być spacji!).
zmiennapierwsza="123Frytki" zmiennaDruga=3.14 zmienna_trzecia=zmiennapierwsza
Do zmiennych odwołujemy się za pomocą znaku $
tekst="123Fryytki" liczba=3.14 echo $tekst echo $liczba
Aby usunąć zmienną, poprzedzamy ją unset
Specjalne
Są to zmienne tworzone przez system. Najczęściej nie można ich edytować. Poniżej znajdują się przykłady:
- $0 - zmienna ta zawiera nazwę skryptu, w którym jest wywoływana.
****** SKRYPT ****** #!/bin/bash echo $0
****** KONSOLA ****** user:~ # ./skrypt ./skrypt user:~ #
- $1,$2,$3...$9 - zmienne te zawierają argumenty wywołania skryptu
****** SKRYPT ****** #!/bin/bash echo $1 echo $2 echo $3 echo $5
****** KONSOLA ****** user:~ # ./skrypt arg1 123 test arg1 123 test user:~ #
- ${10}, ${11}, ${12} - argumenty wywołania, powyżej $9. Muszą one być zapisane w nawiasach klamrowych, bo Bash po znaku $ traktuje pierwszą liczbę, jako numer argumentu wywołania skryptu.
****** BŁĘDNY SKRYPT ****** #!/bin/bash echo $10
****** KONSOLA ****** user:~ # ./skrypt arg1 123 test arg10 user:~ #
****** POPRAWNY SKRYPT ******
#!/bin/bash
echo ${10}
****** KONSOLA ****** user:~ # ./skrypt a b c d e f g h i j j user:~ #
- $# - ilość argumentów z którymi skrypt został wywołany
****** SKRYPT ****** #!/bin/bash echo $#
****** KONSOLA ****** user:~ # ./skrypt a b c d e f g h i j 10 user:~ #
- $@ - wszystkie argumenty wywołania
****** SKRYPT ****** #!/bin/bash echo $@
****** KONSOLA ****** user:~ # ./skrypt a b c d e f g h i j a b c d e f g h i j user:~ #
- $? - kod powrotu polecenia, które zostało wykonane jako ostatnie (zwany też kodem błędu, brak błędu - 0)
****** SKRYPT ****** #!/bin/bash echo $?
****** KONSOLA ****** user:~ # ./skrypt 0 user:~ # ./skrypt mkdir: nie można utworzyć katalogu `test': Plik istnieje 1 user:~ #
- $$ - PID uruchomionego skryptu
****** SKRYPT ****** #!/bin/bash echo $$
****** KONSOLA ****** user:~ # ./skrypt 19255 user:~ #
Środowiskowe
Wszystkie zmienne programowe, których używamy w bashu, są zmiennymi lokalnymi. Jeśli programista wywoła podshell, nie będzie się mógł w nim odwołać do ich wartości.
****** SKRYPT RODZIC ****** #!/bin/bash zmienna=123 echo $zmienna ./dziecko
****** SKRYPT DZIECKO ****** echo $zmienna
****** KONSOLA ****** user:~ # ./rodzic 123 user:~ #
Jeśli jednak przed zmienną użyjemy słowa kluczowego export, zmienna stanie się zmienną globalną i będzie widoczna w podshellach.
****** SKRYPT RODZIC ****** #!/bin/bash export zmienna=123 echo $zmienna ./dziecko
****** SKRYPT DZIECKO ****** echo $zmienna
****** KONSOLA ****** user:~ # ./rodzic 123 123 user:~ #
Aby zobaczyć wszystkie zmienne środowiskowe, należy użyć polecenia declare -x. Do usuwania zmiennej środowiskowej służy polecenie export -n zmienna.
****** SKRYPT DZIECKO ****** #!/bin/bash export zmienna=123 export innazmienna="ala ma kota" declare -x export -n zmienna declare -x
Nie prezentuję wyniku skryptu ze względu na ogrąmną ilość zmiennych środowiskowych, domyślnie zadeklarowanych w powłoce Bash.
Tablice
W Bashu istnieją tylko tablice jedowymiarowe. Deklaruje się je na kilka różnych sposobów:
- rozdzielanie spacjami - zmienne, które mają się znaleźć w tablicy rozdzielamy spacjami, przez co trafiają kolejno do indeksów tablicy
tablica=(123 "ala ma kota" "12345")
- odwoływanie się do konkretnych indeksów tablicy - po nazwie tablicy nawiasach kwadratowy umieszczamy indeks do którego chcemy się odwołać (tablice w Bash numerowane są od 0)
tablica[1]="nic"
Do elementów tablicy odwołujemy się stosując nawiasy klamrowe po znaku $, gdyż jeśli byśmy je pominęli, Bash przypisał by znak $ tylko to nazwy tablicy i potraktował by ją jak zwykłą zmienną. Dzięki znakowi * lub @ możemy odwołać się do wszystkich elementów tablicy.
****** SKRYPT ******
#!/bin/bash
tablica=(123 "ala ma kota" "12345")
echo ${tablica[@]}
tablica[1]="nic"
echo ${tablica[*]}
echo ${tablica[2]}
****** KONSOLA ****** user:~ # ./skrypt 123 ala ma kota 12345 123 nic 12345 12345 user:~ #
Aby zliczyć ilość znaków w danym indeksie (w nwiasach klamrowych podajemy numer indeksu) lub ilość elementów tablicy (w nawiasach klamrowych wpisujemy znak * lub @), dodajemy znak # przed nazwą tablicy.
****** SKRYPT ******
#!/bin/bash
tablica=(123 "ala ma kota" "12345")
echo ${#tablica[@]}
echo ${#tablica[2]}
****** KONSOLA ****** user:~ # ./skrypt 3 5 user:~ #
Dzięki poznanym operacjom na tablicach jesteśmy w stanie łatwo dodać kolejny element na koniec tablicy.
****** SKRYPT ******
#!/bin/bash
tablica=(123 "ala ma kota" "12345")
tablica[${#tablica[@]}]="tralala"
echo ${tablica[@]}
****** KONSOLA ****** user:~ # ./skrypt 123 ala ma kota 12345 tralala user:~ #
Elementy tablicy usuwamy w ten sam sosób co zwykłe zmienne - komenda unset. Elementy tablicy automatycznie "zapełniają powstałą lukę".
****** SKRYPT ******
#!/bin/bash
tablica=(123 "ala ma kota" "12345")
unset tablica[1]
echo ${tablica[@]}
****** KONSOLA ****** user:~ # ./skrypt 123 12345 user:~ #
Zasięg zmiennych
Usuwanie zmiennych
Do usuwania zmiennych służy polecenie unset. Oto prosty skrypt, obrazujący działanie tego polecenia:
****** SKRYPT ****** #!/bin/bash zmienna=123 echo $zmienna unset zmienna echo $zmienna
****** KONSOLA ****** user:~ # ./skrypt 123 user:~ #
Przekierowania
Powłoka przed wykonaniem polecenia sprawdza, czy nie powiązać określonych deskryptorów z innymi deskryptorami lub z plikami. Są to tzw. przekierowania
[n] < plik
Przekierowanie wejścia [n] < plik powoduje otwarcie pliku do czytania i powiązanie deskryptora o numerze n z zawartością pliku. Gdy pominiemy numer deskryptora powłoka przekieruje standardowe wejście (deskryptor nr 0) Przykład:
$ mail login@adres.domena < wiadomosc.dat
[n] > plik
Przekierowanie wyjścia [n] > plik powoduje otwarcie pliku do pisania i powiązanie go z deskryptorem o numerze n. Jeśli plik nie istnieje zostanie stworzony, jeśli istnieje zostanie skrócony do zera. Jeśli pominiemy numer deskryptora powłoka przekieruje standardowe wejście (deskryptor nr 1).
[n] » plik
Przekierowanie wyjścia z dopisywaniem [n] » plik powoduje otwarcie pliku do dopisywania (jeśli nie istnieje zostanie stworzony) i powiązanie go z deskryptorem o numerze n. Gdy pominiemy numer deskryptora powłoka przekieruje standardowe wejście.
&> plik
Przekierowanie wyjścia i standardowego wyjścia &> plik powoduje otwarcie pliku do pisania i powiązanie go ze standardowym wyjściem i standardowym wyjściem błędów.
[n] &< [m]
Wywołanie [n] &< [m] powoduje, że czytając z deskryptora [n] dostajemy strumień z [m].
[n] >& [m]
Podobnie wywołując [n] >& [m] pisząc do deskryptora [n] piszemy do deskryptora [m].
Potoki
Potoki (pipe) służą do łączenia poleceń. Dzięki temu przyspieszymy naszą pracę oraz zwiększymy możliwości prostych poleceń. Potoki wykorzystywane są najczęściej w procesach przetwarzania danych np. gdy chcemy wyszukać jakieś konkretne pliki bądź przeglądać logi systemowe, uruchomione procesy itd. Zasada działania potoków polega na tym, że wyjście poprzedniego polecenia stanowi wejście następnego.
Ogólna składnia:
polecenie1 [ | polecenie2] [ | polecenie3] ...
Jak widać, potok jest ciągiem poleceń porozdzielanych znakiem "|", przy czym standardowe wyjście polecenie poprzedzającego jest standardowym wejściem polecenia następnego.
Przykłady
$ find /home/user/mp3 -name *.mp3 | sort > moje_empetrzy
Potok ten wyszuka nam wszystkie plik mp3 w naszym /home, posortuje je a następnie zapisze wynik do pliku moje_empetrzy.
$ cat /etc/passwd | sort | sed -e 's/root/rut/g'> /etc/passwd
Powyższe wywołanie zamieni wszystkie wystąpienia słowa 'root' na 'rut' w pliku /etc/passwd
(musimy mieć prawo pisania do tego pliku). Pierwsze polecenie wyświetla plik /etc/passwd, drugie sortuje te dane, co jest następnie przetwarzane przez edytor strumieniowy 'sed' i przekierowaniem zapisywane do pliku.
Cytowanie
Cudzysłowy
Znaki umieszczone pomiędzy cudzysłowami, traktowane są przez interpreter Bash jako tekst. Interpretowane są jednak w nich znaki specjalne: $,`,\.
**** SKRYPT **** #!/bin/bash liczba=123 tekst="123Frytki" echo $tekst "ala ma $liczba kota" nic
**** KONSOLA **** 123Frytkiala ma 123 kota nic
Warto zauważyć różnicę pomiędzy tekstem wypisywanych przez echo w cudzysłowach oraz przy ich braku - tekst w cudzysłowach interpretuje spacje jako tekst.
Apostrofy
Zawartość apostrofów także traktowana jest jak tekst. Różnią się one jednak od cudzysłowów brakiem interpretacji znaków specjalnych.
****** SKRYPT ****** #!/bin/bash echo "W cudzysłowach interpretowane są znaki specjalne takie, jak \$: $0" echo 'W apostrofach znaki specjalne, jak \$: $0, nie są interpretowane'
****** KONSOLA ****** user:~ # ./skrypt user:~ # W cudzysłowach interpretowane są znaki specjalne takie, jak $: ./skrypt user:~ # W apostrofach znaki specjalne, jak \$: $0, nie są interpretowane
Odwrotne apostrofy
Tekst zawarty pomiędzy odwrotnymi apostrofami traktowany jest jak polecenie Bash. Identyczny efekt jesteśmy w stanie uzyskać przez umieszczenie polecenia w $(polecenie)
****** SKRYPT ****** #!/bin/bash data=`date` katalog=$(pwd) echo $data $katalog
****** KONSOLA ****** user:~ # ./skrypt user:~ # /home/user śro, 16 cze 2010, 19:29:55 CEST
Polecenie date zwraca aktualną datę i czas pobraną z systemu a polecenie pwd zwraca katalog domowy użytkownika.
Warunki
Sprawdzanie warunków w Bash można realizować na trzy sposoby:
- Za pomocą słowa kluczowego test
test warunek1 operator_logiczny warunek2 operator_logiczny warunek3...
- Używając nawiasów klamrowych - muszą być spacje pomiędzy nawiasami a operatorami (Tak naprawdę jest to ta sama komenda ;) )
[ warunek1 operator_logiczny warunek2 operator_logiczny warunek3... ]
- Za pomocą podwójnych nawiasów klamrowych (extended test command), które zostały wprowadzone w Bash 2.02 po to aby zbliżyć się do sposobu porównywania w innych językach. Jest to mniej restrykcyjna metoda porównywania i używa się jej np. przy wyrażeniach regularnych:
[[ wzor~=tekst ]]
Wartości logiczne zwracane przez sprawdzane warunki (w odróżnieniu do języków programowania, jak C/C++, Java...) zapisywane są do specjalnej zmiennej $?. Jeśli wartość logiczna jest prawdą, to do zmiennej tej wpisywane jest 0 a jeśli nie, to 1.
Opcje sprawdzanych warunków:
- porównywanie arytmetyczne
- -lt - mniejsze niż
- -gt - większe niż
- -le - mniejsze równe
- -ge - większe równe
- -eq - równe
- -ne - różne
- porównywanie napisów
- = - takie same
- != - różne
- < - "mniejsze alfabetycznie"
- > - "większe alfabetycznie"
- -n - nie pusty
- -z - pusty
- testowanie plików
- -e - istnieje
- -b - jest urządzeniem blokowym
- -c - jest urządzeniem znakowym
- -d - jest katalogiem
- -f - jest plikiem zwykłym
- -G - plik należy do grupy o numerze równym efektywnemu GID
- -L - jest dowiązaniem symbolicznym
- -p - jest nazwanym łączem
- -S - jest gniazdem
- -s - ma rozmiar większy niż zero
- -nt - data modyfikacji pierwszego pliku jest nowsza
- -ot - data modyfikacji drugiego pliku jest starsza
- -ef - pliki są wzajemnymi twardymi dowiązaniami
- sprawdzanie praw dostępu do pliku
- -g - ma ustawiony bit set-group-id
- -k - ma ustawiony bit sticky
- -r - może być czytany
- -u - ma ustawiony bit set-user-id
- -w - można do niego pisać
- -x - może być wykonany
- -O - właścicielem jest użytkownik o numerze równym aktualnemu efektywnemu UID
- -G - należy do grupy o numerze równym efektywnemu GID
Warto zapamiętać o panującej zasadzie, że jednoliterowe opcje sprawdzania, przyjmują jeden argument a dwuargumentowe, dwa argumenty (dwa pliki). Wyjątkiem są operatory logiczne:
- -a - operator logiczny AND, zwraca wartość prawdziwą (0), gdy obie wartości są prawdziwe
- -o - operator logiczny OR, zwraca wartość prawdziwą, gdy przynajmniej jedna z wartości jest prawdziwa
Przykład:
****** SKRYPT ****** #!/bin/bash tekst1="abc" tekst2="abd" [ tekst1 < tekst2 ] echo 'tekst1 < tekst2:' $? [ tekst1 = tekst2 ] echo 'tekst1 = tekst2:' $? [ tekst1 > tekst2 ] echo 'tekst1 > tekst2:' $? [ -e ./skrypt ] echo '-e ./skrypt:' $? [ 23 -lt 24 ] echo '23 -lt 24:' $? [ -c ./skrypt ] echo '-c ./skrypt:' $? [ -d ./skrypt ] echo '-d ./skrypt:' $? [ -d ./skrypt -o -c ./skrypt ] echo '-d ./skrypt -o -c ./skrypt:' $? [ -d ./skrypt -a 23 -lt 24 ] echo '-d ./skrypt -a 23 -lt 24:' $?
****** KONSOLA ****** user:~ # ./skrypt tekst1 < tekst2: 1 tekst1 = tekst2: 1 tekst1 > tekst2: 0 -e ./skrypt: 0 23 -lt 24: 0 -c ./skrypt: 1 -d ./skrypt: 1 -d ./skrypt -o -c ./skrypt: 1 -d ./skrypt -a 23 -lt 24: 1
Instrukcje warunkowe
Instrukcje warunkowe służą do kontrolowania przepływu programu.
If
Instrukcja ta ma postać:
if warunek then instrukcje elif warunek then instrukcje else instrukcje fi
Instrukcje elseif oraz else są opcjonalne.
Po instrukcji if znajduje się warunek. Jeśli zostanie on spełniony, wykonywane są instrukcje po then.
Instrukcja elseif jest sprawdzana jeśli warunek w instrukcji if nie jest prawdziwy.
Jeśli żaden warunek w instrukcjach if oraz elseif nie będzie prawdziwy, zostanie wykonany kod, znajdujący się po instrukcji else.
****** SKRYPT ****** #!/bin/bash echo 'ile masz lat?' read lata if [ $lata -le 18 -a $lata -ge 1 ] then echo 'Nie gadam z Tobą!' elif [ $lata -ge 19 -a $lata -le 100 ] then echo 'No, to możemy pogadać' else echo 'Chyba mnie kłamiesz...' fi
****** KONSOLA ****** user:~ # ./skrypt ile masz lat? 11 Nie gadam z Tobą! user:~ # ./skrypt ile masz lat? 23 No, to możemy pogadać! user:~ # ./skrypt ile masz lat? 111 Chyba mnie kłamiesz... user:~ # ./skrypt
W przykładzie tym, została użyta funkcja read. Funkcja ta wczytuje wartość ze stanardowego wejścia do zmiennej, znajdującej się zaraz za nią.
Case
Case jest odpowiednikiem funkcji Switch z innych języków. Umożliwia łatwe dopasowywanie wartości do wzorca. Case da się symulować za pomocą instrukcji If, ale w niektórych przypadkach jest ona po prostu wygodniejsza. Instrukcja zaczyna się od case, następnie wymieniane są wzorce (zakończone nawiasem) z poleceniami (zakończone znakami ;;) a na końcu znajduje się odwrotność pierwszej komendy - esac.
case zmienna wzorzec1) polecenie1 ;; wzorzec2) polecenie2 ;; wzorzec3) polecenie3 ;; ... esac
***** SKRYPT ***** #!/bin/bash echo 'używasz powłoki: ' case $SHELL in '/bin/csh) echo 'csh' ;; '/bin/bash) echo 'bash' ;; '/bin/tcsh) echo 'tcsh' ;; '/bin/ksh) echo 'ksh' ;; esac
***** KONSOLA ***** user:~ # ./skrypt używasz powłoki: bash
Pętle
Cała siła programowania, to możliwość szybkiego powtarzania czynności, które programista raz zaprogramował. Wielokrotne powtarzanie tego samego kodu umożliwiają nam pętle.
While
Pętla while najpierw sprawdza warunek. Jeśli jest on prawdziwy, wtedy wykonuje treść pętli i ponownie wraca do warunku pętli. Jeśli warunek nie jest prawdziwy, interpreter opuszcza pętlę.
while warunek do polecenia done
***** SKRYPT ***** #!/bin/bash czykoniec="nie" while [ $czykoniec != "Koniec" ] do echo 'Jeśli wpiszesz "Koniec", to zakończę program' read czykoniec done echo 'Udało Ci się!'
***** KONSOLA ***** user:~ # ./skrypt Jeśli wpiszesz "Koniec", to zakończę program aaabbbccc Jeśli wpiszesz "Koniec", to zakończę program Koniec Udało Ci się!
Podając zły argument pętli jesteśmy w stanie bardzo łatwo zapętlić program, przez co program może zużywać dużą ilość cennych zasobów, blokować konsolę, pliki itp. Jeśli uruchamiamy skrypt ręcznie, możemy przerwać jego działanie naciskając w konsoli kombinację Ctrl+C. Jeśli uruchamiany jest w sposób automatyczny, musimy użyć komendy kill.
Until
Pętla Until działa na tej samej zasadzie, co pętla While, lecz sprawdzany warunek nie może być prawdziwy aby wykonała się zawartość pętli
until warunek do polecenia done
W przykładzie użyjemy polecenia sleep, które usypia skrypt na podaną liczbę sekind/minut/godzin/dni (sleep [ilość][s,m,h,d]).
***** SKRYPT ***** #!/bin/bash until [ -e test ] do echo "Utwórz plik test" sleep 5s done
***** KONSOLA1 ***** user:~ # ./skrypt Utwórz plik test Utwórz plik test ***** KONSOLA2 ***** user:~ # touch test ***** KONSOLA1 ***** Dziękuję za utworzenie pliku user:~ #
Powyższy skrypt sprawdza, czy został utworzony plik test i jeśli tak, to wychodzi z pętli. W przeciwnym przypadku wchoi do pętli, prosi o utworzenie pliku i dla oszczędności zasobów - zasypia na 5 sekund.
For
Pętla For iteruje po przekazanej jej liście, przekazując ją do podanej zmiennej.
for zmienna in lista do polecenia done
W przykładzie przejdziemy po liście plików kończących się na txt i przeniesiemy każdy z folderu pliki.
***** SKRYPT ***** #!/bin/bash mkdir pliki for x in *txt do mv $x "pliki/$x" done
Select
Pętla Select jest dość specyficzną pętlą. W wielu językach nie ma jej odpowiednika i musi być symulowana za pomocą innych pętli. Ogólna postać tej pętli wygląda następująco:
select zmienna in lista do polecenia done
Pętla ta tworzy autematycznie ponumerowane menu, z którego użytkownik powinien wybrać numer opcji. Pętla pyta się o umieszczone w liście opcje wyboru za każdym razem, chyba że napotka na polecenie break, które powoduje opuszczenie pętli. Po wybraniu numeru opcji, w zmiennej umieszczana jest wartość z listy, odpowiadająca numerowi z wyświetlanego menu.
Poniżej znajduje się prosty przykład użycia pętli Select:
***** SKRYPT *****
#/bin/bash
select x in 123 456 xyz koniec
do
echo "wybrałeś $x"
if [ $x = "koniec" ]
then
echo "Zatem Koniec!"
break
fi
done
W powyższym przykładzie, pętla będzie odpytywała użytkownika o wybór opcji, dopóki nie wybierze on wartości koniec
***** KONSOLA ***** user:~ #./skrypt 1) 123 2) 456 3) xyz 4) koniec #? 2 wybrałeś 2 #? 3 wybrałeś 3 #? 4 wybrałeś 4 Zatem Koniec! user:~ #
Do pętli Select doskonale pasuje instrukcja Case, dzięki aktórej możemy w łatwy sposób zareagować na wybór użytkownika:
#/bin.bash
echo 'podaj nazwę pliku'
read plik
touch $plik
echo "wybierz edytor"
select x in 'vi' 'vim' 'nano' 'pico'
do
case $x in
'vi') vi $plik ;;
'vim') vim $plik ;;
'nano') nano $plik ;;
'pico') pico $plik ;;
esac
break
done
Powyższy skrypt utworzy plik o nazwie, którą mu podaliśmy a następnie otworzy go za pomocą wybranego edytora. Oczywiście edyrot, który wybierzemy - musi być zainstalowany.
Funkcje
Choć pętle umożliwiają programiście wielokrotne wykonywanie tego samego fragmentu kodu, to są one w stanie robić to tylko lokalnie. Po wyjściu z pętli jesteśmy w stanie do niej wrócić tylko wtedy, gdy zawiera się ona w innej pętli a nie zawsze jest to sytuacja pożądana. Innym rozwiązaniem powtarzania raz napisanego kodu są funkcje. Funkcje możemy traktować, jak niewielkie podprogramy, których możemy wielokrotnie używać.
Deklaracja
Każda funkcja w bash musi zaczynać się od słowa kluczowego function, następnie jest nazwa funkcji i ciało funcji.
function nazwa
{
cialo_funkcji
}
Dopuszczalna jest też inna forma deklarowania funkcji:
nazwa()
{
cialo_funkcji
}
Aby wywołać funkcję w skrypcie, wystarczy wpisać jej nazwę.
***** SKRYPT *****
#/bin/bash
function prosta_funkcja
{
echo "Teraz piszę do Ciebie z funkcji"
}
echo "Piszę do Ciebie przed wywołaniem funkcji"
prosta_funkcja
echo "A teraz piszę po wywołaniu funcji. Może by tak jeszcze jedna funkcja?"
prosta_funkcja2()
{
echo "Oto jestem!"
}
echo "Wystarczy!"
***** KONSOLA ***** user:~ #./test Piszę do Ciebie przed wywołaniem funkcji Teraz piszę do Ciebie z funkcji A teraz piszę po wywołaniu funcji. Może by tak jeszcze jedna funkcja? Oto jestem! Wystarczy!
Zwracanie wartości
W przeciwieństwie do języków programowania C-podobnych, w bash-u funcje nie muszą być deklarowane na początku programu oraz nie trzeba określać typu zwracanych przez nie wartości.
Aby funkcja zwróciła jakąś wartość, używamy słowa kluczowego return. Jest ono jednak w stanie zwrócić jedynie wartości liczbowe z zakresu 0-255. Spowodowane jest to tym, że funkcje w bash-u mogą zwracać tylko wartości odpowiadające wartościom powrotu poleceń. Domyślnie funkcje zwracają wartość ostatnio wykonanego polecenia (0-powodzenie, 1-błąd). Zwracaną przez funkcję liczbę, odczytujemy tak samo, jak każdą inną wartość zwracaną przez polecenia - $?
***** SKRYPT *****
#/bin/bash
function moja_funkcja
{
return 123
}
moja_funkcja
echo $?
***** KONSOLA ***** user:~ #./skrypt 123 user:~ #
Sposobem na zwrócenie wartości innej niż liczbowa, jest użycie zmiennych globalnych. W bash-u domyślnie wszystkie zmienne są zmiennymi globalnymi, zatem zmianna zadeklarowana w funkcji, jest widoczna też poza nią. Aby zmienna była widoczna tylko w funkcji, musimy ją zadeklarować jako lokalną przy pomocy słowa kluczowego local.
***** SKRYPT *****
#!/bin/bash
function moja_funkcja
{
local zmienna_lokalna="Ala ma kota"
zmienna_globalna="a kot ma immunitet"
}
moja_funkcja
echo "zmienna_lokalna: $zmienna_lokalna"
echo "zmienna_globalna: $zmienna_globalna"
***** KONSOLA ***** user:~ #./skrypt zmienna_lokalna: zmienna_globalna: a kot ma immunitet user:~ #
Argumenty wywołania
Wartości przyjmowane są przez funkcje w ten sam sposób, co wartości przekazywane do programu. Przykładowy skrypt jest próbą ułatwienia administratorowi zakładania nowych użytkowników. Obrazuje on przekazanie zmiennej do funkcji.
#/bin/bash
function dodaj
{
useradd $1
echo -e "Start0w3\nStart0w3" | (passwd --stdin $1)
chage -d 0 $1
}
if [ $# -gt 0 ]
then
for x in $@
do
dodaj $x
done
else
echo "wymagany jest przynajmniej jeden argument"
fi
W skrypcie została rozdzielona część odpowiedzialna za pobieranie informacji oraz część odpowiedzialna za samo tworzenie użytkownika. Dzięki temu możemy w przyszłości kilkukrotnie użyć funkcji dodającej użytkownika i modyfikującej jego konto oraz modyfikować kod opowiedzialny za tą funkcjonalność tylko w jednym miejscu.
W skrypcie tym użyliśmy ciekawej konstrukcji dla passwd. Nie jesteśmy w stanie podać passwd hasła podczas jego wywoływania (najprawdopodobniej z powodu bezpieczeństwa ;) ) ale możemy przełączyć passwd w tryb, w którym będzie czytać dane z standardowego wejścia i przekierować do niego dane z echo, które dzięki opcji -e, interpretuje znaki specjalne, jak \n (nowa linia).
Wyrażenia regularne
Wyrażenia regularne, to wzorce dopasowujące się do tekstu. Dzięki nim możemy sprawdzać, czy jakiś tekst spełnia określony przez nas szkielet tekstu, czy nie. Bez narzędzia, jakim są wyrażenia regularne, musieli byśmy za każdym razem budować skomplikowane algorytmy złożone z warunków i pętli.
Znaki specjalne
Do pisania wzorców, oprócz samego tekstu, używany też pewnych znaków specjalnych, dzięki którym możemy określić pewne parametry występowania danych znaków. Poniżej znajduje się lista znaków specjalnych dla wyrażeń regularnych:
- . - dowolny znak
- ^ - początek wiersza
- $ - koniec wiersza
- [znaki] - w nawiasach klamrowych, znajdują się znaki lub przedziały znaków, które mogą wystąpić
- ( ) - służą do grupowania wyrażeń
- ? - dokładnie jedno powtórzenie elementu, który znajduje się przed tym znakiem
- * - zero lub więcej elementów, które znajdują się przed tym znakiem
- + - jeden lub więcej elementów, które znajdują się przed tym znakiem
- znak_a|znak_b - znak_a lub znak_b
Aby użyć znaku specjalnego, jako normalnego znaku do wyrażenia regularnego, należy postawić przed nim znak \ . Przykładowo, znak \* oznacza po prostu gwiazdkę a nie dowolną ilość znaków.
Podczas używania nawiasów klamrowych, możemy wpisać do nich pojedyncze znaki (np. [abc] - dopasuje się do znaku a lub b lub c) albo zbiór znaków (np. [0-9], [a-Z]).
Porównywanie
Do porównywania teksty z wzorcem, używamy znaków =~ (w ten san sposób, jak standardowe sprawdzanie warunków).
if [ $tekst =~ $wzor ] then ... fi
Przykład
Postaramy się przerobić przykład, w którym dodawaliśmy nowych użytkowników o dodawanie adresu e-mail w polu, które jest komentarzem w pliku /etc/passwd. Do dodawania tego pola, służy opcja -c. Będzie on wprawdzie dodawał jednego użytkownika, ale za to będzie starał się sprawdzać poprawność wprowadzanych danych.
#/bin/bash
email_wzor="[a-z0-9]*@[a-z]\.[a-z]"
if [ $# -eq 2 ]
then
if [[ $2 =~ $email_wzor ]]
then
useradd $1 -c $2
echo -e "Start0w3\nStart0w3" | (passwd --stdin $1)
chage -d 0 $1
else
echo "adres e-mail nie spełnia założonych wymagań"
fi
else
echo "wymagane są dwa argumenty (nazwa, e-mail)"
fi
Ważne jest użycie podwójnych nawiasów klamrowych, o których była mowa w rozdziale Warunki.