Programowanie w Bash

Z openSUSE wiki
Uwaga.png Uwaga: Artykuł jest w trakcie tworzenia


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.