Shell samozrejme podporuje funkcie. Syntax je — ako to už býva tradíciou — pomerne nezvyklá, ale mnohokrát uľahčí prácu.
Predstavme si, že chceme generovať náhodné čísla. V bashi i v Korn shelli máme k dispozícii špeciálnu premennú RANDOM
, ale napr. v znovuzrodenom klasikovi Ubuntu a Debianu, v dashi už takáto možnosť nie je.
Môžeme si ju vytvoriť. Hľa, majsterštuk inšpirovaný dielom Heinera Stevana
dd if=/dev/urandom bs=2 count=1 2>/dev/null | od -d | awk 'NR==1 {print $2}'
V skratke: načítame dva náhodné bajty, ktoré interpretujeme ako číslo medzi 0 a 65535,
Príkaz dd
Príkaz dd
slúži na čítanie a kopírovanie bajtov zo zariadenia. V tomto prípade si zvolíme zariadenie /dev/urandom
, ktoré predstavuje pseudonáhodný generátor čísiel. Inak povedané, každý bajt načítaný z tohto zariadenia bude predstavovať náhodné číslo.
Nástroj dd
číta dáta po blokoch. Parametrom bs=2
deklarujeme dvojbajtové bloky, ale hneď nasledovným count=1
povieme, že budeme chceme načítať len jeden blok.
Presmerovanie 2>/dev/null
nás zbaví zbytočných štatistík a získame len samotné bajty.
Príkaz od
Ďalší od
konvertuje binárne dáta do textovej podoby. Parametrom -d
povieme, že ich budeme intepretovať ako dvojbajtové bezznamienkové čísla. To sa presne hodí: pretože dvojbajtový blok z dd
prevedieme na číslo z rozsahu 0…65535.
Výsledkom bude napr:
0000000 8543
0000002
Ako vidíme, od
nás oblaží kopou zbytočných dát. Z nich potrebujeme prvý riadok, zrušiť dvojité medzery a vyseknúť druhé pole. Dosiahneme to buď:
head -n1 | tr -s " " | cut -d" " -f2
alebo cez awk
, ktorý to spraví o niečo elegantnejšie.
awk 'NR==1 {print $2}'
Deklarácia funkcií
Syntax je jednoduchá: uvediete názov funkcie, dve zátvorky a skupinu príkazov — najčastejšie uvedenú v kučeravých zátvorkách:
random() {
dd if=/dev/urandom bs=2 count=1 2>/dev/null | od -d | awk 'NR==1 {print $2}'
}
Pozor na to, že POSIX nedovoľuje žiadne kľúčové slovo function
(to je výmysel bashu). Medzi guľatými zátvorkami nesmú byť žiadne parametre!
Volanie funkcie je jednoduché: ničím sa nelíši od volania iného príkazu. Päť náhodných čísiel získate cez
for I in $(seq 5)
do
random
done
Vlastnosti funkcií
Funkciu v shelli možno veľmi dobre prirovnať k skriptu v skripte.
- Funkciu vieme zavolať rovnako ako akýkoľvek iný skript.
- Ak chce funkcia vracať nejakú hodnotu, môže využiť exit code alebo zapisovať do výstupu či súboru.
- V prípade, že funkcia preberať nejaké dáta, môže využiť buď parametre, alebo čítať zo vstupu či súborov.
Dôležité je vedieť, že na rozdiel od C a pod. neexistuje možnosť vrátiť z funkcie hodnotu: vidieť to na príklade našej funkcie random()
, ktorá svoj výsledok zapíše do výstupu.
Na rozdiel od vnorených skriptov však funkcia dokáže vidieť (a modifikovať) hodnoty premenných skriptu, v ktorom sa nachádza. O tom však až o chvíľu.
Parametre funkcií
O tri odseky vyššie bolo spomenuté, že funkcia sa správa ako skript v skripte. Táto vlastnosť súvisí aj s filozofiou parametrov.
Skúsme dodať do funkcie random()
parameter, ktorý udá horný rozsah vygenerovaného čísla. Napr. ak chceme vygenerovať číslo medzi 0 a 24:
random 25
Všimnime si, že nepoužívame žiadne guľaté zátvorky! Parametre dodávame rovnakým spôsobom, ako keby išlo o iný skript, či program, ktorý voláme s parametrami príkazového riadka.
To isté platí aj pri pohľade zvnútra funkcie: k parametrom pristupujeme presne tým istým spôsobom, ako k parametrom príkazového riadka: ich hodnoty sa objavia v premenných 0
, 1
, 2
…
Prejdime ale k algoritmu: Zatiaľ generujeme čísla medzi 0…65535, ale ak chceme zraziť hodnoty do požadovaného rozsahu, môžeme použiť operáciu zvyšku po delení. Samotné vygenerované číslo však už nemôžeme zapísať do výstupu: potrebujeme si ho odložiť do premennej.
random() {
CISLO=$( dd if=/dev/urandom bs=2 count=1 2>/dev/null | od -d | awk 'NR==1 {print $2}' )
echo $CISLO
}
Hodnotu rozsahu si môžeme poznamenať do premennej ROZSAH
. Ak premenná 1
nebude definovaná, použijeme implicitnú hodnotu 65535:
ROZSAH=65535
if [ -n "$1" ]
then
ROZSAH="$1"
fi
Na konci skriptu vykonáme operáciu delenia so zvyškom v rámci aritmetického prostredia:
echo $(( CISLO % ROZSAH ))
čím získame výsledok.
Celý skript:
random() {
ROZSAH=65535
if [ -n "$1" ]
then
ROZSAH="$1"
fi
CISLO=$( dd if=/dev/urandom bs=2 count=1 2>/dev/null | od -d | awk 'NR==1 {print $2}' )
echo $(( CISLO % ROZSAH ))
}
for i in $(seq 25)
do
random 25
done
Namiesto if
u vieme využiť aj vlastnosti expanzie a uviesť do ekvivalentným spôsobom:
ROZSAH=${1:=65535}
Modifikátor :=
v expanzii zistí, či je premenná 1
definovaná a neprázdna. Ak nie, priradí do rozsahu implicitnú hodnotu.
Funkcia tak môže vyzerať::
random() {
ROZSAH=${1:=65535}
CISLO=$( dd if=/dev/urandom bs=2 count=1 2>/dev/null | od -d | awk 'NR==1 {print $2}' )
echo $(( CISLO % ROZSAH ))
}
Funkcie a viditeľnosť premenných
V POSIXovom shelli neexistuje žiadny rozdiel medzi lokálnymi a globálnymi premennými. Inak povedané: všetky premenné sú automaticky globálne.
Funkcia vidí a dokáže meniť všetky premenné zvonku. Dokonca platí ešte čudesnejšia vlastnosť: funkcia zadefinovaná vo vnútri funkcie je automaticky globálna.
Dopíšte si za cyklus z príkladu:
for i in $(seq 25)
do
random 25
done
echo "Rozsah generatora cisiel: $ROZSAH"
Výsledok bude
Rozsah generatora cisiel: 25
Premenná ROZSAH
bola definovaná vo vnútri funkcie (zdalo by sa, že je lokálna), ale v skutočnosti je to globálna premenná, ktorú je vidieť po dobehnutí funkcie.
Návrat hodnôt cez exit code
K tomuto stačí povedať dve zásady: exit code funkcie sa určí podľa exit code posledného príkazu.
Ak chcete funkciu ukončiť predčasne, napr. s exit code 1, použijete
exit 1
Zbytočných dát sa môžeme zbaviť aj parametrom
-An
, ktorým vypneme výpis offsetov. Číslo bude zarovnané doprava, ale vieme sa zbaviť medzier ceztr