Další Předchozí Obsah

8. Programování v Bourne shellu

Jako většina shellů (prostředí příkazové řádky) dostupné v Unixu není sh pouze výbornou příkazovou řádkou, ale zároveň také plnohodnotným skriptovacím jazykem. Skriptování v shellu umožňuje zautomatizovat spoustu úloh, které by jinak vyžadovaly psaní spousty příkazů na příkazovou řádku. Mnoho programů v jednotlivých distribucích Unixu jsou pouze skripty příkazové řádky. Zajímáte-li se jak to vlastně pracuje, nebo chcete-li tyto skripty modifikovat či vytvářet, je podstatné porozumět syntaxi a sémantice jazyka sh. Při porozumění syntaxi a způsobech jazyka sh budete schopni modifikovat programy, ale hlavně psát programy vlastní, které se budou chovat přesně tak, jak chcete. Ale pamatujte si - počítač nikdy neudělá to, co chcete, ale to, co mu zadáte.

Lidé, kteří teprve začínají programovat jsou často zmateni a neví, jaký je rozdíl mezi skriptovacím a kompilovaným jazykem. Tak tedy - kompilované programovací jazyky jsou mnohem více rychlejší a silnější než skriptovací jazyky. Příkladem kompilovaných jazyků je například C, C++ a Java. Programovací jazyky se většinou skládají ze zdrojového kódu (soubor obsahující instrukce a data, která budou při běhu vykonána). Po napsání zdrojového kódu je program kompilován (přeložený do jazyka procesoru) a následně se může program spustit. Takto vytvořený program však nelze přenést na jinou platformu bez nutnosti opět přeložit zdrojový kód na jiné platformě (existuje-li na ní ovšem překladač). Například jestliže napíšete program v jazyku C pro Unix, nebudete schopni tento program spustit ve Windows 98.

Skriptovací jazyky také začínají psaním zdrojového kódu, ale již nejsou kompilovány a spuštěny. Místo toho interpreter jazyka čte instrukce ze zdrojového kódu a podle obsahu tyto instrukce vykonává. V případě sh spouští programy, které jste napsali do zdrojového kódu. Bohužel, protože interpreter čte každé instrukce jednotlivě a jejich zpracování není tím pádem úplně nejrychlejší (program je částčně překládán za běhu), jsou tedy skriptovací jazyky výrazně pomalejší než kompilované programy. Hlavní výhoda skriptovacích jazyků spočívá v jejich přenositelnosti na jiné platformy či operační systémy, jejich jednoduchosti a také velikosti. Skriptovacím jazykem je právě například sh. Příklady dalších interpretovaných jazyků (skriptovacích) jsou Perl, Lisp, Prolog a Tcl (čteno tikl).

Psaní skriptů v sh vyžaduje znalost alespoň základních příkazů shellu sh (jako kopírování, přesování, tvorba nových souborů atd.), znalost některého textového editoru. V Unixu jsou takové tři nejznámější editory vi, emacs a pico.

A na závěr jedno varování!!! Raději nedělejte jako začátečník žádné skripty jako superuživatel root. Vzhledem k fakticky neomezeným právům by jste snadno mohli narušit systém. Raději své skripty dělejte jako normální uživatel. Odpadnou vám tak problémy s opravami systému.

8.1 První program

Náš první program nemůže být samozřejmě nic jiného než naprosto klasické "Hello World" :-) Tento program jednoduše vypíše na obrazovku slova Hello World. Takže nyní nastartujem textový editor a napíšeme do něj následující řádky:

#!/bin/sh
echo "Hello, world"

První řádka programu říká Unixu, co se má použít za interpret. V našem případě tedy sh, který se nachází v /bin/sh. Jestliže však je váš sh v jiném adresáři, musíte zadat jinou cestu. Určení interpretru, který bude program vykonávat je velice důležité, protože pak by se taky mohlo stát, že skript bude vykonávat úplně jiný interpretr a tam by náš skript pravděpodobně vůbec běžet nemusel. Příkaz echo provede výstup zadaného řetězce na obrazovku. Teđ už zbývá pouze tento skript spustit.

Skript je možné spouštět různými způsoby:

1) Vyvoláním shellu s parametrem tvořeným jménem souboru se skriptem následovaným případnými parametry skriptu:


$ sh skript parametry
$ csh skript parametry

Není-li soubor skript v aktuálním adresáři, je nutné uvést cestu k souboru. K souboru skript stačí mít právo read. Pro provedení skriptu se spouští přímo zadaný interpret příkazů, který se může lišit od interpretu, který právě daný uživatel používá.

2) Zpracováním skriptu aktuálním shellem:


$ . skript parametry

(příkaz tečka) v sh, nebo

$ source skript parametry

v csh. Pravidla jsou stejná jako v případě 1).

3) Zadáním jména skriptu:


$ skript parametry

V tomto případě je nutné mít k souboru skript právo read a execute (kdyby se jednalo o binární program, stačilo by pouze execute). Aby nebylo nutné zadávat celou cestu ke skriptu, je možné skript umístit do jednoho z adresářů uvedených v proměnné $PATH. Jestliže se jedná o skript, který nebyl umístěný v jednom z těchto adresářů v okamžiku nalogování, je nutné v csh zadat příkaz

$ rehash

Csh si totiž při svém startu vytváří databázi všech programů ve všech adresářích uvedených v $PATH a nově vytvořený skript nebere v úvahu, dokud se databáze příkazem rehash neaktualizuje.

Předpokládejme, že se náš skript jmenuje hello.sh (přípona sh není podmínkou, většina skriptů žádnou příponu nemá). Pak bude zápis chmod vypadat takto:

$ chmod 700 ./hello.sh
$ chmod +x ./hello.sh

Následně skript můžeme spustit takto:

$ ./hello.sh
Hello, world

A je to! Máte první program. A funguje. Sice zatím toho mnoho neumí, ale jako demonstrace primitivního programu zatím postačí. Takže čeho jsme docílili? Použili jsme příkaz shellu echo, který zajistil výpis textu na obrazovku. Jeho paramtrem je právě ten text. Je také možný ještě volitelný parametr -n, který zajistí to, že po vypsání textu neskočí na další řádku. A ještě zkuste jednu věc. Napište přímo do příkazové řádky toto:

$ echo "Hello, world"
Hello, world

Je tedy doufám zřejmé, které příkazy se používají v sh skriptu. Ty stejné, co můžete napsat přímo do příkazové řádky.

Nyní tedy zkusme napsat mnohem užitečnější program. Program, který přesune všechny soubory do adresáře, a pak tento adresář smaže i s tím, co obsahuje. Poté se tento adresář znovu vytvoří. To můžeme udělat následujícími příkazy:

$ mkdir trash
$ mv * trash
$ rm -rf trash
$ mkdir trash

Místo toho, aby jsme to pořád psali do příkazové řádky, můžeme si na to místo toho vyvořit na tuto úlohu skript:

#!/bin/sh
mkdir trash
mv * trash
rm -rf trash
mkdir trash
echo "Všechny soubory jsou smazány!"

Tento soubor můžeme nazvat třeba clean.sh, nastavit práva a používat. Jednoduché, že? Do programu je také možné přidat komentář. Děje se tak pomocí znaku #. Začíná-li řádka právě tímto znakem, bere se tato řádka jako poznámka. Tedy tato řádka se nevykonává. Jedinou výjimku tvoří právě první řádka skriptu, ve které je uveden interpreter, který provede skript. Je dobré poznámky používat už jen z toho důvodu, aby jste si program zpřehlednili. (I když správný programátor ví, že komentáře jsou zbytečné, že činnost programu vyplyne z kódu.)

Ještě bych upozornil na využívání manuálových stránek (nebo na nápovědu info). Mnoho variant použití konkrétního příkazu se dozvíte právě zde. Pro ukázku bych vám zde uvedl manuálovou stránku programu echo:


ECHO(1)                                                   ECHO(1)

JMÉNO
       echo - zobrazí řádek textu

POUŽITÍ
       echo [-ne] [řetězec ...]
       echo {--help,--version}

POPIS
       Tato  manuálová  stránka  popisuje GNU verzi příkazu echo.
       Pamatujte, že většina shellů má vestavěný příkaz  stejného
       jména s podobnou funkcí.

       Příkaz  echo  vypíše  všechny zadané řetězce na standardní
       výstup, oddělené mezerami a ukončené znakem newline.

   VOLBY
       -n     Nevypisovat závěrečný znak newline.

       -e     Povolit   interpretaci   následujících    obráceným
              lomítkem uvozených znaků v řetězcích:
              \a     pípnutí, zvonek (bell)
              \b     backspace
              \c     nevypisovat závěrečný znak newline
              \f     nová stránka (form feed)
              \n     nový řádek (new line)
              \r     návrat vozíku (carriage return)
              \t     horizontální tabelátor (horizontal tab)
              \v     vertikální tabelátor (vertical tab)
              \\     obrácené lomítko (backslash)
              \nnn   znak, jehož ASCII kód je nnn (osmičkově)

   VOLBY
       Když je GNU příkaz echo vyvolán právě s jedním parametrem,
       jsou rozpoznávány následující volby:

       --help Vypíše návod k použití na standardní výstup a  bez­
              chybně skončí.

       --version
              Vypíše číslo verze na standardní výstup a bezchybně
              skončí.

FSF                    GNU Shell Utilities                      1

8.2 Proměnné

Proměnné jsou vlastně takové "krabičky" do kterých se ukládají hodnoty (laicky řečeno). Jsou to tedy místa v paměti označená identifikátorem (názvem proměnné). Tento identifikátor představuje pro překladač/interpreter adresu místa v paměti, kde je uložena hodnota odpovídající proměnné. Samozřejmě hodnoty proměnných lze měnit. V sh se proměnné deklarují např takto:

#!/bin/sh
x=5 echo "Hodnota proměnné x je $x."

Z příkladu je tedy dobře vidět, že proměnné se deklarují tím, že se jim přiřadí nějaká hodnota. Je to tedy obdobné jako v BASICu. Další řádka programu vytiskne na obrazovku (tedy pokud nepřesměrujeme výstup :-) větu Hodnota proměnné x je 5.

Z příkladu je též patrno, že k proměnné se přistupuje pomocí znaku symbolu dolaru ($) uvedeného před názvem proměnné. A ještě něco pro ty, kteří rádi mezery mezi =. V sh nesmí být u rovná se mezera! Proměnná x by se totiž brala jako příkaz a rovná se její parametr. Vzhledem k tomu, že příkaz x neexistuje, ohlásil by sh chybu. Používá se tedy zápis:

jméno_proměnné=hodnota_proměnné

Co se proměnných týče, obecně jsou v sh dva typy proměnných. Lokálních (platných pouze pro konkrétní spuštěný skript) a systémových (platných pro celý operační systém, které lze využít všemi programy v systému). Pokud by jste chtěli svou proměnnou udělat jako systémovou, musíte použít tento způsob deklarace:

#!/bin/sh
export x=5

nebo také

#!/bin/sh
x=5
export $x

Systémové proměnné se dají také využít. Například pro zjištění názvu interpretru, který je právě spuštěný:

$ echo $SHELL
/bin/sh

Systémové proměnné jsou většinou definovány v /etc/profile a v ~/.sh_profile. Příkaz echo je dobrý právě ke zjišťování těchto proměnných. Chtěli-li by jste vypsat seznam všech systémových proměnných v systému, existuje příkaz env, který tyto proměnné vypíše.

8.3 Řídící struktury

Řídící struktury můžou váš program řídit a tím ho udělat mnohem více užitečný a efektivnější. Velmi důležitá je například detekce chyb. Příklad programu s řídící strukturou by mohl být tento:

#!/bin/sh
cp /etc/foo .
echo "Hotovo!"

Tento skript nazvěme například bar.sh. Jeho činnost spočívá v tom, že zkopíruje soubor /etc/foo do aktivního adresáře a nakonec vypíše "Hotovo!". Ale uvědomte si, že ne každý má ve svém adresáři soubor /etc/foo. V tom případě se žádný soubor nezkopíruje, program cp vypíše chybu a poté se v klidu vypíše "Hotovo!". Toto chování by se však nemělo objevovat. V pseudokódu by to tedy mělo vypadat takto:

jestli /etc/foo existuje pak
  kopíruj /etc/foo do aktivního adresáře
  napiš na obrazovku "Hotovo!"
jinak
  napiš na obrazovku "Soubor neexistuje!"   konec

Mohlo by toto být realizováno v sh? Samozřejmě! Jeho řídící strkutury jsou: if, while, until, for a case. Každá tato struktura je tzv. párová. To znamená, že začíná jako "návěst" a končí taky "návěst". Například struktura podmínky začíná if a končí fi. Řídící struktury nejsou programy, které by jste mohli nalézt v systému. Jsou to pouze interní funkce shellu sh. Řídící struktury obecně tvoří základ tazvaného strukturovaného programování.

8.4 If ... else ... elif ... fi

Jedna z nejběžnějších a nejdůležitějších struktur ve skriptech je právě struktura if. Ta umožňuje na základě podmínky vykonat buđ tu nebo jinou činnost. Používá se na větvení programu, kde na základě vyhodnocení podmínky se pokračuje danou částí kódu. Příklad struktury if je zde:

if test -f /etc/foo
then   #Soubor existuje. Zkopíruj tedy soubor
  cp /etc/foo .
  echo "Hotovo!"
else
  # Soubor neexistuje. Vypiš tedy chybu
  echo "Soubor neexistuje!"
  exit
fi

Tento příklad tedy na základě vyhodnocení příkazem test soubor /etc/foo zkopíruje nebo neexistuje-li soubor, vypíše se chybové hlášení. K otestování existence souboru je použit příkaz test. Příkaz na základě přepínače -f určí, zda-li je /etc/foo soubor. Další možnosti přepínače příkazu test shrnuje následující tabulka. Je také možno použít manuálovou stránku:

Možnosti příkazu test
-d testuje, jestli soubor je adresář
-e testuje, jestli soubor existuje
-f testuje, jedná-li se o regulární soubor
-g testuje, má-li soubor povolení SIGD
-r testuje, zda-li jde soubor číst
-s testuje, zda-li velikost souboru není 0
-u testuje, má-li soubor povolení SUID
-w testuje, zda-li se dá do souboru zapisovat
-x testuje, zda-li je soubor spustitelný

Jestliže není splněna podmínka testovaná příkazem if, neprovede se tedy blok příkazů uvedený za slovem then, ale za to se provedou příkazy v bloku zahájeném příkazem else (pokdu se vyskytuje). Je zde také slovo elif. To je používáno pro testování podmínky místo příkazu else. Normálně se totiž blok příkazů začínající else provede automaticky po nesplnění podmínky. Ale příkaz elif místo příkazu else umožní, že se tento blok provede jen pokud vyhoví podmínka za příkazem elif.

Připadá-li vám formát podmínky příliš nekomfortní, je možné používat také formát následující:

if test -f /etc/foo
then

# se dá také zapsat takto:

if [ -f /etc/foo ]; then

Hranaté závorky nahrazují příkaz test. Máte-li zkušenosti s programováním v jazyce C, může vám tato syntaxe připadat více komfortnější. Středník ; říká interpretru, že je zde konec řádku. Všechno, co následuje za středníkem je chápáno jako by bylo na novém řádku. Je to důležité, protože sh očekává příkaz then až na následujícím řádku.

8.5 While ... do ... done

Struktura while je struktura cyklu. V podstatě vykonává blok příkazů tak dlouho, dokud je splněna podmínka uvedena za slovem while. Příklad struktury while je zde:

#!/bin/sh
while true; do
  echo "Press Ctrl-C to quit."
done

Příkaz true je program, který vždy vrací hodnotu true (pravda). Náš skript tedy bude neustále opakovat vypisování hlášky jak ukončit program a to až do té doby než ho uměle přerušíme (Ctrl-C). Způsob volání true je však poněkud pomalý, protože systém musí vždy spouštět program true. Existuje rychlejší varianta jak docílit toho samého příkladu jak před tím a to použítím dvojtečky místo true:

#!/bin/sh
while :; do
  echo "Press Ctrl-C to quit."
done

Dvojtečka je totiž interní příkaz interpretru sh. Tento příklad je však poněkud nepraktický, tak pojđme na další už více užitečný.

V následujícím příkladu použijeme proměnnou x, která bude při každém průchodu cyklu zvyšovat svojí hodnotu a to až do té doby než dosáhne hodnoty 10.

#!/bin/sh
x=0;     # inicializuje hodnotu x na 0
while [ "$x" -le 10 ]; do
  echo "Aktuální hodnota x: $x"
  # zvýšení hodnoty x o 1
  x=$(expr $x + 1)
  sleep 1
done

Jak vidíte, používáme příkaz test (hranaté závorky), abychom zjistili, jestli hodnota proměnné x je menší nebo rovno 10. Je-li menší nebo rovno 10, test je vyhodnoceno jako true a blok příkazů za slovem do se může provést. Je-li však podmínka vyhodnocena jako false, pokračuje program v běhu za slovem done.

Následující tabulka shrnuje další možnosti příkazu test.

Možnosti příkazu test
Pro čísla:
x -eq y Testuje, jestli x=y
x -ne y Testuje, jestli x!=y (nerovná se)
x -qt y Testjue, jestli x>y
x -lt y Testuje, jestli x<y
Pro řetězce:
x = y Testuje, jsou-li řetězce shodné
x != y Testuje, jsou-li řetězce rozdílné
-n x Vrátí true, není-li řetězec prázdný
-z x Vrátí true, je-li řetězec prázdný

Věřím, že nebude těžké porozumět i dalšímu příkazu ve skriptu a to je expr. Tento příkaz ve skriptu přičte k proměnné x hodnotu 1 a vrátí to do x. Ale co znamená $(...)? Tato konstrukce zařídí, že hodnota výsledeku výrazu v závorkách se zapíše jako hodnota do výrazu, který tuto konstrukci obsahuje. Je tedy možné tímto způsobem například zjistit pod jakým jménem jsme přihlášeni do systému. Příklad:

#!/bin/sh
x=$(whoami)
echo "Já jsem $x."

Samozřejmě lze také tímto způsobem dostat hodnotu vrácenou příkazem whoami přímo do řetězce. A to takto:

#!/bin/sh
echo "Já jsem $(whoami)."

Můžete se rozhodnout, který způsob je pro vás lepší. Konstrukce $(...) nám tedy dává opět další prostředek jak spustit příkaz a ještě použít hodnotu, kterou příkaz vrátí.

8.6 Until ... do ... done

Struktura until je velice podobná struktuře while. Jediný rozdíl je ve vyhodnocování podmínky pro běh bloku kódu mezi slovy do a done. Ve struktuře until se blok kódu provede pouze, je-li podmínka vyhodnocena jako false (nepravda). Zde je příklad struktury:

#!/bin/sh
x=0
until [ "$x" -ge 10 ]; then
  echo "Aktuální hodnota x: $x"
  x=$(expr $x + 1)
  sleep 1
done

Tento kód vypadá velice podobně kódu příkladu uvedeného v předchozí kapitole. Vlastně se liší jen ve dvou věcech. Je použita struktura until a tedy příkaz test je upraven tak, aby byla struktura ukončena bude-li hodnota proměnné x větší nebo rovno 10. Znamená to tedy, že se vypíší hodnoty pouze od 0 do 9.

Příkaz sleep pozastaví běh skriptu na tolik sekund, kolik je uveden parametr (v našem případě pozastaví běh skriptu na 1 sekundu). Další možnosti příkazu sleep se můžete dozvědět v manuálových stránkách, které říkají o příkazu sleep následující:

SLEEP(1)                                                 SLEEP(1)

JMÉNO
       sleep - čeká zadaný časový interval

POUŽITÍ
       sleep [--help] [--version] číslo[smhd]...

POPIS
       Tato  manuálová  stránka popisuje GNU verzi příkazu sleep.
       Příkaz sleep čeká časový interval, který je součtem hodnot
       zadaných  parametry.   Každý  argument  je číslo nepovinně
       následované označením jednotky.  Implicitní jednotkou jsou
       sekundy. Povolené jednotky jsou:

       s      sekundy

       m      minuty

       h      hodiny

       d      dny

   VOLBY
       --help Vypíše  návod k použití na standardní výstup a bez­
              chybně skončí.

       --version
              Vypíše číslo verze na standardní výstup a bezchybně
              skončí.

FSF                    GNU Shell Utilities                      1

8.7 For ... in ... do ... done

Struktura for se používá, chcete-li během cyklu použít různé hodnoty pro jednu proměnnou, a to tak, že při každém průběhu cyklu bude mít proměnná jinou hodnotu. Například chcete napsat skript, který desetkrát vytiskne každou sekundu tečku (.) lze to provést takto:

#!/bin/sh
echo -n "Zjišťuji chyby v systému"
for dots in 1 2 3 4 5 6 7 8 9 10; do
  echo -n "."
  echo "Systém je čistý."
done

V příkazu echo vidíme parametr -n. Tento parametr určuje, že po vypsání textu se neskočí na další řádku na obrazovce, ale bude se pokračovat přímo za vypsaný text. Pro lepší pochopení zkuste jednou tento skript bez -n a jednou s -n. Rozdíl jasně uvidíte.

Nyní zpět k příkazu for. Za příkazem forse uvede název proměnné, která bude měnit svou hodnotu podle hodnot uvedených za slovem in. Blok příkazů, který se bude s takovouto hodnotou provádět je ohraničen klíčovými slovy doa done. V našem případě tedy bude proměnná x postupně nabývat hodnot 1 až 10 při každém jednotlivém průběhu cyklu. Tyto hodnoty však v našem cyklu nejsou použity (ale samozřejmě použity být můžou, a běžne se tak děje). V našem konkrétním případě slouží pouze pro to, aby cyklus proběhl desetkrát. Nyní si tedy uvedeme další příklad ve kterém už budou hodnoty v cyklu použity:

#!/bin/sh
echo "Zjišťuje se existence adresářů v systému"
for x in /dev /etc /bin /root /var /usr /home /mnt; do
  if [ -d "$x" ]; then
    echo "Adresář $x existuje"
  else
    echo "Adresář $x neexistuje"
  fi
done

Tento příklad testuje postupně jestli existují některé vybrané adresáře v systému uvedené jako hodnoty ve struktuře for. V příkladu je zkombinována struktura for se strukturou if. Je zde možné vidět, že jednotlivé řídící struktury lze vnořovat do sebe. Zde je také vidět, že jako hodnoty pro for můžou být též použity řetězce. Další užitečný příklad ukáže, jak je možné přidat všem souborům v aktuálním adresáři příponu html. Ukáže, že je tedy možné použít i žolíkové znaky pro výběr souborů (v našem případě všechny soubory, tedy *):

#!/bin/sh
for file in *; do
  echo "Přidávám příponu .html souboru $file..."
  mv $file $file.html
done

Žolíkové znaky tvoří obecně silnou stránku shellu a také jeho používání a programování značně usnadňují a přidávají mu velkou sílu a výkonnost.

8.8 Case ... in ... esac

Struktura case je velmi podobná struktuře if. V základu struktura case je určena pro vykonání jednoho z několika kusů kódu a to podle toho, jakou hodnotu má proměnná uvedená za příkazem case. Zde je příklad struktury case:

#!/bin/sh
x=5;     # inicializuje x na hodnotu 5
# nyní se bude testovat hodnota x:
case $x in
  0) echo "Hodnota x je 0."
  ;;
  5) echo "Hodnota x je 5."
  ;;
  9) echo "Hodnota x je 9."
  ;;
  *) echo "Hodnta x není ani 0, ani 5, ani 9."
esac

Je tedy vidět, že se vykoná příkaz, který je zapsán u hodnoty 5, kterou proměnná x má. Nastane-li situace, že proměnná x nebude ani 0, 5 nebo 9, provede se příkaz určen znakem *. Příkaz case tedy vyhodnotí hodnotu řídící proměnné x, podle hodnoty se poté najde příslušná hodnota před závorkou a ta se provede. Nebude-li odpovídat žádné hodnotě ve struktuře, zkusí se nalést *) a provede se blok příkazů za ním. Nenalezne-li se hvězdička, žádný příkaz ve struktuře se neprovede. Celá konstrukce by se též dala realizovat strukturou if:

#!/bin/sh
x=5
if [ "$x" -eq 0 ]; then
  echo "Hodnota x je 0."
elif [ "$x" -eq 5 ]; then
  echo "Hodota x je 5."
elif [ "$x" -eq 9 ]; then
  echo Hodnota x je 9.
else
  echo "Hodnota x je neurčena."
fi

Práce struktury case je tedy podle známé struktury if patrná. Nakonec ještě jeden příklad speciální konstrukce pomocí case.

#!/bin/sh

echo "Je nyní ráno? Odpovězte \"ano\" nebo \"ne\""
read timeofday

case "$timeofday" in
  ano | a | Ano | ANO )
    echo "Dobré ráno"
    ;;
  [nN]* )
    echo "Dobré odpoledne"
    ;;
  * )
    echo "Promiňte, ale nerozuměl jsem"
    echo "Odpovězte \"ano\" nebo \"ne\""
    exit 1
    ;;
esac

exit 0

Pro ještě lepší a efektivnější kontrolu odpovědi ano můžeme využít kontrukce pomocí žolíkových znaků

[aA] | [aA][nN][oO] )

8.9 Uvozovky

V shellu sh můžete psát tři různé typy uvozovek, z nichž každé mají jiný význam. A to jednak dvojité uvozovky: ", přední uvozovka: ' (tzv. devítka nebo apostrofa), a zadní uvozovka: `.

Dvojitá uvozovka se používá hlavně k určení několika slov oddělenými mezerami jako jeden řetězec. Tedy slova ve dvojitých uvozovkách se považují vždy za jeden parametr, a to jako jeden řetězec. Například použijeme-li příkaz:

$ mkdir hello world
$ ls -F
hello/     world/

Uvidíme to, co po použití příkazu ls -F vidíme. To jest vytvořené dva samostatné adresáře. Ale pokud použijeme následně jako parametr "hello world", uvidíme toto:

$ mkdir "hello world"
$ ls -F
hello world/

Vidíme tedy, že chceme-li vytvořit adresář, který obsahuje dvě a více slov oddělenými mezerami, musíme tento název uvést v dvojitých uvozovkách, jinak se nám vytvoří adresářů několik, z nichž každý bude mít název jako jedno ze slov uvedeného jako parametr příkazu mkdir.

Přední uvozovky také uzavírají řetězec podobně jako dvojté uvozovky, ale je zde jeden rozdíl. Řetězec uzavírající přední uvozovky bude zobrazen přesně tak, jak je zadán. Lepší pochopení usnadní následující příklad:

#!/bin/sh
x=5
echo "Hodnota proměnné x je $x"
echo 'Hodnota proměnné x je $x'

Pro pochopení je vhodné skript spustit a prohlédnout si výsledek. Je z toho patrné, že při použití dvojitých uvozovek se provede nahrazení $x za konkrétní hodnotu, kdežto pokud je výraz uzavřen do předních uvozovek (apostrof), k tomuto nahrazení nedojde.

Poslední typ uvozovek (zadní uvozovky) se používá místo této konstrukce:

x=$(expr $x + 1)

Jak vidíte x je přiřazena hodnota o jedno větší než byla hodnota x před provedením. Proměnná x se tedy inkrementovala. Zápis lze také provést pomocí uvozovek:

x=`expr $x + 1`

Který způsob je lepší? To záleží na vás. Já mám raději $(...), ale jak říkám, záleží na vás, jaký způsob vám lépe vyhovuje. Použít lze obojí. I když, někdy je možná estetičtější, obzvláště v řetězcích, použít výraz se závorkami:

echo "Já jsem `whoami`."

8.10 Aritmetika

sh také podobně jako většina programovacích jazyků dovede provádět matematické výrazy. Jak jste již viděli, aritmetika se provádí pomocí příkazu expr. Nicméně stejně jako příkaz true je i příkaz expr pomalý, a proto lze aritmetika dělat pomocí interních příkazů sh. Stejně jako je dvojtečka alternativa k příkazu true, je i k příkazu expr alternativa. A tou je uzavření výrazu do kulatých závorek. Výsledek tedy dostaneme pomocí konstrukce $((...)). Rozdíl oproti výrazu $(...) je v počtu závorek.

Zkuste tedy následující příklad:

#!/bin/sh
x=8
y=4
z=$(($x + $y))
echo "Součet $x a $y je $z"

sh je schopen vykonat i následující matematické operace:

Možné matematické operace
Operace
Znak
Sčítání +
Odčítání -
Násobení *
Dělení /
Dělení modulo %

Dělení modulo je zbytek po dělení. Tedy například 5 % 2 = 1, protože 5 děleno 2 je 2, zbytek 1. Je také nutno podotknout, že sh dovede pracovat pouze s čísly celými. Nelze tedy používat desetinná čísla.

Zde je příklad skriptu s aritmetickými operacemi:

#!/bin/sh
x=5
y=3

add=$(($x + $y))
sub=$(($x - $y))
mul=$(($x * $y))
div=$(($x / $y))
mod=$(($x % $y))

echo "Součet: $add"
echo "Rozdíl: $sub"
echo "Součin: $mul"
echo "Podíl: $div"
echo "Zbytek: $mod"

Místo add=$(($x + $y)) je též možné použít add=$(expr $x + $y) nebo add=`expr $x + $y`.

Manuálová stránka příkazu expr je uvedena zde.

EXPR(1)                                                   EXPR(1)

JMÉNO
       expr - vyhodnotí výraz

POUŽITÍ
       expr výraz...
       expr {--help,--version}

POPIS
       Tato  dokumentace  není dále udržována a může být nepřesná
       nebo neúplná.  Autoritativním zdrojem je Texinfo  dokumen­
       tace.

       Tato  manuálová  stránka  popisuje GNU verzi příkazu expr.
       Příkaz expr vyhodnotí výraz a  vypíše  výsledek  na  stan­
       dardní  výstup.  Každé  slovo  (token)  ve výrazu musí být
       zadáno jako samostatný  parametr.   Operandy  ve  výrazech
       jsou  buď  čísla  nebo  řetězce. Psaní řetězců do uvozovek
       nebo apostrofů příkaz  expr  sám  nevyžaduje,  ale  jejich
       použití může být nezbytné, aby speciální znaky v řetězcích
       neinterpretoval shell.  Příkaz  expr  automaticky  převádí
       operandy  ve  výrazech  na  celá  čísla nebo řetězce podle
       toho, jaké na ně mají být aplikovány operace.

       Příkaz expr rozpoznává následující binární operátory  (zde
       seřazené od nejnižší po nejvyšší prioritu):

       |      Pokud  první  argument  není  ani  prázdný  řetězec
              (null) ani 0, je výsledkem první argument, jinak je
              výsledkem  druhý argument.  Obvykle se používá jako
              logické `nebo'.

       &      Jestliže  ani  jeden  argument  není  ani   prázdný
              řetězec  (null) ani 0, je výsledkem první argument,
              jinak je výsledkem 0.

       <  <=  =  ==  !=  >=  >
              Pokusí se převést oba argumenty na čísla a  provést
              číselné  porovnání;  jestliže  převod na čísla není
              možné  uskutečnit,  provede   abecední   porovnání.
              Vrátí  1,  jestliže  je podmínka porovnání splněna,
              jinak vrátí 0.  Operátor == je synonymem pro =.

       +  -   Aritmetické operace sčítání a odčítání.  Oba  argu­
              menty  jsou  napřed  převedeny na čísla; je chybou,
              jestliže není možné převod na čísla provést.

       *  /  %
              Aritmetické operace násobení, dělení  a  zbytek  po
              celočíselném dělení, jako v jazyku C. Oba argumenty
              jsou napřed převedeny na čísla; je chybou, jestliže
              není možné převod na čísla provést.

       :      Porovnání  řetězce  s  regulární výrazem. Oba argu­
              menty  jsou  převedeny  na  řetězce  a  na  začátek
              druhého  je  automaticky  přidán znak `^'. Potom je
              první argument porovnán s druhým, který  je  inter­
              pretován  jako regulární výraz. Pokud první řetězec
              vyhovuje  regulárnímu  výrazu,  pak  je   porovnání
              úspěšné.  V tomto případě, jestliže je část druhého
              operandu uzavřena mezi `\(' a `\)', bude  výsledkem
              ta  část  prvního  řetězce,  která vyhovovala části
              regulárního výrazu  mezi  `\('  a  `\)',  jinak  je
              výsledkem  číslo  udávající,  kolik  znaků  prvního
              řetězce vyhovovalo celému regulárnímu výrazu. Pokud
              porovnání úspěšné nebylo, pak jestliže v regulárním
              výrazu bylo  `\('  a  `\)',  je  výsledkem  prázdný
              řetězec,  jinak  je  výsledkem  0.  Může být použit
              nejvýše jeden pár `\(' a `\)'.


       Navíc jsou rozpoznávána následující klíčová slova:

       match řetězec regex
              Alternativní zápis porovnání s regulárním  výrazem.
              Totéž jako ``řetězec : regex''.

       substr řetězec pozice délka
              Vrátí  podřetězec  zadaného  řetězce  začínající na
              zadané pozici a nejvýše o  zadané  délce.  Jestliže
              pozice  a  délka nejsou kladná čísla, vrátí prázdný
              řetězec.

       index řetězec seznam-znaků
              Vrátí první pozici v řetězci, na které  je  některý
              ze  znaků  uvedených  v  seznamu-znaků.  Jestliže v
              řetězci není žádný znak ze seznamu-znaků, vrátí  0.

       length řetězec
              Vrátí délku řetězce.

       Pro  vyznačení  pořadí  vyčíslování je možné použít kulaté
       závorky.  Klíčová slova nemohou být použita jako  řetězce.

   VOLBY
       Když je GNU příkaz expr vyvolán právě s jedním parametrem,
       jsou rozpoznávány následující volby:

       --help Vypíše návod k použití na standardní výstup a  bez­
              chybně skončí.

       --version
              Vypíše číslo verze na standardní výstup a bezchybně
              skončí.

PŘÍKLADY
       Přičtení jedničky do proměnné shellu a:

              a=`expr $a + 1`

       Následující příklad může být použit pro  vypsání  poslední
       složky  jména  souboru uloženého v proměnné a (hodnota v a
       nemusí obsahovat `/'):

              expr $a : '.*/\(.*\)' \| $a

       -- metaznaky shellu je nutné dávat  do  uvozovek  či  apo­
       strofů.

       Příkaz expr nastavuje následující kódy ukončení (exit sta­
       tus):

       0 jestliže výsledek není ani prázdný řetězec ani 0,
       1 jestliže výsledek je prázdný řetězec nebo 0,
       2 pro chybné výrazy.

FSF                    GNU Shell Utilities                      1

8.11 Čtení uživatelského vstupu

Nyní konečně přichází nějaká zábavná část. V sh můžete vytvořit program, který bude interaktivní, tedy reagovat na vstup uživatele (popř. na vstup čtený ze souboru). Příkaz uživatelského vstupu je příkaz read. Jedná se o vnitřní příkaz shellu. Příkaz vyžaduje jako parametr proměnnou. Nyní si uveđme jednoduchý příkald:

#!/bin/sh
echo -n "Zadej své jméno: "
read jmeno
echo "Zdravím tě $jmeno!"

Skript počká, až napíšete vaše jméno a zmáčknete klávesu ENTER. Pak se to, co jste napsali uloží jako hodnota do proměnné jmeno. Ta se pak může dále v programu využívat stejně jako jakoukoliv jinou proměnnou. Využití hodnoty zadané uvádí následující příklad:

#!/bin/sh
echo -n "Zadej své jméno: "
read jmeno

if [ -z "$jmeno" ]; then
  echo "Tys mi neřek tvé jméno!"
  exit
fi

echo "Zdravím tě $jmeno!"

Toto je typický příklad kontroly vstupních dat. Nemůžeme totiž předpokládat, že uživatel vždy zadá to, co očekáváme a je tedy nutné se proti tomu bránit. Je však vhodné uživatele na špatně zadané data upozorňovat citlivě a ne tak, aby si uživatel z toho vydedukoval, že si o něm myslíme, že je blbec. Jestliže uživatel nezadá žádné jméno, bude podmínka vyhodnocena jako true, vypíše se chybové hlášení a skript se ukončí.

8.12 Funkce

Funkce dělají skriptování mnohem jednodušší a překlednější. Rozdělují totiž program do jednodušších menších kousků. Funkce provádí vámi definované příkazy a také může vracet hodnotu. Funkce můžete volat z hlavního programu. Doteď jsme totiž psali všechno do hlavního programu. Funkce umožňují dílčí úkoly rozložit do menších částí kódu, které lze volat v programu, a to i vícekrát. Před pokračováním si uvedeme příklad funkce, která pouze vypíše text:

#!/bin/sh

hello()
{
  echo "Nyní jsme ve funkci hello()"
}

echo "Nyní se zavolá funkce hello()..."
hello
echo "Nyní jsme zase mimo funkci hello()"

Nyní se na funkci podíváme blíže. Funkce zde v našem konkrétním případě plní účel vypsání textu, který nás pouze informuje o tom, že je funkce prováděna. Funkce samozřejmě může dělat mnohem komplikovanější úlohy, ale právě zde jen vytiskne text. Funkci vždy voláme napsáním jejího jména do programu, jejíž jméno může být libovolné. V našem programu se jmenuje hello, a volá se:

hello

Když je tento řádek vykonáván, sh prohledá skript, zdali je v něm funkce hello(). Nalezne-li jí, tak provede příkazy, které jsou v ní zapsány.

Samotná funkce je určena svým názvem po kterém následuje dvojice kulatých závorek (). Blok příkazů ve funkci je uzavřen složenými závorkami {...}. Ty značí začátek a konec funkce. Veškerý kód funkce bude vykonán pouze, bude-li funkce zavolána. Funkce by také měla být vždy definována ještě před tím, než bude zavolána. Je též přípustné, že před názvem funkce může být slovo function. Funkce tedy vypadá takto:

hello()
{
  echo "Nyní jsme ve funkci hello()"
}

Nebo další možný zápis je:

function hello()
{
  echo "Nyní jsme ve funkci hello()"
}

Jak to tedy funguje?

Skript se začne normálně provádět od začátku, jak jsme zvyklí, takže nic nového. Když ale narazí na konstrukci [ 'function' ] hello (), ví, že se jedná o definici funkce jménem hello. Zapamatuje si, že hello odkazuje na funkci a pokračuje v provádění příkazů po odpovídající složené závorce. Jakmile narazí na řádek foo, vi, že má provést dříve definovanou funkci. Po skončení funkce se pokračuje na řádku, který následuje po volání funkce foo.

Dříve než funkci vyvoláte, ji vždy musíte nejprve definovat, podobně jako v jazyce Pascal, jen s tou výjimkou, že v shellu neexistuje žádná deklarace forward. To ale nebývá problém, protože se všechny skripty provádějí od začátku, takže pokud umístíte všechny funkce před první volání libovolné funkce, budou vždy všechny funkce před prvním volání definovány.

Při volání funkce jsou poziční parametry skriptu $*, $@, $#, $1, $2 atd. nahrazeny parametry funkce. Pomocí nich pak čtete parametry předané funkci. Po skončení funkce jsou hodnoty všech parametrů zase obnoveny. Tyto parametry se používají stejně jako proměnné.

Funkce mohou pomocí příkazu return vracet číselnou hodnotu. Pokud je potřeba vrátit řetězec, uloží se do proměnné, kterou lze po skončení funkce použít. Případně můžete řetězec vypsat příkazem echo a výsledek odchytit jako v následujícím příkladu:

#!/bin/sh

foo()
{
  echo Ahoj
}

...

result="$(hello)"

Uvnitř shellu můžete za pomoci klíčového slova local deklarovat lokální proměnné. Proměnná pak existuje jen v rámci dané funkce. Jinak může funkce přistupovat k jiným proměnným shellu, které jsou v podstatě globální. Pokud má lokální proměnná stejný název jako globální, pak tuto proměnnou potlačí, ale jen v rámci příslušné funkce. Abychom si to demonstrovali, ukážeme si následující příklad:

#!/bin/sh

vzorový_text="globální proměnná"

foo()
{
  local vzorový_text="lokální proměnná"

  echo "Provádí se funkce foo"
  echo $vzorový_text
}

echo "Začátek skriptu"
echo $vzorový_text

foo

echo "Konec skriptu"
echo $vzorový_text

exit 0

Pokud funkce neobsahuje příkaz return, vrací návratový kód posledního provedeného příkazu.

V následujícím příkladu si ukážeme způsob předávání parametrů funkci a také způsob jakým mohou funkce vracet hodnoty true a false.

#!/bin/sh

yes_or_no() {
  echo "Je tvé jméno $* ?"
  while true
  do
    echo -n "Napiš ano nebo ne: "
    read x
    case "$x" in
      a | ano ) return 0;;
      n | ne ) return 1;;
      * ) echo "Napiš ano nebo ne"
    esac
  done
}

echo "Původní parametr je $*"

if yes_or_no "$1"
then
  echo "Ahoj $1, pěkné jméno"
else
  echo "Nikdy"
fi
exit 0

Tento příklad funguje tak, že po spuštění skriptu je definována funkce yes_or_no, ale není provedena. V příkazu if provede skript funkci yes_or_no, které předá jako parametry zbytek řádku, kde zápis $1 nahradí prvním parametrem skriptu. Funkce pak pracuje s těmito parametry, které jsou nyní uloženy v pozičních proměnných $1, $2 atd. a vrátí hodnotu volajícímu příkazu. Příkaz if v závislosti na návratové hodnotě provede příslušní příkaz.

8.13 Zachytávání signálů

Signál je událost, kterou generuje systém UNIX jako odpověď na nějakou podmínku a po jehož přijetí může proces (skript) provést nějakou akci. Signály generují některé chybové podmínky, například porušení segmentů paměti, chyby procesoru v plovoucí čárce nebo neplatné instrukce. Jsou generovány shellem a ovladači terminálu, které vyvolají přerušení. Může je také jeden proces explicitně poslat druhému procesu a předat mu tak nějakou informaci, případně ovlivnit jeho chování. Ve všech těchto případech je programové rozhraní stejné. Signály mohou být generovány, odchytávány anebo (některé přinejmenším) ignorovány. Názvy signálů jsou definovány v hlavičkovém souboru signal.h. Všechny začínají zkratkou "SIG". Výpis signálů systému Unix je uveden níže.

Ve vašich skriptech je možné použít zabudovaný program trap, který dovede zachytávat signály ve vašem programu. Je to dobrá cesta, jak uhlazeně ukončit program. Například, máte-li spuštěný program, zmáčknutím kláves Ctrl-C pošlete aplikaci signál přerušení, který zlikviduje váš program. Příkaz trap může zachytit signál a dát tak šanci programu v pokračování. Nebo se zeptá uživatele, jestli má skončit. trap používá následující syntaxi:

trap akce signál

Kde akce je to, co bude vykonáno při doručení signálu aplikaci. Signál je název signálu, který bude-li doručen, tak se vykoná příslušná akce. Signály, které může aplikace dostat si můžete nechat například vypsat pomocí příkazu trap -l. Používáte-li signály ve vašem skriptu, musíte vynechat první tři písmena jeho názvu (obvykle je to SIG). Například je-li signál SIGINT, bude do skriptu zapsáno pouze INT. Můžete také místo názvů signálů používat jejich čísla. Například číselná hodnota SIGINT je 2. Nyní vyzkoušejte následující program:

#!/bin/sh

trap sorry INT

sorry()
{
  echo "Sorry, to jsem nechtěl!"
  sleep 3
}

for i in 10 9 8 7 6 5 4 3 2 1; do
  echo "$i sekund do selhání systému."
  sleep 1
done echo "Systém selhal!"

Nyní zkuste program spustit a zmáčknout Ctrl-C. To pošle skriptu signál SIGINT, který by měl přerušit program. Jelikož však signál bude zachycen a dojde ke zpracování funkce sorry(), která však program neukončí, takže bude běžet dál.

Chcete-li však, aby byl signál ignorován úplně, použijte jako akci "''". Chcete-li pak někde v programu již standardní zpracování signálu, použijte jako akci "-". Například:

# dojde-li signál, spustí funkci sorry()
trap sorry INT

# resetuje akci (nastavuje standardní chování SIGINT)
trap - INT

#nastavuje signál tak, aby byl ignorován
trap '' INT

Seznam signálů, které můžete v Unixu použít (manuálová stránka signal z sekce 7 manuálových stránek):

SIGNAL(7)         Unix - příručka Programátora         SIGNAL(7)

JMÉNO
       signal - seznam signálů

POPIS
       V  Unixu  jsou  podporovány  níže  uvedené signály. Čísla
       některých signálů jsou závislá  na  architektuře.  Nejprve
       jsou uvedeny signály popsané v normě POSIX.1.

       Signál    Hodnota    Akce   Poznámka
       --------------------------------------------------------------------------
       SIGHUP        1       A     "Hangup" - při zavěšení na řídícím terminálu
                                   nebo ukončení řídícího procesu.
       SIGINT        2       A     "Interrupt" - přerušení z klávesnice.
       SIGQUIT       3       A     "Quit" - ukončení z klávesnice.
       SIGILL        4       A     "Illegal Instruction" - neplatná instrukce.
       SIGABRT       6       C     "Abort" - ukončení funkcí abort(3)
       SIGFPE        8       C     "Floating point exception" - přetečení
                                   v pohyblivé řádové čárce.
       SIGKILL       9      AEF    "Kill" - signál pro nepodmíněné ukončení
                                   procesu.
       SIGSEGV      11       C     Odkaz na nepřípustnou adresu v paměti.
       SIGPIPE      13       A     "Broken pipe" - pokus o zápis do roury,
                                   kterou nemá žádný proces otevřenou pro čtení.
       SIGALRM      14       A     Signál od časovače, nastaveného funkcí
                                   alarm(1)
       SIGTERM      15       A     "Termination" - signál ukončení
       SIGUSR1   30,10,16    A     Signál 1 definovaný uživatelem
       SIGUSR2   31,12,17    A     Signál 2 definovaný uživatelem
       SIGCHLD   20,17,18    B     Zastavení nebo ukončení dětského procesu
       SIGCONT   19,18,25          Pokračování po zastavení
       SIGSTOP   17,19,23   DEF    Zastavení procesu
       SIGTSTP   18,20,24    D     Zastavení znakem "Stop" z terminálu
       SIGTTIN   21,21,26    D     čtení z terminálu v procesu běžícím na pozadí
       SIGTTOU   22,22,27    D     zápis na terminál v procesu běžícím na pozadí

       Následují ostatní signály:

       Signál      Hodnota    Akce   Poznámka
       --------------------------------------------------------------------------
       SIGTRAP        5        CG    Přerušení při ladění (trasování,breakpoint)
       SIGIOT         6        CG    IOT - synonymum signálu SIGABRT
       SIGEMT       7,-,7      G
       SIGBUS      10,7,10     AG    "Bus error" - pokus o přístup mimo
                                     mapovanou paměť
       SIGSYS      12,-,12     G     Nepřípustný parametr syst. volání (SVID)
       SIGSTKFLT    -,16,-     AG    Chyba zásobníku koprocesoru
       SIGURG      16,23,21    BG    Soket přijal data s příznakem Urgent
                                     (4.2 BSD)
       SIGIO       23,29,22    AG    Lze pokračovat ve vstupu/výstupu (4.2 BSD)
       SIGPOLL                 AG    Synonymum SIGIO (Systém V)
       SIGCLD       -,-,18     G     Synonymum SIGCHLD
       SIGXCPU     24,24,30    AG    Překročen limit času CPU (4.2 BSD)
       SIGXFSZ     25,25,31    AG    Překročen limit velikosti souboru (4.2 BSD)
       SIGVTALRM   26,26,28    AG    Virtuální časovač (4.2 BSD)
       SIGPROF     27,27,29    AG    Časovač používaný při profilování
       SIGPWR      29,30,19    AG    Výpadek napájení (Systém V)
       SIGINFO      29,-,-     G     Synonymum SIGPWR
       SIGLOST      -,-,-      AG    Zámek souboru byl ztracen
       SIGWINCH    28,28,20    BG    Změna velikosti okna (4.3 BSD, Sun)
       SIGUNUSED    -,31,-     AG    Nepoužívaný signál
       (Znak - ve sloupci "Hodnota" znamená, že se signál na dané
       architektuře nepoužívá. Jsou-li uvedeny tři hodnoty, první
       je  obvykle použita na procesorech Alpha a Sparc, druhá na
       i386 a PowerPC a poslední na procesorech MIPS. Výjimkou je
       signál č. 29, který na procesorech Alpha znamená SIGINFO /
       SIGPWE, ale na Sparcu SIGLOST.)

       Příznaky ve sloupci "Akce" mají následující význam:

       A      Tento signál standardně ukončí proces.

       B      Tento signál je standardně ignorován.

       C      Tento signál standardně způsobí výpis  paměti  pro­
              cesu (core dump).

       D      Tento  signál  standardně  pozastaví provádění pro­
              cesu.

       E      Tento signál nemůže být zachycen.

       F      Tento signál nemůže být ignorován.

       G      Tento signál není definován normou POSIX.1.

POZNÁMKA K PŘEKLADU
       U často se vyskytujících signálů byly ponechány ve sloupci
       "Poznámka"  i  původní  termíny.  Snáze  se  pak z hlášení
       shellu o ukončení procesu lokalizuje, kterým signálem  byl
       proces přerušen.

SPLŇUJE STANDARDY
       POSIX.1

CHYBY
       Signály  SIGIO a SIGLOST mají stejnou hodnotu.  SIGLOST se
       ve  zdrojových  textech  jádra  již  nepoužívá,  ale   při
       překladu určitých balíků software se stále předpokládá, že
       signál č.29 znamená SIGLOST.

DALŠÍ INFORMACE
       kill(1), kill(2), setitimer(2).

Unix 1.3.88              4. března 1997                        1

8.14 AND & OR

Viděli jsme již struktury, které řešili podmínky, ale podmínky můžeme ještě více zdokonalit. Chceme-li třeba, aby pro vykonání následujícího příkazu byly splněny dvě či více podmínek najednou, nebo aby byla splněna alespoň některá z několika podmínek, použijeme právě znaky || nebo && mezi jednotlivými podmínkami. Znaky || znamenají nebo (OR, logický součet). Pokud je alespoň jedna z podmínek oddělených těmito znaky true, je výsledek true. Naopak znaky && znamenají a (AND, logický součin). Zde musí být splněny všechny podmínky, aby výsledek byl true.

Zde je příklad použití:

#!/bin/sh
x=5
y=10
if [ "$x" -eq 5 ] && [ "$y -eq 10 ]; then
  echo "Obě podmínky jsou splněny"
else
  echo "Nejsou splněny obě podmínky"
fi

Podobné použití má i logický součet. Následující příklad to ilustruje:

#!/bin/sh
x=3
y=2
if [ "$x" -eq 5 ] || [ "$y -eq 2 ]; then
  echo "Alespoň jedna podmínka je splněna"
else
  echo "Není splněna žádná podmínka"
fi

Další využití konstrukcí AND a OR je v tzv. seznamech. Konstrukce seznamu AND umožňuje provádět sadu příkazů tím způsobem, že další příkaz je proveden jen v případě úspěšného provedení všech předchozích příkazů. Má následující syntaxi:

příkaz1 && příkaz2 && příkaz3 && ...

Začne se zleva, je proveden první příkaz a vrátí-li hodnotu true, provede se další příkaz vpravo od něj. Tak to pokračuje, dokud některý z příkazů nevrátí hodnotu false a tím provedení seznamu skončí. Konstrukce && testuje hodnotu předchozího příkazu.

Každý příkaz je prováděn nezávisle, takže v jednom seznamu můžeme smíchat mnoho různých příkazů, jak plyne z níže uvedeného skriptu. Seznam AND vrátí hodnotu true, když jsou všechny příkazy úspěšně provedeny, v opačném případě vrátí hodnu false.

V následujícím skriptu provedeme příkaz touch file_one (čímž kontrolujeme existenci souboru file_one a pokud neexistuje, tak ho vytvoříme) a potom odstraníme soubor file_two. Potom pomocí seznamu AND otestujeme existenci každého ze souboru a vypíšeme nějaký text.

#!/bin/sh

touch file_one
rm -f file_two

if [ -f file_one ] && echo "hello" && [ -f file_two ] && echo "there"
then
  echo -e "in if"
else
  echo -e "in else"
fi

exit 0

Výstup programu bude následující:

$ ./skript.sh
hello
in else

Jak to funguje?

Příkazy touhc a rm zajistí požadovaný stav souborů v aktuálním adresáři. Seznam && pak provede příkaz [ -f file_one ], který bude úspěšný, provede se příkaz echo. Ten bude také úspěšný (příkaz echo vždy vrací hodnotu true). Potom je proveden třetí test, [ -f file_two ], který selže, protože tento soubor neexistuje. Jelikož byl poslední příkaz neúspěšný, neprovede se poslední příkaz seznamu echo. Výsledek seznamu && je false, protože jeden z příkazů seznamu selhal, tim pádem se provede vštev else příkazu if.

Konstrukce seznamu OR umožnuje provádět skupinu příkazů, dokud jeden nevrátí hodnotu true, pak skončí. Syntaxe je následující:

příkaz1 || příkaz2 || příkaz3 || ...

Příkazy jsou prováděny zleva, jeden po druhém. Tak to pokračuje, dokud některý z příkazů nevrátí hodnotu true a tím provádění seznamu skončí.

Seznam || je podobný seznamu && s tím rozdílem, že nyní musí předchozí příkaz selhat.

Následující příklad je pouze modifikací příkladu minulého, liší se pouze v použítí seznamu OR místo seznamu AND a také nevytvoří soubor file_one. Průběh však bude díky této konstrukci jiný.

#!/bin/sh

rm -f file_one

if [ -f file_one ] || echo "hello" || echo "there"
then
  echo -e "in if"
else
  echo -e "in else"
fi

exit 0

Výstup programu bude následující:

$ ./skript.sh
hello
in if

Jak to funguje? První dva řádky jen nastavují soubory pro zbytek skriptu. První příkaz [ -f file_one ], selže, protože daný soubor neexistuje. pak je proveden příkaz echo. Ten vrátí hodnotu true a překvapivě již nejsou žádné další příkazy seznamu || provedeny. Příkaz if bude úspěšný, protože jeden z příkazů seznamu || (echo) vrátil hodnotu true.

Výsledek obou těchto konstrukcí je výsledkem posledního provedeného příkazu.

Seznam ne vyhodnocován podobným způsobem, jakým je v jazyce C vyhodnocováno víc podmínek. K určení výsledku je proveden jen minimální počet příkazů. Příkazy, které již výsledek nemohou ovlivnit, se neprovedou. Tomu říkáme tzv. zkrácené vyhodnocování.

Zkombinování těchto dvou konstrukcí dostáváme "ráj logiků". Vyzkoušejte:

[ -f file_one ] && příkaz pro true || příkaz pro false

Bude-li test úspěšný, provede se první příkaz, v opačném případě druhý. S těmito méně obvyklými seznamy je vždy lépe trošku experimentovat.

Pokud chcete použít víc příkazů na místě, kde je povolen příkaz jeden, například v seznamu AND nebo OR, můžete si pomoci složenými závorkami a vytvořit blok příkazů. V aplikaci, kterou si ukážeme dále, se například setkáte s následujícím kódem:

get_confirm && {
  grep -v "^${cdcatnum}," $tracks_file > temp_file
  mv $temp_file $tracks_file
  echo
  add_record_tracks
}

8.15 Použití argumentů

Když píšete do příkazové řádky název nějakého programu, většinou k němu připíšete i nějaké paramtery, které mění chování programu. Parametry také můžete napsat, když spouštíte nějaký skript. V této kapitole si ukážeme, jak tyto parametry ve skriptu použít.

Tyto argumenty jsou uloženy v proměnných. Proměnná $# uchovává počet argumentů předaných programu. Jednotlivé argumenty jsou uloženy v proměnných $0, $1, $2,... Je tedy vidět, že jsou uloženy v proměnných číslovaných od 0 do počtu parametrů, přičemž v proměnné $0 je uložen název programu. Argumenty programu předané jsou až v proměnných 1 a více. Parametrů může být celkem 9. Nyní bude následovat příklad, ve kterém se parametry využijí:

#!/bin/sh

if [ "$#" -ne 1 ]; then
  echo "Použití: $0 argument."
fi

echo "První a jediný argument předaný skriptu je $1"

Tento program očekává pouze jeden parametr, aby se spustil program. Jestliže bylo předáno více nebo méně než jeden parametr, vytiskne zprávu použití. Jestliže byl jeden argument předán, bude také vytištěn.

Zde by bylo vhodné si uvést některé proměnné prostředí

Pokud by se vyskytla situace, že skriptu bude předáno více než devět parametrů, samozřejmě existuje jednoduchý způsob, jak přistoupit i k dalším parametrům. Je to příkaz shift.

Pokud budete chtít přistoupit ke všem parametrům skriptu jako k jediné proměnné, použijte ke čtení parametrů proměnnou $*. Ta vrátí seznam všech parametrů uložených v jedné proměnné a oddělených prvním znakem uvedeným v proměnné IFS. Použijete-li však pro přístup proměnnou $@, vrátí vám totéž co $*, ale nepoužívá proměnnou prostředí IFS. Přesný rozdíl mezi $* a $@ specifikuje X/Open.

Některé proměnné prostředí
Název
Popis
$HOME Domovský adresář aktuálního uživatele
$PATH Seznam adresářů oddělený dvojtečkami, ve kterých se mají hledat příkazy
$PS1 Prompt příkazové řádky, obvykle $
$PS2 Druhý prompt, který se využívá při dodatečném vstupu, obvykle >
$IFS Oddělovač polí. Seznam znaků, které slouží k oddělování slov, když shell čte vstup, obvykle mezera, tabulátor a znak nový řádek
$0 Název skriptu shellu
$# Počet předaných parametrů
$$ ID procesu skriptu, které se často používá uvnitř shellu ke generování jedinečných názvů dočasných souborů, například /tmp/tmpfile_$$

8.16 Dočasné soubory

Často potřebujeme vytvořit dočasný soubor. Jak ale zajistit, že soubor námi vytvořený bude v systému jedinečný? Celkem jednoduše. Soubor vytvoříme pomocí příkazu touch. Zvolíme název a jako příponu použijeme znaky "$$". Tím zajistíme, že vytvoříme jedinečný soubor, protože příkaz touch nahradí znaky $$ číslem, které se v adresáři u tohoto souboru s největší pravděpodobností nevyskytuje, protože je to jedinečný ID identifikátor procesu. Zde je příklad vytvoření dočasného souboru:

$ touch hello
$ ls
hello
$ touch hello.$$
$ ls
hello     hello.689

A je to. Dočasný soubor je vytvořen.

8.17 Návratové hodnoty

Spousta programů vrací nějakou hodnotu, která závisí na tom, jak program skončí. Například podíváte-li se na manuálovou stránku příkazu grep, zjistíte, že příkaz vrátí 0 jestliže hledané nalezl a 1 jestli nenašel nic. Proč by jsme měli zjišťovat návratovou hodnotu programu? Pro různé důvody. Toto uvážení nechme na každém uživateli. Jedna z možností je například, chceme-li pomocí skriptu zjistit, zda-li je v systému uživatel "karel". Nahlédneme do souboru /etc/passwd a budeme pátrat po slovu karel. Příklad skriptu je zde:

#!/bin/sh

if grep "karel" /etc/passwd > /dev/null
then
  echo "Uživatel karel v systému existuje"
else
  echo "Uživatel karel v systému neexistuje"
fi

Nyní když spustíme skript, bude program grep vyhledávat v souboru /etc/passwd slovo karel. Nalezne-li, vrátí true a funkce if tedy vykoná výpis, že uživatel existuje. Další možnost skriptu, který bude vykonávat totéž si ukážeme nyní. Budeme však používat návrtovou hodnotu trochu jinak. Návratová hodnota se totiž uloží do speciální proměnné, která se jmenuje $?.

#!/bin/sh
# hledání karla a veškerý výstup je přesměrován do /dev/null:
grep "karel" > /dev/null 2>&1
# uloží návratovou hodnotu a podle ní vykoná:
if [ "$?" -eq 0 ]; then
    echo "Nenalezen."
    exit
else
    echo "Nalezen."
fi

Návratovou hodnotu může mít také funkce ve vašem skriptu. Toho se docílí přidáním řádku return a příslušné hodnoty. Když shell dorazí na tento řádek při vykonávání funkce, je funkce ukončena s návratovou hodnotu, která je stanovena jako parametr za slovem return. Malý příklad:

detekce_souboru()
{
# testuje, jestli soubor existuje:
    if [ -f /etc/passwd ]; then
    echo "Soubor hesel existuje"
    return 0
else
    echo "Soubor hesel nenalezen."
    return 1
}

# načte hodnotu z funkce detekce_souboru:

foo=detekce_souboru
# zjištění hodnoty:
if [ "$foo" -eq 0 ]; then
    echo "Soubor existuje"
    exit 0
else
    echo "Soubor neexistuje"
    exit 1
fi

Věřím, že kódu není až zas tak těžké porozumět. Nakonec je důležité si uvědomit, že hodnota true představuje číslo 0 a hodnota false představuje číslo 1.

8.18 Příklady

Vytvoření nového uživatele

První z příkladů je vcelku jednoduchý skript, pomocí kterého lze do systému přidat nového uživatele. Tedy pouze v případě, že jste přihlášen jako uživatel root. Za předpokladu, že ne Vás skript hned při kontrole na začátku skriptu s tímto faktem obeznámí a skript ukončí pro nedostatečná práva.

addusr.shSoubor se skriptem

Hádání čísel

Druhý příklad skriptu sh už představuje poněkud komplexnější program. Skript si vymyslí nějaké náhodné číslo pomocí systémové proměnné RANDOM od 0 do 9. Pak bude na vás, aby jste toto číslo uhodli.

hadani.shSoubor se skriptem

8.19 Příkaz break

Tento příkaz slouží k odskoku z cyklu for, while nebo until ještě před splněním řídicí podmínky. Příkazu break můžete předat číselný parametr, který udává, kolik cyklů má přerušit. Tím ale můžete hodně znepříjemnit čtení skriptů, takže vám jeho použití nedoporučuji. Příkaz break implicitně zruší jednu úroveň cyklu. Zde je příklad:

#!/bin/sh

rm -rf fred*
echo > fred 1
echo > fred 2
mkdir fred3
echo > fred4

for file in fred*
do
  if [ -d "$file" ]; then
    break;
  fi
done

echo "První adresář fred byl $file"

rm -rf fred*

exit 0

8.20 Příkaz :

Příkaz dvojtečka je nulový příkaz. Příležitostně se používá ke zjednodušení logiky podmínek, kde zastupuje hodnotu true. Protože se jedná o zabudovaný příkaz, běží rychleji než příkaz true, i když je mnohem méně čitelný.

Můžete se s ním setkat v podmínce cyklů while. Zápis while : znamená nekonečný cyklus, který se ale běžněji zapisuje jako while true.

Kostrukce : je užitečná při podmíněném nastavování proměnných. Například:

: ${var:=value}

Pokud bychom neuvedli znak:, snažil by se shell vyhodnotit proměnnou $var jako příkaz.

V některých, zejména starších skriptech se můžete setkat s použitím dvojtečky na začátku řádku, kde uvádí komentář, ale moderní skripty by měly na začátku řádku komentáře vždy uvádět znak #, protože je to efektivnější.

Na závěr příklad:

#!/bin/sh

rm -rf fred
if [ -f fred ]; then
  :
else
  echo "Soubor fred neexistuje."
fi

exit 0

8.21 Příkaz continue

Podobně jako stejnojnenný příkaz jazyka C spustí tento příkaz další iteraci (průchod) cyklů for, while nebo until, přičemž je proměnné cyklu přiřazena další hodnota ze seznamu. Příklad:

#!/bin/sh

rm -rf fred*
echo > fred1
echo > fred2
mkdir fred 3
echo > fred4

for file in fred*
do
  if [ -d "$file" ]; then
    echo "přeskakuji adresář $file"
    continue
  fi
  echo soubor je $file
done

rm -rf fred*
exit 0

Příkaz continue může mít nepovinný parametr, který je číslo cyklu, jímž se má pokračovat, takže můžete částečně vyskočit z vnořených cyklů. Tento parametr je však jen zřídka používán, protože často velice stíží pochopení skriptů. Například skript:

#!/bin/sh

for x in 1 2 3
do
  echo before $x
  continue 1
  echo after $x
done

exit 0

vrátí následující výstup:

before 1
before 2
before 3

8.22 Příkaz .

Příkaz tečka provede příkaz v aktuálním shellu:

$. ./shell_script

Normálně, když skript provádí externí příkaz nebo skript, je vytvořeno nové prostředí (podřízený shell), příkaz je proveden v tomto novém prostředí a prostředí je pak zrušeno, s výjimkou návratového kódu, který je vrácen rodičovskému shellu. Nicméně externí příkazy source a tečka (další dvě synonyma) způsobí spuštění příkazu uvedených ve skripto v rámci stejného shellu, který skript vyvolal.

To znamená, že normálně se změny v proměnných přostředí, které program provede, ztratí. Příkaz tečka naproti tomu umožňuje prováděnému příkazu změnit aktuální prostředí. To je nezřídka užitečné, když poutíváte skript jako "obal" pro nastavení prostředí pro pozdější provedení nějakého příkazu. Když například pracujete současně na několika různých projektech, zjistíte, že potřebujete spouštět příkazy s různými parametry, třeba kvůli spuštění starší verze kompilátoru při úpravě starého programu.

Ve skriptech shellu funguje příkaz tečka jako direktiva #include jazyka C nebo C++. Ačkoli tento příkaz skript nikam "nezahrne", provede příkaz v aktuálním kontextu, takže ho můžete použít k začlenění definic proměnných a funkcí do skriptu.

8.23 Příkaz eval

Příkaz eval umožňuje vyhodnocovat argumenty. je zabudován do shellu a normálně neexistuje jako samostatný příka. Asi nejlépe jeho použití demonstruje následující příklad, který je přímo ze specifikace X/Open:

foo=10
x=foo
y='$'$x
echo $y

Tento skript vrátí $foo, ale následující

foo=10
x=foo
eval y='$'$x
echo $y

již vrátí 10. Tudíž příkaz eval funguje trochu jako další znak $ - vrátí hodnotu hodnoty proměnné.

Příkaz eval je velmi užitečný, protože umožňuje generování a spouštění kódu za běhu. Komplikuje ladění skriptů, ale umožňuje provádět věci, které bby normálně byly obtížné, ne-li nemožné.

8.24 Příkaz exec

Příkaz exec má dvě různá použití. Normálně slouží k nahrazování aktuálního shellu jiným programem. Například následující řádek

exec wall "Děkuji za všechny ryby"

ve skriptu nahradí aktuální shell příkazen wall. Řádky, které ve skriptu následují po příkazu exec, nebudou provedeny, protže shell, který skript prováděl, již neexistuje.

Druhý způsob použití příkazu exec umožňuje upravit deskriptory souboru:

exec 3< soubor

Výše uvedený zápis způsobí, že deskriptor tři bude otevřen pro čtení ze souboru soubor. Používá se jen zřídka.

8.25 Příkaz exit n

Příkaz exit způsobí ukončení skriptu s návratovým kódem n. Pokud ho použijete v příkazovém řádku kteréhokoliv interaktivního shellu, pak vás tento příkaz odhlásí. Pokud váš skript skončí, aniž by specifikoval nějaký návratový kód, použije se jako návratorý kód stav posledního příkazu provedeného ve skriptu. Vždy je dobré nějaký návratový kód uvést.

Při programování skriptů shellu znamená 0 úspěšné ukončení, 1 až 125 včetně pak chybové kódy, které mohou skripty využívat. Zbylé hodnoty jsou rezervovány: 126 - soubor nebyl spustitelný, 127 - příkaz nenalezen a 128 a vyšší - objevil se nějaký signál.

Toto je jednoduchý příklad, který vrátí nulu, pokud v aktuálním adresáři existuje soubor .profile.

#!/bin/sh

if [ -f .profile ]; then
  exit 0
fi

exit 1

Pokud si libujete v hutných skriptech, můžete tento skript přepsat za pomoci seznamů AND a OR.

[ =f .profile ] && exit 0 || exit 1

8.26 Příkaz export

Příkaz export zpřístupní proměnnou, kterou mu předáte jako parametr, podřízeným shellům. Proměnné vytvořené v shellu implicitně nejsou přístupné dalším (podřízeným) shellům vyvolaným z tohoto skriptu. Příkaz export vytvoří proměnnou prostředí předanou jako parametr, která pak bude přístupná jiným skriptům, a programům vyvolaným z tohoto programu. Odborně řečeno, exportované proměnné vytvoří proměnné prostředí v libovolném dceřiném procesu odvozeném od tohoto shellu. To nejlépe ilustrují dva skripty nazvané export1 a export2.

Nejprve skript export2:

#!/bin/sh

echo "$foo"
echo "$bar"

Nyní skript export1. Na konci skriptu voláme skript export2.

#!/bin/sh

foo="The first meta-syntactic variable"
export bar="The second meta-syntatic variable"

./export2

Spustíme-li skript export1, dostaneme následující výstup:

$ export1

The second meta-syntatic variable
$

První řádek je prázdný, protože proměnná foo nebyla ve skriptu export2 dostupná, takže byla vyhodnocena jako prázdná. Předáme-li příkazu echo prázdnou proměnnou, vypíše jen znak nový řádek.

Jakmile byla proměnná exportována z shellu, bude exportována do všech skriptů spuštěných z tohoto shellu a také do všech dalších shellů, které z něj budou spuštěny. Pokud by skript export2 volal jiný skript, také on by měl k dispozici hodnotu proměnné bar.

Příkazy set -a nebo set -allexport exportují všechny nově vytvořené proměnné.

8.27 Příkaz printf

Příkaz printf je dostupný jen v moderních shellech (je dostupný v sh). Podle specifikace X/Open byste mu při vytváření formátovaného výstupu měli dávat přednosti před příkazem echo. Jeho syntaxe je:

printf "formátovací řetězec" parametr1 parametr2 ...

Formátovací řetězec je (s určitými omezeními) podobný formátovacímu řetězci v jazyce C či C++. Především nejsou podporovány reálné proměnné, přotože veškerá aritmetika v shellu probíhá v oboru celých čísel. Formátovací řetězec je tvořen libovolnou kombinací písme, řídících sekvencí a konverzních specifikátorů. Všechny jiné znaky než % a \ se na výstupu objeví v původní podobě.

Jsou podporovány následující řídící sekvence:

Řídící sekvence
Popis
\\ Znak zpětné lomítko
\a Varování (pípnutí)
\b Znak zpětné lomítko
\b Znak vysunutí stránky
\n Znak nový řádek
\r Návrat vozíku
\t Znak tabulátor
\v Znak vertikální tabulátor
\ooo Znak, jehož osmičková hodnota je ooo.

Konverzní specifikátor je o něco složitější, takže zde uvedeme jen běžně používané parametry. Další podrobnosti najdete v manuálu. Konverzní specifikátor je tvořen znakem % následovaným konverzním znakem. Základní konverze jsou tyto:

Konverzní specifikátor
Popis
d Vypíše desítkové číslo
c Vypíše znak
s Vypíše řetězec
% Vypíše znak %

Formátovací řetězec je pak použit k interpretaci zbylých paramterů a výstupu výsledku. Například:

$ printf "%s\n" hello
hello
$ printf "%s %d\t%s" "Hi there" 15 people
Hi There 15    people

8.28 Příkaz set

Příkaz set nastavuje proměnné shellu. Může to být způsob, jak využít pole v příkazech, které předávají na výstup hodnoty oddělené mezerami.

Dejeme tomu, že chceme v shellu použít název aktuálního měsíce. Systém nabízí příkaz date, který vrací řetězec obsahující měsíc, ale my chceme tento měsíc získat odděleně od ostatních polí. Můžeme to udělat zkombinováním konstrukce $(...), pomocí které provedeme příkaz date a získáme výsledek (na který se za chvíli podíváme podrobněji) s příkazem set. Měsíc tvoří ve výstupu příkazu date druhou položku:

#!/bin/sh

echo Datum je $(date)
set $(date)
echo Měsíc je $2

exit 0

Tento program nastavuje seznam parametrů podle výstupu příkazu date a z pozičních parametrů $2 pak získá požadovaný měsíc.

Všiměte si, že jsme na příkazu date demonstrovali způsob nastavení pozičních parametrů. Protože výstup příkazu date závisí na místním jazyce, ve skutečnosti bychom název měsíce získali pomocí zápisu date +%B. Příkaz date má mnoho dalších parametrů, o kterých se dozvíte víc v manuálových stránkách.

Pomocí příkazu set a příslušných parametrů můžeme také řídit chování shellu. Nejčastěji se používá set -x, který způsobí, že skript vždy vypíše název právě prováděného příkazu.

8.29 Příkaz unset

Příkaz unset odstraní z prostředí proměnnou nebo funci. Netýká se to ale proměnných určených jen pro čtení, které definoval sám shell, jako například IFS. Příliš často se nepoužívá.

Skript

#!/bin/sh

foo="Hello world"
echo $foo

unset foo
echo $foo

vypíše nejdříve pozdrav Hello world a potom prázdný řádek.

Zápis foo= má podobný efekt jako příkaz unset ve výše uvedeném příkladu, ale nastavení nulové hodnoty řetězce nemá stejný účinek jako odstranění proměnné foo z prostředí.

8.30 Příkaz shift

Příkaz shift způsobí posun všech pozičních parametrů, takže z pozičního parametru $2 se stane $1, z $3 bude $2 atd. Hodnota $1 zmizí, zatímco hodnota parametru $0 bude zachována. Pokud příkazu shift předáte číselný parametr, posunou se poziční parametry o daný počet míst. Ostatní proměnné $*, $@, $# jsou tímto příkazem také ovlivněny.

Příkaz shift je vhodný pro procházení parametrů a pokud váš skript vyžaduje deset nebo více parametrů, budete ho potřebovat pro přístup k desátému a vyššímu parametru.

Jako příklad si můžeme ukázat sktipt, který projde všechny poziční parametry:

#!/bin/sh

while [ "$1" != "" ]; do
  echo "$1"
  shift
done

exit 0

8.31 Ladění skriptů

Ladění skriptu shellu je většinou poměrně jednoduché, ale také nemáme k dispozici žádné nástroje. V rychlosti si shrneme běžné metody ladění.

Když dojde k chybě, shell normálně vytiskne číslo řádku, na kterém se vyskytla chyba. Není-li chyba na první pohled zřejmá, můžeme pomocí dodatečných příkazůecho zobrazit obsah proměnných a otestovat fragmenty kódu tak, že je interaktivně zapíšeme přímo v shellu.

Protože jsou skripty interpretovány, nedochází při úpravách a zkoušení k žádnému zdržení při překladu.

Základní způsob vystopování těch nejkomplikovanějších chyb spočívá v nastavení různých voleb shellu. K romu můžete využít buď volby příkazového řádku při spouštění shellu anebo příkaz set. Tyto volby shrnuje následující tabulka.

Volba příkazového řádku
Volba příkazu set Popis
sh -a <skript> set -o noexec
set -a
Kontroluje jen syntaktické chyby; neprovádí příkazy.
sh -v <skript> set -o verbose
set -v
Před zpracováním příkazy vypíše.
sh -x <skript> set -o xtrace
set -x
Po zpracování přízaky vypíše
  set -o nouset
set -u
V případě použití nedefinované proměnné vrátí zprávu o chybě.
Volby příkaz set můžete zapnout zápisem -o a vypnout +o. Podobně to platí i pro zkrácené zápisy.

Volba xtrace nabízí jednoduché sledování provádění skriptu. Při první zkoušce můžete použít volbu příkazové řádky, ale pokud budete provádět jemnější ladění, můžete příznaky xtrace (zapínající a vypínající vypisování příkazů) zařadit do kódu kolem problematické části. Při sledovaném provádění shell vytiskne každý řádek skriptu s expandovanými proměnnými ještě před jeho provedením. Stupeň expanze určuje (implicitně) počet znaků + na začátku každého řádku. Znak + můžete změnit na jiný pomocí proměnné shellu PS4, která se nastavuje v konfiguračním souboru shellu.

V shellu můžete také odchytáváním signálu EXIT zjišťovat stav programu při jeho ukončení. Dosáhnete toho, když na začátek skriptu umístíte například následující řádek:

tran 'echo Exitting: critical variable = $critical_variable' EXIT

8.32 Skripty v C-shellu

Skripty mohou být psány jak v shellech vycházejících z Bourne shellu, tak v shellech vycházejících z C-shellu. C-shell a jeho následovníci však nejsou považovány za příliš vhodné pro psaní skriptů, protože obsahují mnoho chyb, které znemožňují používání některých složitějších konstrukcí, a některé poměrně často používané záležitosti neumožňují zapsat vůbec (přesměrování standardního chybového výstupu do souboru, atd.). Některé chyby byly v nových verzích shellů odstraněny, jiné však vyplývají z nevhodného návrhu syntaxe C-shellu. Na druhou stranu shelly vycházející z csh mají mnoho příjemných vlastností pro interaktivní práci. Mnoho zkušených uživatelů Unixu proto používá jako interaktivní shell například tcsh, ale skripty píše v Bourne shellu. Programování v csh je však nutné ovládat také, pro psaní startovacích skriptů pro csh a jeho následovníky.

Syntaxe některých příkazů je pozměněna, aby odpovídala syntaxi jazyka C.

Skript suma n

suma.cshSoubor se skriptem

Výpis adresáře, ls

mmdir.cshSoubor se skriptem

Počet souborů v adresáři a všech podadresářích

mndir.cshSoubor se skriptem

Strom podadresářů

mtree.cshSoubor se skriptem


Další Předchozí Obsah