Základy skriptovacího jazyka BASH
Podklady pro výuku předmětu Operační systémy.
Bash (Bourne Again SHell) je interpret příkazů (shell) projektu GNU kompatibilní se starším Bourne shell-em (sh), přidávající užitečné funkce převzaté z Korn shellu (ksh) a C shellu (csh). Shell představuje základní rozhraní pro komunikaci uživatele s operačním systémem počítače. Jeho základní funkcí je spouštění zadaných příkazů a programů, navíc poskytuje řadu vlastních příkazů umožňujících efektivní práci se systémem. Bash je tedy možné používat:
- interaktivně (realizuje příkazovou řádku operačního systému),
- jako interpret skriptů.
V interaktivním režimu čte Bash příkazy ze standardního vstupu (od uživatele) místo ze souboru, jinak je jeho chování v obou režimech stejné. Skript v jazyce bash je textový soubor obsahující příkazy jazyka bash, oddělené novou řádkou, nebo středníkem.
Na první řádku skriptu je vhodné napsat hlavičku, tzv. “shebang”, která umožní jádru OS rozpoznat, že se jedná o skript, a jakým programem má být skript interpretován. Na rozdíl od Windows totiž UNIXové OS (např. Linux) při spouštění skriptu ignorují příponu souboru a řídí se pouze hlavičkou. V UNIXových OS bude mít hlavička tvar
#!/usr/bin/env bash
Znaky #!
na začátku první řádky (POZOR, opravdu to musí být první řádka, tzn.
před hlavičkou nesmí být ani prázdná řádka) říkají, že se jedná o skript, za
nimi následuje plná cesta k interpretu příkazů. Protože cesta k interpretu bash
se mezi různými systémy i distribucemi Linuxu liší (např. ve FreeBSD to může být
/usr/local/bin/bash
), je vhodnější místo cesty přímo k interpretu (tedy typicky
/bin/bash
) v hlavičce použít program /usr/bin/env
s argumentem bash
,
který nalezne interpret bash tak, jak by ho při zadání příkazu našel shell,
pomocí proměnné prostředí PATH
.
Hlavička je důležitá v případě, že skript spouštíme pouze zadáním jeho cesty
(skript musí být v tomto případě nastaven jako spustitelný, třeba pomocí příkazu
chmod +x
). Pokud spouštíme skript jako argument příkazu bash
, např.
/bin/bash ./muj_skript.sh
hlavička není pro spuštění nezbytná. Přesto je vhodné ji do souboru uvést pro pozdější jednoduchou identifikaci, o jaký soubor se jedná.
Základní funkcí shellu je spouštění externích programů a skriptů, přičemž
syntaxe je absolutní nebo relativní cesta k souboru obsahujícímu binární kód,
nebo jiný skript. Příkazy se oddělují koncem řádky, nebo středníkem. Znakem #
je možné do kódu vkládat komentáře – cokoliv za tímto znakem interpret
ignoruje.
Pokud budeme chtít od bashe něco víc, budeme potřebovat proměnné.
Proměnné
Bash nemá datové typy proměnných – resp. s obsahem proměnné se vždy pracuje
jako s řetězcem. Proměnná je identifikována názvem, který se může skládat z
malých a velkých písmen anglické abecedy, podtržítek a číslic, přičemž nesmí
číslicí začínat. V názvech proměnných záleží na velikosti písmen, což znamená,
že promenna
a PROMENNA
jsou dvě různé proměnné.
Přiřazení hodnoty proměnné se provádí operátorem =
, např.
A=24
přiřadí do proměnné A
řetězec “24
”. Při přiřazování je důležité myslet na
to, že před operátorem =
ani za ním nesmí být mezera, jinak je příkaz
interpretován chybně:
A =24 # pokus o spuštění příkazu "A" s argumentem "=24"
A= 24 # pokus o spuštění příkazu "24" s přednastavenou hodnotou proměnné "A" na prázdný řetězec (viz export níže)
A = 24 # pokus o spuštění příkazu "A" se dvěma parametry "=" a "24"
Pokud potřebujeme pracovat s hodnotou proměnné (předat ji jako parametr, nebo přiřadit jako hodnotu do jiné proměnné), musíme proměnnou zapsat se znakem dolaru, tedy např.
echo $A # výpis obsahu proměnné na standardní výstup
Alternativní způsob zápisu využívá složené závorky kolem názvu proměnné:
echo ${A}
To je výhodné, pokud např. bezprostředně za hodnotou proměnné následují znaky, které by jinak byly považovány za součást názvu proměnné:
echo x$Ax # vytiskne znak "x" + hodnotu proměnné "Ax"
echo x${A}x # vytiskne znak "x" + hodnotu proměnné "A" + znak "x"
Pole
Kromě obyčejných proměnných umožňuje bash pracovat s poli. Pole vytvoříme buď deklarací příkazem
declare -a POLE
který vytvoří prázdné pole, nebo přímo přiřazením prvků pole do proměnné. Prvky pole se zapisují do kulatých závorek:
PRAZDNE_POLE=() # vytvoření prázdného pole v proměnné PRAZDNE_POLE
BARVY=(cervena zelena modra) # vytvoření pole se třemi prvky "cervena", "zelena" a "modra" v proměnné BARVY
Při přístupu k prvkům pole se používá zápis
${BARVY[0]} # vrátí první položku pole
přičemž v hranatých závorkách je číselný index položky, číslování je od 0. Pokud bychom na index a hranaté závorky zapomněli, dostaneme první prvek pole. Následující zápisy nám umožní zjistit délku pole (počet položek), nebo získat všechny položky pole najednou:
${#BARVY[*]} # vrátí počet položek pole BARVY
${BARVY[*]} # vrátí všechny položky pole BARVY jako jeden řetězec, ve kterém jsou položky pole vzájemně odděleny mezerou
${BARVY[@]} # vrátí všechny položky pole BARVY samostatně (pro správnou funkci je potřeba správně použít uvozovky)
Přidávat prvky k existujícímu poli můžeme např. operátorem +=
:
BARVY+=(cerna bila)
export
Viditelnost proměnné v bashi je omezena na instanci interpretu vykonávajícího aktuální skript. Pokud chceme hodnotu proměnné přenést do synovského procesu (pokud spouštíme z jednoho skriptu jiný program nebo skript), můžeme použít příkaz
export PROMENNA
kterým říkáme, že se má proměnná zkopírovat do proměnných spuštěných procesů. V běžných programech spuštěných z aktuálního skriptu jsou takovéto proměnné viditelné jako “proměnné prostředí”.
Pokud je přiřazení proměnné uvedeno před příkazem, je proměnná “exportována” automaticky, ale jen pro spouštěný příkaz:
A=1
A=2 bash -c 'echo $A' # vypíše 2
echo $A # vypíše 1
Uvozovky
Řetězce v bashi je důrazně doporučeno uzavírat do uvozovek, ačkoliv to syntaxe jazyka striktně nevyžaduje. Uvozovky pomohou především v případech, kdy obsah proměnné obsahuje mezery nebo některé znaky se zvláštní interpretací. Podívejme se na příklad:
A="text s mezerami"
program $A
program "$A"
Pokud bychom vynechali uvozovky u přiřazení do proměnné A
, obdržíme zřejmě
chybové hlášení o neznámém příkazu “s
” – interpret nemá jak odlišit zda se
jedná o pokračování řetězce, nebo příkaz který chcete spustit s přednastavenou
proměnnou A
na hodnotu “text”. Druhá řádka se interpretuje jako program text
s mezerami což znamená, že spuštěný program obdrží tři samostatné argumenty
“text”, “s” a “mezerami”, což zřejmě není náš záměr. Třetí řádek pracuje tak,
jak požadujeme, tj. předá programu jediný argument bez ohledu na jeho obsah.
Pokud budu chtít předat celé pole jako parametry spuštěného programu (každý prvek jako samostatný argument), musím výraz uzavřít do uvozovek:
program "${POLE[@]}"
jinak mezery v řetězcích způsobí rozdělení řetězce na více samostatných argumentů.
Bash umožňuje uzavírat řetězce kromě “dvojitých uvozovek” také do ‘apostrofů’, přičemž základní rozdíl mezi nimi je ten, že uvnitř apostrofů se neprovádí překlad proměnných na jejich hodnoty, tedy např.
echo "Obsah proměnné je $A"
vytiskne hodnotu proměnné, ale
echo 'promenna $A'
vytiskne řetězec “promenna $A
”. Uvozovky a apostrofy můžeme jinak s výhodou
zaměňovat, pokud potřebujeme vytvořit řetězec obsahující právě apostrof nebo
uvozovky.
Doporučení: zvykněte si vždy uzavírat hodnoty proměnné do uvozovek. Vyhnete se tak neočekávanému chování skriptu a chyb, jejichž důvod nemusí být na první pohled zřejmý.
Kromě uvozovek a apostrofů umožňuje syntaxe jazyka použití obrácených apostrofů
, které však neslouží k definici řetězce, ale řetězec uvnitř
apostrofů se interpretuje jako příkaz který je vykonán a standardní výstup
tohoto příkazu je vložen na místo těchto uvozovek, např.
ADRESAR=`pwd` # spustí příkaz "pwd" (print working directory) a výstup (cestu k aktuálnímu pracovnímu adresáři) uloží do proměnné ADRESAR
Stejného výsledku je možné dosáhnout i alternativním zápisem bez obrácených apostrofů:
ADRESAR=$(pwd)
Výhoda tohoto zápisu je, že lze více nahrazení vkládat do sebe. Např:
SOUBORY=$(ls $(pwd))
Pokud budete chtít uložit jednotlivá slova ve výstupu příkazu pole, můžete použít zápis
SOUBORY=( `ls` ) # spustí příkaz "ls" a každé slovo v jeho výstupu uloží do jednoho prvku pole SOUBORY (pozor, v případě mezer v názvech souborů nemusí příkaz fungovat jak očekáváte)
Zpracování argumentů
Často požadujeme parametrizaci chování skriptu prostřednictvím argumentů zadaných při spuštění. V některých případech jsou obsahem argumentů zpracovávaná data. Bash umožňuje číst jednotlivé argumenty prostřednictvím speciálních proměnných
$1 # hodnota 1. argumentu
$2 # hodnota 2. argumentu
$3 # hodnota 3. argumentu
atd...
V proměnné $0
je potom uložen příkaz, kterým byl skript spuštěn (cesta ke
skriptu). Proměnné $*
a $@
nám umožní pracovat se všemi parametry buď
najednou, jako s jedním řetězcem, ve kterém jsou argumenty odděleny mezerou,
nebo se seznamem samostatných argumentů (rozdíl mezi *
a @
je stejný jako u
práce s celým polem). V proměnné $#
najdeme počet zadaných argumentů.
Uvedený způsob můžeme použít pouze pro argumenty 1-9, pro přečtení dalších
argumentů je nutné použít zápis se složenými závorkami ${11}
, ${12}
, atd.
Velmi často však požadujeme možnost proměnlivého počtu parametrů skriptu, které
nemusí mít pevné pořadí (např. skript pracující se skupinou zadaných souborů,
kterých může být libovolný počet). V takovém případě s výhodou využijeme příkaz
shift
, který odstraní 1. argument a všechny následující posune o jedno místo
dopředu (obsahem proměnné $1
pak bude hodnota 2. argumentu). S využitím cyklů
a opakovaného volání příkazu shift
potom můžeme postupně zpracovat libovolný
počet parametrů. Příkaz shift
ovlivňuje i hodnoty proměnných $#
a $*
,
resp. $@
.
Speciální proměnné
Bash nám nabízí několik speciálních proměnných, které nám umožňují získat užitečné informace, nebo ovlivňovat chování interpretu.
Kromě proměnných pro přístup k argumentům je to proměnná $?
, která obsahuje
návratovou hodnotu posledního spuštěného příkazu. V případě, že poslední příkaz
obsahoval roury a spouštěl tedy více paralelních procesů, obsahuje proměnná
$?
návratovou hodnotu posledního příkazu v kaskádě. Návratové hodnoty
předchozích příkazů můžeme získat z proměnné PIPESTATUS
, která obsahuje pole
návratových hodnot jednotlivých procesů v kaskádě.
Další užitečnou proměnnou je $PWD
, která obsahuje cestu k aktuálnímu
pracovnímu adresáři (nezaměňovat s příkazem pwd
).
Velmi užitečnou proměnnou je $IFS
(internal field separator), která ovlivňuje
rozdělování a spojování řetězců při mnoha různých operacích. Obsahem proměnné
$IFS
je seznam znaků, které jsou považovány za oddělovače. V případě volání
příkazu, který rozděluje vstupní řetězec na pole nebo seznam argumentů je tento
řetězec rozdělován v místech výskytu těchto oddělovačů. V případě spojování
více řetězců do jednoho jsou spojené řetězce odděleny prvním znakem této
proměnné (např. při použití výrazu $*
nebo ${POLE[*]}
). Výchozí nastavení
proměnné $IFS
obsahuje 3 znaky: mezeru, tabulátor a konec řádku. Pokud tedy
například budeme číst seznam souborů z výstupu programu ls
a ukládat je do
položek pole, je vhodné předem nastavit proměnnou pouze na konec řádku, čímž
zabráníme rozdělení souborů s mezerami v názvu na více položek:
OLD_IFS=$IFS # uschování původního nastavení IFS
IFS=$'\n' # tento zápis uloží do proměnné IFS znak "new-line"
SOUBORY=( `ls` ) # přečtení názvů souborů v aktuálním adresáři do pole "SOUBORY"
IFS=$OLD_IFS # je rozumné nastavit proměnnou IFS na původní nastavení s ohledem na chování ostatních příkazů
Globy (wildcards)
Při práci se soubory, zejména v interaktivním uživatelském režimu, často oceníme tzv. globy – vzory umožňující jednoduše vytvořit seznamy souborů s podobnými názvy. Tyto vzory mohou obsahovat následující znaky:
*
(hvězdička) – reprezentuje libovolný řetězec?
(otazník) – reprezentuje jeden libovolný znak[...]
(seznam znaků v hranatých závorkách) – reprezentuje jeden znak ze zadané množiny
V případě že interpret bashe narazí na řetězec obsahující tyto znaky, jsou nalezeny všechny soubory, jejichž názvy odpovídají danému výrazu a seznam názvů těchto souborů je vložen na místo původního výrazu. Pozor, toto nahrazení se používá pouze v případě, že uvedený výraz NENÍ uzavřen v žádných uvozovkách – v opačném případě se výraz použije jako obyčejný řetězec tak jak je. Z toho je zřejmé že použití globů uvnitř skriptu nemusí být rozumné s ohledem na možné nežádoucí chování, bude-li skript pracovat se soubory obsahujícími mezery.
Vstup a výstup
Běžící skript má, stejně jako každý jiný proces, k dispozici svůj standardní
vstup, výstup a chybový výstup, jejichž prostřednictvím si vyměňuje data s
jinými procesy nebo uživatelem. Nejjednodušší způsob výpisu řetězce na
standardní výstup umožňuje příkaz echo
. Častěji však využíváme výstupu jiných
programů spuštěných z našeho skriptu – pokud jejich výstup explicitně
nepřesměrujeme, je automaticky napojen na výstup procesu běžícího skriptu.
Výpisu na chybový výstup docílíme prostým přesměrováním výstupu příkazu echo
(nebo libovolného jiného příkazu):
echo "Chybove hlaseni" >&2
Pro čtení jedné řádky standardního vstupu můžeme použít příkaz read
. Syntaxe
jeho volání je
read PROMENNA
Uvedený příkaz přečte jednu řádku ze vstupu (pokud vstupní buffer neobsahuje
žádná data, příkaz zablokuje proces a čeká na vstup) a uloží ji do proměnné s
názvem PROMENNA
. V případě chyby, nebo konce souboru přesměrovaného na vstup,
vrací příkaz read
nenulovou návratovou hodnotu. Pro zpracování víceřádkového
vstupu je tedy nutné volat příkaz read
opakovaně.
Řízení běhu programu, podmínky a cykly
Bezpodmínečné ukončení běhu skriptu je možné příkazem exit
. Nepovinným
parametrem je návratová hodnota, která je vrácena rodičovskému procesu, výchozí
návratová hodnota je 0
.
Podmínky
Podmínky jsou v bashi realizovány příkazem if, jehož syntaxe je
if <příkaz>
then
<příkaz1>
<příkaz2>
...
else
<příkaz3>
...
fi
Za klíčovým slovem if
následuje příkaz, který je spuštěn, a na základě jeho
návratové hodnoty probíhá interpretace kódu prováděním příkazů za klíčovým
slovem then
(návratová hodnota 0
), nebo else
(nenulová návratová
hodnota). Blok “else
” je nepovinný. Pro vyhodnocení podmínek s využitím
operátorů nad hodnotami proměnných slouží příkaz test
. Ten umožňuje provádět
operace nad řetězci (porovnávání, test prázdného řetězce), čísly (základní
algebraické operace) a soubory (existence souboru, typ souboru, přístupová
práva apod.). Příkaz
test "$A" = "abcd"
tedy vrátí návratovou hodnotu 0
, pokud je obsahem proměnné A
řetězec
“abcd
”. Pro praktické použití příkazu test
je výhodnější jeho úspornější a
přehlednější zápis hranatými závorkami:
if [ "$A" = "abcd" ]; then
# podmínka splněna
fi
Příkaz test očekává všechny operandy a operátory jako samostatné argumenty. Při použití zápisu v hranatých závorkách si proto dejte dobrý pozor na důsledné oddělení všech operátorů a operandů mezerami. Pokud tyto mezery vynecháte, jako v tomto případě:
if [ "$A"="abcd" ]; then ... ; fi
obdrží příkaz test jediný parametr “...=abcd
” a vrátí vždy 0
, protože se
jedná o neprázdný řetězec (výchozí test, pokud není zadán žádný operátor). Dále
důsledně dbejte uzavírání hodnot proměnných do uvozovek, jinak můžete narazit
na problémy v případě proměnné obsahující prázdný řetězec:
if [ $A = "abcd" ]; then ... ; fi
Uvedený příkaz bude fungovat bez problémů jen tehdy, bude li obsah proměnné A
neprázdný. V opačném případě obdrží příkaz test pouze argumenty “=
” a
“abcd
” a skončí chybou vyžadující dva operandy pro operátor “=
”. Užitečné
operátory příkazu test jsou:
-n STRING ... řetězec je neprázdný
-z STRING ... řetězec je prázdný
STRING1 = STRING2 ... dva řetězce jsou shodné
STRING1 != STRING2 ... dva řetězce jsou rozdílné
INTEGER1 -eq INTEGER2 ... dvě čísla jsou si rovná
INTEGER1 -ne INTEGER2 ... dvě čísla si nejsou rovná
INTEGER1 -gt INTEGER2 ... INTEGER1 je větší než INTEGER2
INTEGER1 -ge INTEGER2 ... INTEGER1 je větší nebo rovno INTEGER2
INTEGER1 -lt INTEGER2 ... INTEGER1 je menší než INTEGER2
INTEGER1 -le INTEGER2 ... INTEGER1 je menší nebo rovno INTEGER2
-e FILE ... existuje soubor s názvem FILE
-f FILE ... existuje obyčejný soubor s názvem FILE
-d FILE ... existuje adresář s názvem FILE
-r FILE ... existuje soubor s názvem FILE a mám práva pro čtení
-w FILE ... existuje soubor s názvem FILE a mám práva pro zápis
-L FILE ... soubor FILE je existující symbolický odkaz
Příkaz test
dále umožňuje konjunkce a disjunkce výrazů operátory -a
(AND) a
-o
(OR) a uzavírání dílčích výrazů do závorek.
V případě, že potřebujeme hodnotu proměnné porovnávat s více jinými hodnotami,
můžeme použít příkaz case
, jehož syntaxe je
case $PROMENNA in
hodnota1)
<příkaz>
<příkaz>
...
;;
hodnota2)
<příkaz>
;;
hodnota3|hodnota4)
<příkaz>
;;
*)
<příkaz>
;;
esac
Hodnota v zadané proměnné je porovnávána s jednotlivými zadanými hodnotami (což
může být i glob) a v případě shody se provádí blok příslušných příkazů až do
dvojice středníků ukončujících blok. Svislítkem |
je možné oddělit více
hodnot, pro které se má vykonávat stejný blok příkazů. V případě, že žádná
hodnota neodpovídá, provádí se blok *)
, pokud existuje.
Seznamy příkazů
Bash nabízí operátory &&
a ||
, umožnující spojování příkazů do seznamů (též
řetězení příkazů) s podmíněným vykonáváním následujících příkazů. Interpretace
seznamu příkazů
<příkaz1> && <příkaz2>
vykoná příkaz1
a pokud ten vrátí nulovou návratovou hodnotu, vykoná se i
příkaz2
. Operátor ||
se používá obdobně s tím rozdílem, že následující
příkaz se provede pouze pokud je návratová hodnota nenulová. Uvedené operátory
umožňují zkrátit zápis podmínek zejména v případech, kdy na základě vyhodnocení
podmínky chceme (nebo nechceme) spustit jediný příkaz. Operátory je možné
libovolně řetězit a vzájemně kombinovat.
Cykly
Bash nabízí tři druhy cyklů: for
, while
a until
.
Cyklus for
slouží k procházení seznamu hodnot a jeho syntaxe je
for PROMENNA in seznam_hodnot
do
<příkaz>
...
done
Příkaz for
postupně přiřazuje položky ze seznamu (řetězec hodnot oddělených
$IFS
nebo pole) do proměnné PROMENNA
a pro každé přiřazení vykoná seznam
příkazů uvnitř bloku do ... done
.
Cykly while
a until
mají shodnou syntaxi a rozdíl mezi nimi je pouze ve
vyhodnocení podmínky:
while <příkaz> # nebo until <příkaz>
do
<příkaz>
...
done
Na začátku každého průchodu cyklem se spustí příkaz následující klíčové slovo
while
resp. until
. Rozdíl mezi nimi je ten, že cyklus while
pokračuje,
dokud je návratová hodnota spuštěného příkazu nulová, cyklus until
pokračuje,
dokud je návratová hodnota nenulová. Cykly while
/until
je výhodné
kombinovat s příkazem test
, stejně jako u podmínek.
Uvnitř všech cyklů (tedy uvnitř bloku do ... done
) je možné používat příkazy
pro řízení průchodu cyklem break
a continue
. Příkaz break
způsobí
okamžité ukončení cyklu. Příkaz continue
přeskočí zbytek příkazů uvnitř cyklu
a skočí zpět na začátek cyklu.
Ošetřování chyb
Ve většině skriptů je užitečné reagovat na chyby, které nastanou při
spouštění příkazů, tedy když nějaký příkaz vrátí nenulový návratový
kód. Můžete použít příkaz if
, ale často je to zbytečně pracné:
if ! prikaz; then
echo "Nastala chyba" >&2
exit 1
fi
Často není potřeba ve skriptu vypisovat chybová hlášení jako výše,
protože to už dělá spouštěný prikaz
. V takovém případě jde výše
zmíněný příklad jde napsat i jednodušeji:
prikaz || exit 1
Dost často ale chceme, aby provádění skriptu skončilo při chybě v
libovolném příkazu. Psát za každý příkaz || exit 1
by byla otrava.
Naštěstí můžeme použít příkaz
set -e
který zajistí, že pokud některý z následujících příkazů selže, bash hned skončí s chybou.
Bash má i další užitečná nastavení, která pomáhají při ošetřování typických programátorských chyb. Zapnete je příkazem:
set -euo pipefail
Tímto příkazem je dobré začínat všechny bashové skripty. Přepínač -u
zajistí, že bash bude považovat za chybu i použití nedefinované
proměnné a -o pipefail
zase, že chyba nastane při selhání
libovolného příkazu v takzvané rouře (viz příští cvičení).
Aritmetické operace
Proměnné v bashi jsou řetězce. Pokud do proměnné uložíme celé číslo, uloží se
jako řetězec jednotlivých dekadických číslic. Příkaz expr
umí převést zadané
řetězcové argumenty na čísla a provádět s nimi základní aritmetické operace.
Parametry příkazu expr
jsou seznamem operandů, operátorů, případně závorek
aritmetického výrazu. Výsledek výpočtu vypisuje expr
na standardní výstup.
Nejužitečnější operátory jsou sčítání (+
), odečítání (-
), násobení (*
),
celočíselné dělení (/
) a zbytek po celočíselném dělení (%
). Kromě těchto
operátorů nabízí navíc relační operátory (ty už ale umíme realizovat příkazem
test
) a dále několik operací pro práci s řetězci (ty však budeme schopni
provést jednodušeji jiným způsobem).
Při zadávání parametrů příkazu expr
je nutné důsledně oddělovat mezerami
operátory a operandy, jinak je příkaz nerozpozná správně. Volání externího
příkazu expr
není v rámci skriptu příliš pohodlné a přehledné, proto bash
nabízí zkrácený zápis $(( výraz ))
. Tento zápis navíc poskytuje lepší parser
vstupního výrazu, nevadí mu chybějící mezery mezi operátory a operandy a navíc
automaticky nahrazuje jména proměnných jejich hodnotami. Inkrementace
celočíselné proměnné tak může vypadat takto:
A=$(( A+1 ))
Další možnosti aritmetických výpočtů nabízí vnitřní příkaz bashe let
.
Poskytuje navíc mj. logické a bitové operátory a operátory inkrementace a
dekrementace. Inkrementaci proměnné je možné zapsat velmi úsporně
let A++
Skupinové příkazy
Skupinu příkazů je možné uzavřít do složených závorek, což umožňuje např. hromadné přesměrování vstupu/výstupu pro všechny příkazy:
{
<příkaz>
<příkaz>
...
} > soubor
Kromě složených závorek je možné použít také kulaté závorky, které navíc způsobí, že příkazy uvnitř se vykonávají v samostatné instanci (nový proces) interpretu bash. Proměnné nastavené v novém procesu si v původním procesu nechávají svou hodnotu.
Operace s řetězci
Bash nabízí některé základní operace nad řetězci uloženými v proměnné. Délku
řetězce v proměnné RETEZEC
je možné získat zápisem
echo ${#RETEZEC}
Vytvoření pod-řetězce na základě počátečního indexu (první znak má index 0
) a
délky je možné
echo ${RETEZEC:<index>:<délka>}
Bash dále podporuje odřezávání začátků a konců řetězců na základě jejich obsahu:
echo ${RETEZEC#zacatek} # odřízne na začátku řetězce $RETEZEC řetězec "zacatek"
# (pokud $RETEZEC tímto řetězcem začíná) a vrátí zbytek
echo ${RETEZEC%konec} # odřízne z konce řetězce $RETEZEC řetězec "konec"
# (pokud $RETEZEC tímto řetězcem končí) a vrátí zbytek
Síla těchto operací vynikne zejména při použití znaku *
, reprezentujícícho
libovolný řetězec uvnitř hledaného výrazu. Zdvojením znaku #
na ##
, resp
%
na %%
je pak možné ovlivnit hladovost výrazu obsahujícího *
:
A="abcdefghabcdefgh"
echo ${A#*cde} # vypíše "fghabcdefgh"
echo ${A##*cde} # vypíše "fgh"
echo ${A%cde*} # vypíše "abcdefghab"
echo ${A%%cde*} # vypíše "ab"
Jednoduché nahrazení části řetězce za jiný řetězec je možné
echo ${RETEZEC/vzor/nahrazeni} # vypíše řetězec z proměnné RETEZEC, přičemž první výskyt řetězce "vzor" bude nahrazen řetězcem "nahrazeni"
echo ${RETEZEC//vzor/nahrazeni} # vypíše řetězec z proměnné RETEZEC, přičemž všechny výskyty řetězce "vzor" budou nahrazen řetězcem "nahrazeni"
Tento způsob umožňuje nahradit pouze první výskyt řetězce. Podporuje navíc znak
*
zastupující libovolný řetězec.
Další možnosti práce s řetězci a texty nabízejí externí nástroje (viz příští
cvičení). Nástroj cut
umožňuje ze vstupní řádky vyříznout znaky, slova, nebo
řetězce oddělené specifikovaným oddělovačem na základě jejich pořadí. Pro
hromadné nahrazení znaků za jiné znaky se hodí příkaz tr
. Příkaz tr
dostává
jako parametry dvě stejně dlouhé množiny znaků zadané jako řetězce a nahrazuje
ve vstupním textu znaky z první množiny odpovídajícími znaky z druhé množiny.
Pro spočítání znaků, slov, nebo řádek ve vstupním textu je možné použít příkaz
wc
(word count).
Funkce
Definice funkce v jazyce bash je možná dvěma způsoby:
function nazev_funkce
{
<příkaz>
<příkaz>
...
}
nebo
nazev_funkce()
{
<příkaz>
...
}
Funkce nemá pevný počet parametrů. K jednotlivým parametrům se uvnitř funkce
dostanete přes proměnné $1
, $2
, atd., podobně jako mimo funkce pracujete s
parametry skriptu. Analogicky se uvnitř funkce chovají proměnné $#
, $*
,
$@
a příkaz shift
. Návratová hodnota funkce je dána návratovou hodnotou
posledního příkazu. Provádění funkce je možné ukončit příkazem return
, jehož
nepovinným parametrem je návratová hodnota. Funkce se volá prostým uvedením
jejího jména, následovaného parametry, stejně jako se volá externí program nebo
skript.
Proměnné uvnitř funkcí mají globální viditelnost. Pokud chceme mít ve funkci proměnnou pouze lokální (tak, aby nebyly ovlivňovány proměnné stejného jména vně funkce), je možné explicitně deklarovat proměnnou jako lokální:
function funkce
{
local A
A=1
}
A=2
funkce
echo $A # vypíše "2"
Zpracování přepínačů v argumentech skriptu
Syntaxe volání většiny nástrojů v unixových systémech se řídí doporučeními
podle standardu POSIX. Při zpracování jednopísmenných přepínačů dle této
syntaxe vám pomůže příkaz getopts
. Syntaxe příkazu je
getopts options proměnná
První parametr (options
) je řetězec obsahující seznam písmen podporovaných
přepínačů. Pokud má daný přepínač argument, následuje za písmenem dvojtečka.
Dvojtečka na začátku řetězce nastavuje tichý mód, ve kterém se nevypisují chyby
při zadání neplatného přepínače. Druhým parametrem je název proměnné, do které
se uloží písmeno rozpoznaného přepínače. Každé volání příkazu getopts
zpracuje jediný argument skriptu, proto je nutné jej volat v cyklu. Použití
příkazu je zřejmé z následujícího příkladu:
while getopts ":abchn:s:" opt; do
case $opt in
a|b|c) echo "Byl aktivovan prepinac -${opt}"
;;
n) echo "Prepinac -n ma hodnotu ${OPTARG}"
;;
s) echo "Prepinac -s ma hodnotu ${OPTARG}"
;;
h) echo "Pouziti skriptu:"
echo "$(basename "$0") [-a] [-b] [-c] [-h] [-n <argument>] [-s <argument>]"
;;
?) echo "Byl zadan neplatny prepinac -${OPTARG}"
;;
esac
done
shift $((OPTIND - 1))
if [ $# -gt 0 ]; then
echo "Ostatni parametry skriptu: $*"
fi
Uvedený skript rozpoznává přepínače -a
, -b
, -c
a -h
, dále přepínače
-n
a -s
, které vyžadují argument, a dále libovolný počet dalších parametrů,
které nezačínají znakem ‘-
’ (uvedená implementace vyžaduje, aby byly
přepínače ve tvaru -písmeno
uvedeny před ostatními parametry). Jednotlivé
přepínače mohou být zadány v libovolném pořadí. Příkaz getopts
nastavuje
proměnné $OPTARG
(hodnota argumentu aktuálního přepínače) a $OPTIND
(index
aktuálně zpracovávaného parametru skriptu).
Varianta příkazu s názvem getopt
umí zpracovávat i volby v dlouhém formátu
(např. --help
).
Další užitečné příkazy a externí nástroje
eval
Příkaz eval
interpretuje řetězec zadaný parametrem jako příkaz, který vykoná.
Využijete jej tam, kde si potřebujete připravit složitější příkaz (např. podle
zadaných parametrů skriptu) a pak jej vykonat.
cat
Nástroj cat
slouží ke spojení obsahu více souborů, zadaných jako parametry,
do jednoho výstupu. Pokud není zadán žádný vstupní soubor, čte cat
data ze
standardního vstupu. Tohoto chování je možné s výhodou využít, pokud píšeme
skript, který čte data buď ze zadaného souboru, nebo ze standardního vstupu,
není-li soubor zadán.
find
Příkaz find
slouží k rekurzivnímu procházení stromové struktury zadaného
adresáře. Nalezené soubory buď vypisuje na standardní výstup, nebo nad nimi
vykoná zadaný příkaz. Parametry příkazu find
umožňují filtrovat výsledky
podle mnoha kritérií (např. jméno nebo typ souboru). Použitím příkazu find
se
vyhnete implementaci rekurze při procházení podadresářů.
dirname
Příkaz dirname
vrátí cestu k nadřazenému adresáři pro zadaný soubor nebo
adresář. V případě, že zadaná cesta je relativní, vrací také relativní cestu.
basename
Příkaz basename
odstraní z cesty k zadanému souboru či adresáři cestu k
nadřazenému adresáři a vrátí pouze jeho jméno.
readlink
Příkaz readlink
umožňuje zjistit, na jaký soubor odkazuje symbolický odkaz.
Užitečný je jeho parametr -f
, se kterým příkaz vrací absolutní cestu a to i v
případě, že není aplikován na odkaz.
stat
Příkaz stat
umožňuje získat dostupné informace o souboru – velikost, čas
změny, oprávnění a další informace uložené v I-node. Jeho parametry umožňují
libovolné formátování požadovaných informací.
diff
Příkaz diff
slouží k porovnání obsahu dvou textových souborů. Na výstup
vypisuje nalezené změny ve formátu závisejícím na použitých parametrech.
seq
Příkaz seq
generuje posloupnosti čísel v zadaném rozsahu a zadaným krokem.
Hodí se např. pro implementaci cyklu for
.
head
a tail
Příkazy head
a tail
vrací N
prvních resp. posledních řádek zadaného
souboru, nebo textu na standardním vstupu, kde N
je číslo zadané jako
argument parametru -N
.
sort
Příkaz sort
slouží k seřazení řádků textu (ze souboru nebo standardního
vstupu) podle abecedy. Parametry příkazu umožňují změnit způsob řazení.
uniq
Příkaz uniq
kopíruje řádky standardního vstupu na výstup s vynecháním
bezprostředně následujících duplicitních řádků. Parametr -c
umožňuje sčítat
počty duplicitních řádků.
grep
grep
je užitečný nástroj pro filtrování řádek textu podle jejich obsahu.
Povinným parametrem příkazu je vzor, se kterým jsou porovnávány vstupní řádky
a ty odpovídající jsou vytištěny na výstup. Vzor pro porovnávání je regulární
výraz.
awk
awk
je nástroj pro zpracování vstupního textu řádku po řádce, přičemž na
každou řádku se aplikuje skript napsaný ve vlastním jazyce. Tento skript se
skládá z řádek ve tvaru <vzor> { <akce> }
. Vzor představuje regulární
výraz, nebo podmínku, která se vyhodnocuje pro každou řádku vstupu a v případě
jejího splnění je na řádek aplikována zadaná akce – příkaz jazyka awk
.
awk
automaticky rozděluje vstupní text na řetězce podle zadaného oddělovače,
což dále usnadňuje jeho zpracování. Podporuje proměnné, pole, aritmetické
operace a programové konstrukce podobně jako jazyk C
(if
, while
, for
,
…). Dále poskytuje užitečné funkce pro zpracování textu nebo matematické
funkce.
sed
sed
je nástroj pro zpracování vstupního textu podle programu napsaného ve
vlastním jazyce. Jedná se o velmi efektivní nástroj s širokými možnostmi
použití. Velmi výhodná je jeho schopnost pracovat s regulárními výrazy pro
porovnávání nebo nahrazování textu.