Shellové skriptovanie: funkcie

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 ifu 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 

One thought on “Shellové skriptovanie: funkcie

  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 cez tr

    dd if=/dev/urandom bs=2 count=1 2>/dev/null | od -d -An | tr -d " "

Pridaj komentár

Vaša e-mailová adresa nebude zverejnená. Vyžadované polia sú označené *