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.
#!/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
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.
#!/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í.
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.
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 |
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í.
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
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 for
se 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 do
a 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.
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] )
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`."
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 | |
---|---|
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
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čí.
#!/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.
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
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
}
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í | |
---|---|
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_$$ |
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.
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.
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.sh | Soubor se skriptem |
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.sh | Soubor se skriptem |
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
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
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
$. ./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.
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é.
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.
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
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é.
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:
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:
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
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.
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í.
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
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ří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ě. |
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
Syntaxe některých příkazů je pozměněna, aby odpovídala syntaxi jazyka C.
if (výraz) jednoduchý příkaz
if (výraz) then
příkazy
endif
if (výraz) then
příkazy
else
příkazy
endif
if (výraz) then
příkazy
else if
(výraz) then
příkazy
...
else
příkazy
endif
switch (testovací řetězec)
case vzor:
příkazy
breaksw
...
default:
příkazy
breaksw
endsw
foreach indentifikátor (seznam argumentů)
příkazy
end
while (testovací příkaz)
příkazy
end
suma.csh | Soubor se skriptem |
mmdir.csh | Soubor se skriptem |
mndir.csh | Soubor se skriptem |
mtree.csh | Soubor se skriptem |