5 Skrypty - wstęp do programowania w powłoce

5.4 Powłoka sh (Bash)

5.4.1 Zmienne

Zmienne definiuje się używając składni zmienna=wartosc lub w przypadku zmiennych liczbowych let zmienna=liczba, np.
$ napis=``Ala ma kota''
$ let wynik=10
Zmienne środowiskowe (globalne) tworzymy poleceniem export.
$ export zmienna
$ export PATH=$PATH:.
Nazwa zmiennej może składać się z dowolnych liter, cyfr (cyfra nie może być pierwszym znakiem nazwy zmiennej) oraz znaku podkreślenia.
Wartość umieszczoną w zmiennej wydobywamy umieszczając $ przed nazwą zmiennej
$ echo $napis
$ Ala ma kota
$ echo $HOME
$ /home/student
$ echo $wynik
$ 10

Tablice

W powłoce Bash mamy do dyspozycji tablice jednowymiarowe. Nie muszą one być deklarowane. Do poszczególnych elementów tablicy odwołujemy sie poprzez nawiasy kwadratowe ${zmienna[indeks]}
$ kolor[1]=bialy
$ kolor[2]=czarny
$ kolor[3]=zielony
$ echo Kolor pierwszy $kolor[1]
$ echo Wszystkie kolory $kolor[*]
Tablice indeksowane są liczbami całkowitymi począwszy od zera. Automatyczne przypisanie kolejnych wartości do tablicy uzyskujemy wpisując zmienna=(wartosc1 wartosc2 ... wartoscN), np.
$ dzien=(poniedzialek wtorek sroda czwartek piatek sobota niedziela)
$ echo $dzien[6]
$ echo Dni tygodnia: $dzien[8]
$ dzien[0]=Monday
Liczbę elementów tablicy uzyskujemy wyrażeniem ${#zmienna[*]}
$ echo Ilosc dni tygodnia = $#dzien[*]
Wyrażenie ${#zmienna[indeks]} poda ilość znaków zawartych w elemencie tablicy i podanym indeksie.
$ echo Slowo $dzien[1] zawiera $#dzien[1] znakow
Polecenie unset zmienna usuwa podaną zmienną. Chcąc usunąc wybrany element tablicy nalezy wykonać unset tablica[index].
$ unset kolor
$ unset dzien[4]

Zmienne $*, $#, $0, $1

Zmienna $*$ zawiera listę wszystkich argumentów z jakimi został wywołany skrypt, zmienna $# podaje liczbę tych argumentów, zmienna $0 zawiera nazwę skryptu, zaś zmienne $1, $2, $3, itd. zawierają kolejne argumenty, np.:

#!/bin/bash
echo Nazwa skryptu=$0
echo Podales $# argumentow
echo Oto one: $*
echo Argument 1 = $1
echo Argument 2 = $2

5.4.2 Operacje arytmetyczne i warunki logiczne

Powłoka tcsh pozwala na wykonywanie prostych operacji arytmetycznych na liczbach całkowitych za pomocą instrukcji let.
Przykład:
$ let suma=2+2
$ echo $suma
$ 4
$ let liczba=$suma*2
$ echo $liczba
$ 8
Składnia polecenia let pozwala na używanie zmiennych liczbowych bez konieczności poprzedzania ich znakiem $.
$ let liczba=suma*suma+3
$ let suma++
Dostępne operatory oraz priorytet ich wykonywania są takie same jak w języku C (Zobacz tabelę 1).
Równoważne użyciu polecenia let jest zastosowanie (( wyrażenie )).
Przykłady:
$ a=$((1+2))
$ a=$(($a*$a))
$ a=$((a+1))
$ ((a++))

Proste operacje arytmetyczne można także wykonywać za pomocą instrukcji expr. Obliczenia o precyzji zmiennopozycyjnej (dla liczb rzeczywistych) można wykonywać za pomocą kalkulatorów bc lub dc.

Wyrażenia warunkowe realizowane są za pomocą [ wyrażenie ] lub poprzez polecenie test. Wartością zwracana jest kod (status programu) 0 w przypadku gdy wyrażenie jest prawdziwe lub 1 gdy wyrażenie jest fałszywe. Porównywanie napisów odbywa się za pomocą operatorów ==, !=, < i >.
$ [ $SHELL == /bin/bash ] && echo Uzywasz powloki Bash
$ test $USER != root ] && echo Nie jestes administratorem
Porównując liczby całkowite należy skorzystać z operatorów -eq ((ang. equal) - równe), -ne ((ang. not equal) - nie równe), -lt ((ang. less then) - mniejsze niż), -gt ((ang. greater than) - większe niż), -le ((ang. less equal) - mniejsze równe) i -ge ((ang. greater equal) - większe równe).
$ [ $RANDOM -lt 16384 ] && echo Reszka
$ test `cat /etc/passwd | wc -l` -gt 100 && echo Uzytkownikow jest wiecej niz 100
Wyrażenie warunkowe może tez sprawdzać atrybuty plików:
-e - plik istnieje, -f jest zwykłym plikiem, -d jest katalogiem, -r jest plikiem do odczytu, -w - jest plikiem do zapisu, x - plik można uruchomić.
$ [ -e /etc/passwd ] && echo Plik /etc/passwd istnieje
$ test -d /etc/passwd && echo Plik /etc/passwd jest katalogiem
Dostępne mamy też operatory && (AND), || (OR) oraz zaprzeczenie ! (NOT) pozwalające łączyć wyrażenia warunkowe w bardziej złożone warunki.
W takim przypadku złożone wyrażenie logiczne należy umieścić w dodatkowych nawiasach [].
$ [[ $(($RANDOM%2)) -eq 1 && ! $USER == root ]] && echo Warunek jest spelniony
$ test ! -w /etc/passwd || $USER != root && echo Nie masz uprawnien do modyfikcaji /etc/passwd lub nie jestes root-em.
Wyrażenia warunkowe znajdują zastosowanie wraz z instrukcją if oraz pętlą while).

5.4.3 Instrukcje sterujące

Warunek if

Składnia warunku:
 
if wyrażenie;
then
       instrukcje
fi
 
Gdy wyrażenie jest prawdziwe (zobacz wyrażenia warunkowe) wówczas wykonywane są instrukcje pomiędzy słowem then i fi.
Przykład:

#!/bin/bash
if [ $# -eq 0 ];
then
   echo "Nie podałeś żadnych argumentów "
fi
exit 0

Bardziej rozbudowane wyrażenie warunkowe:
 
if wyrażenie;
then
       instrukcje
else
       instrukcje 2
fi
 
Instrukcje zawarte w bloku rozpoczynającym się od else są wykonywane gdy wyrażenie nie jest spełnione. Np.:

#!/bin/bash
if [ $# -eq 0 ];
then
   echo "Nie podałeś żadnych argumentów. "
   echo "Podaj liczbe calkowita "
   echo "Sprobuj: $0 liczba"
   exit 1
fi
if [ $1 -lt 0 ];
then
   echo Liczba $1 jest mniejsza od zera
else
  echo Liczba $1 jest wieksza lub równa 0
fi
exit 0

Przykład - skrypt o nazwie rzut.sh wyświetlający słowo Orzeł lub Reszka z prawdopodobieństwem 1/2.

#!/bin/bash
if [ $(($RANDOM%2)) -eq 1 ];
then
   echo "Reszka"
else
  echo "Orzeł"
fi

Przykład - skrypt o nazwie podglad.sh który dla danego w argumencie pliku wyświetla jego zawartość za pomocą less, zaś jeżeli podany argument jest nazwą pliku wówczas wyświetlana jest zawartość tego katalogu.

#!/bin/bash
if [ $# -lt 1 ];
then
   echo "Podaj plik lub katalog jako argument."
   echo "Uzycie: $0 plik"
   exit 1
fi
if [ -f $1 ];
then
  less $1
else
  if [ -d $1 ];
  then
     ls -l $1
  else
    echo "Blad: $1 nie jest plikiem ani katalogiem"
  fi
fi

Pętla for

Pętla for wykonuje zadane instrukcje tyle razy ile jest elementów na podanej liście.  
for zmienna in lista;
do
       instrukcje
done
 
W każdej iteracji kolejny element z listy jest podstawiany do zmiennej.
Przykład uzycia w linii komend:
dla każdego pliku w bieżącym katalogu wyświetli komunikat o ilości zajmowanych bajtów. Przykład - skrypt zamieniający w nazwach plików duże litery na małe.

#!/bin/bash
if [[ $# -eq 0 || $1 == "-h" || $1 == "--help" ]];
then
  echo "Uzycie: $0 [-h] plik..."
  echo "Zamienia w nazwach podanych plikaow duze litery na male (np. Plik.TXT na plik.txt)."
  echo "Opcja -h wyswietla pomoc."
  exit 1
fi
for plik in $*
do
  if [ -e $plik ];
  then
    nowy_plik=$(echo $plik | tr '[A-Z]' '[a-z]')
    if [ $plik != $nowy_plik ];
    then
      echo "Zamieniam: $plik na $nowy_plik"
      mv $plik $nowy_plik
    fi
  else
    echo "Blad: $plik - nie ma takiego pliku"
  fi
done

Powłoka Bash umożliwia także użycie pętli for znanej z języka C.
 
for (( wyrażenie1 ; warunek ; wyrażenie2 ))
do
       instrukcje
done
  Wszystkie instrukcje są wykonywane dopóki warunek jest spełniony. Początkowe wyrażenie1 jest uruchomione tylko raz przed rozpoczęciem pętli, zazwyczaj służy do zainicjowania zmiennych. Końcowe wyrażenie2 jest wykonywane na końcu każdej iteracji, zazwyczaj używane jest do zwiększenia (lub zmniejszenia) pewnego licznika.
Przykład - skrypt wyznaczający silnię:

#!/bin/bash
if [[ $# -eq 0 || $1 == "-h" || $1 == "--help" ]];
then
  echo "Uzycie: $0 [-h] liczba"
  echo "Oblicza silnie podanej liczby."
  echo "Opcja -h wyswietla pomoc."
  exit 1
fi
silnia=1;
for (( i=2 ; i<=$1 ; i++ ))
do
  let silnia=silnia*i;
done
echo "Silnia wynosi $silnia"

Przykład - wielokrotne losowanie kostką:

#!/bin/bash
if [[ $1 == "-h" || $1 == "--help" ]];
then
  echo "Rzut kostka"
  echo "Uzycie: $0 [-h] liczba"
  echo "Wyswietla wynik rzutu kostka powtorzenoge zadana liczbe razy."
  echo "Opcja -h wyswietla pomoc."
  exit 1
fi
ile=1
if [ $# -gt 0 ]; then ile=$1 ;fi
for (( i=1 ; i<=ile ; i++ )) do
  wynik=$(($RANDOM%6+1))
  echo $wynik
done

Pętla while

Składnia pętli while:
 
while warunek
do
       instrukcje
done
  Podane instrukcje są wykonywana dopóki warunek jest prawdziwy.
Przykład - stoper, odlicza sekundy od rozpoczęcia działania skryptu:

#!/bin/bash
if [[ $1 == "-h" || $1 == "--help" ]];
then
  echo "Stoper - po prostu uruchom bez argumentow."
  echo "Ctrl+C konczy odliczanie."
  echo "Opcja -h wyswietla pomoc."
  exit 1
fi
let s=0
while true; do
  echo $s
  sleep 1
  let s++
done

Instrukcja true zwraca zawsze wartość logiczną prawda. Analogicznie false daje odpowiedź negatywną.

Instrukcja case

Instrukcja case pozwala na wykonanie wybranych instrukcji w zależności od wartości przyjmowanej przez pewną zmienną. Działanie bardzo podobne do instrukcji if jednak często wygodniejsze w użyciu, zwłaszcza gdy mamy więcej niż dwie możliwości do wyboru.
 
case zmienna in
       wartość_1)
              instrukcje 1
              ;;
       wartość_2)
              instrukcje 2
              ;;
       …
       *)
              instrukcje
              ;;
esac

  Przykład - skrypt wyświetla menu:

#!/bin/sh
while true
  do
     clear
     echo "============================"
     echo "[1] Wyświetl dzisiejszą datę"
     echo "[2] Wyswietl listę plików w bierzącym katalogu"
     echo "[3] Pokarz kalendarz"
     echo "[4] Pokarz listę zalogowanych uzytkownków"
     echo "[5] Zakończ"
     echo "============================"
     echo -n "Wybierz liczbę [1-5]: "
     read akcja
     case $akcja in
       1) echo "Dzisiejsza data: `date`"
          ;;
       2) echo "Lista plików w katalogu `pwd`"
          ls -l
          ;;
       3) cal  ;;
       4) echo "Lista zalogowanych" ; who ;;
       5) echo "Do widzenia"
          exit 0 ;;
       *) echo "Blad!!! Proszę wybrać wartość 1,2,3,4, lub 5";
  esac
  echo  "Wciśnij klawisz Enter"
  read
done

Instrukcja exit

Instrukcja exit kończy działanie skryptu. Liczba całkowita umieszczona po instrukcji exit jest zwracana do powłoki jako wynik działania skryptu. W przypadku poprawnego wykonania skrypt powinien kończyć się wyrażeniem exit 0. Gdy skrypt nie został wykonany poprawnie wówczas po słowie exit wstawiamy dowolną liczbę różną od zera (wartość zwracanej liczby może w ten sposób sygnalizować rodzaj błędu który spowodował niepoprawne wykonanie skryptu). Wartość zwracana po słowie exit umieszczana jest w zmiennej $?.

Instrukcja read

Instrukcja read pozwala wczytać linie tekstu do podanej zmiennej.
Przykład:

#!/bin/bash
echo Podaj imie i nazwisko
read dane
echo Witaj $dane

Możliwe jet też czytanie tekstu z pliku linia po linii, np.:

#!/bin/bash
echo Podaj nazwe pliku do wyswietlenia
read plik
if [ ! -r $plik];
then
  echo "Nie moge czytac z pliku $plik"
  exit 1
fi
cat $plik | while read linia
do
  echo $linia
done

Funkcje

W powłoce bash możliwe jest definiowanie funkcji, czyli wyodrębnionych podprogramów oznaczonych pewną unikatową nazwą.
 
nazwa_funkcji( )
{
       instrukcje do wykonania
       return
}

 
Funkcja wykonywana jest w momencie gdy pojawi się wywołanie jej nazwy.
Przykład:

#!/bin/sh
pomoc()
{
  echo "Wyswietla liste i liczbe zalogowanych uzytkonikow"
  echo "Opcje:"
  echo "-h pomoc"
  echo "-l liczba zalogowanych użytkoników"
  echo "-w lista zalogowanych użytkoników"
  return
}
# lista niepowtarzajacych sie nazw zalogowanych uzytkownikow
lista()
{
  users=( `who | cut -f 1 -d ' ' | sort | uniq ` )
  return
}
case $1 in
  "-h")  pomoc ;;
  "-l") lista
        echo "Liczba zalogowanych uzytkonikow = ${#users[*]} "
        ;;
  "-w") lista
        echo "Lista uzytkownikow: ${users[*]}" ;;
     *)Ψpomoc ;;
esac

Do funkcji możemy przekazać argumenty dodając je przy wywołaniu po nazwie funkcji. Kolejne argumenty umieszczone są w zmiennych $1, $2, $3, itd. Ilość argumentów dana jest poprzez $#, lista wszystkich argumentów zawarta jest w zmiennej $* a zmienna $0 zawiera nazwe funkcji.

#!/bin/sh
funkcja()
{
  echo "Argumenty funkcji $*"
  echo "Ilość argumentów funkcji $#"
  return 0
}
echo "Argumenty skryptu $*"
echo "Ilość argumentów skryptu $#"
echo Uruchamiam funkcje z argumentami: raz dwa trzy
funkcja raz dwa trzy
echo Uruchamiam funkcje z argumentami będącymi nazwami plików z bierzącego katalogu
funkcja *
exit 0

Zmienne użyte wewnątrz funkcji maja zakres globalny, tzn. ich wartość jest dostępna poza funkcją (np. zmienna users z pierwszego przykładu w tym paragrafie). Chcąc ograniczyć czas życia zmiennej wyłącznie do obszaru zdefiniowanego przez funkcję należy zadeklarować zmienną poprzedzając ja instrukcją local. Używanie zmiennych lokalnych może uchronić przed wieloma trudnymi do wykrycia błędami, więc gdzie tylko jest to możliwe, wewnątrz funkcji należy je stosować.
Przykład:

#!/bin/sh
funkcja()
{
  local zmienna="Zmienna lokalna"
  echo "Jestem wewnątrz funkcji"
  echo "zmienna=$zmienna"
  return 0
}
zmienna="Zmienna globalna"
echo "Zmienna=$zmienna"
funkcja
echo "Zmienna=$zmienna"
exit 0