Chcete-li opakovat nějaké příkazy, hodí se vám pro ně cykly. (Nebo rekurze, ale tu tady probírat nebudeme.)
Podmíněné cykly
Počet opakování těchto cyklů je definovaný nějakou podmínkou tedy dokud něco nenastane.
Bash rozeznává příkazy while a until, které jsou vlastně stejné a zároveň zcela opačné. while provádí tělo cyklu, pokud je podmínka splněna, until provádí tělo cyklu, dokud podmínka není splněna. Jak si to zapamatovat? Třeba tak, že si pamatuju nekonečnou smyčku:
while true
do
echo "cyklim"
done
Kdo ji spustil, nechť zase vyskočí pomocí ctrl-c.
Jednoduchý příklad je třeba následující počítání:
i=1
while [ $i -le 5 ]
do
echo -n "$i "
let i++
done
echo "uz jdu"
Cyklus se opakuje, dokud proměnná i je menší nebo rovna 5. Uvnitř cyklu pak probíhá její inkrementace.
jana@drak:~$ ./loop.sh
1 2 3 4 5 uz jdu
Tohle je hezký příklad na ukázku syntaxe, ale protože vy už dopředu víte, kolikrát se bude cyklus opakovat, je zbytečné zde plýtvat podmíněným cyklem. Lepší příklad bude, když necháme uživatele zadávat čísla a budeme hlídat, aby jejich součet nepřesáhl 20.
Proměnnou součet na začátek vynuluju, poté hlídám, aby nepřesáhla 20 a opakuju čtení čísla a jeho přičítání k součtu.
V tuto chvíli už asi tušíte, že while mám raději než until. Ale přesto se mu nebráním, hezký příklad pro until bude třeba hádanka:
max=3
cislo=$((RANDOM % max + 1)) #vylosuju nahodne cislo 1-max
hadam=0 #inicializuju cislo, ktere pak nactu. jinak hazi chybu
until [ "$hadam" -eq "$cislo" ] #dokud se cisla nerovnaji
do
echo -n "zadej cislo 1 - $max: "
read hadam #nacitam od uzivatele
done
echo "uhodl"
Povšimněte si drobnosti, že nastavuju proměnnou max, ačkoli ji používám ve skriptu jen drakrát. Jasně, nemusela bych, ale jakmile je něco aspoň dvakrát, radši jednou nastavím proměnnou na začátku skriptu, než abych se pak hrabala v kódu.
for cyklus
Syntaxe je for je taková, že definujete nějakou proměnnou, přes kterou chcete, aby cyklus iteroval, a pak zadáte seznam hodnot, kterých má proměnná nabývat.
for houba in bedla zampion muchomurka lysohlavka
do
echo $houba
done
V tomto příkladu nastavuju proměnnou houba postupně na hodnotu bedla, zampion, atd.
Předchozí příklady jsou ryze početní, teď pojedeme cyklus přes seznam, který nám vygeneruje nějaký příkaz. Nejprve skript, který se podívá do parametricky zadaného adresáře a všechny soubory přejmenuje tak, že jim před jméno přidá jejich datum poslední změny.
adr="adresar"
for soubor in $(ls $adr)
do
cas=$(ls --full-time $adr/$soubor | tr -s " " | cut -d" " -f6)
mv $adr/$soubor $adr/$cas-$soubor
done
Přikládám výstup. Zvídavý čtenář se teď ptá, jak mám tohle vyzkoušet? Musím si každý den udělat soubor a nakonec to otestovat? Nikoli, zvídavý čtenáři! Stačí použít příkaz touch a změnit jím čas poslední změny.
jana@drak:~/skripty$ ls -l adresar/
total 0
-rw-r--r-- 1 jana vyucujici 0 Dec 15 2020 neco.jpg
-rw-r--r-- 1 jana vyucujici 0 Dec 16 2020 skript.sh
-rw-r--r-- 1 jana vyucujici 0 Dec 14 2020 soubor
-rw-r--r-- 1 jana vyucujici 0 Dec 17 2020 texty.txt
jana@drak:~/skripty$ ./prejmenovani_time_stamp.sh adresar/
jana@drak:~/skripty$ ls -l adresar/
total 0
-rw-r--r-- 1 jana vyucujici 0 Dec 14 2020 2020-12-14-soubor
-rw-r--r-- 1 jana vyucujici 0 Dec 15 2020 2020-12-15-neco.jpg
-rw-r--r-- 1 jana vyucujici 0 Dec 16 2020 2020-12-16-skript.sh
-rw-r--r-- 1 jana vyucujici 0 Dec 17 2020 2020-12-17-texty.txt
jana@drak:~/skripty$
Jiný příklad skriptu se podívá, kdo se dnes přihlásil na server a jestli něco dělal ve svém home.
#!/bin/bash
dnes=$(date --iso-8601) #vytiskne cas ve formatu YY-MM-DD
for clovek in $(utmpdump /var/log/wtmp | grep 2020-12-11 | grep "^\[7\]" | cut -d"[" -f5 | cut -d"]" -f1 | sort | uniq)
do
echo -n "$clovek: "
houm=$(grep "^$clovek:" /etc/passwd | cut -d: -f6)
if ls --full-time $houm | grep $dnes &> /dev/null
then
echo "pracoval ve svem home"
else
echo -n "nevidim, ze by pracoval ve svem home"
if [ ! -r $houm ]
then
echo "ale nemam do nej pristup"
fi
echo
fi
done
Cyklus zde naplňuje proměnnou clovek postupně všemi lidmi, kteří jsou zapsaní s dnešním datem v souboru /var/log/wtmp.
break a continue
Příkaz break způsobí, že vyskočíte z cyklu.
pricitam=$((RANDOM % 3))
nasobim=$((RANDOM % 3 + 1))
vysledek=0
for i in {1..5}
do
vysledek=$((vysledek + i*nasobim + pricitam))
if [ $i -ge 5 ]; then
break
fi
echo -n "$vysledek "
done
echo "?"
read hadam
if [ "$hadam" -eq "$vysledek" ]; then
echo ok
else
echo x vysledek je $vysledek
fi
Generuju zde řadu čísel s náhodným předpisem a tisknu první 4 čísla. Poslední sice spočítám, ale již netisknu.
jana@drak:~$ ./loop.sh
3 8 15 24 ?
35
ok
jana@drak:~$ ./loop.sh
1 3 6 10 ?
15
ok
jana@drak:~$ ./loop.sh
3 7 12 18 ?
22
x vysledek je 25
Příkaz continue cyklus neopouští, ale vynechá zbytek příkazů v těle cyklu, které jsou za tímto příkazem, a jde rovnou na další průchod cyklem.
nasobim=$((RANDOM % 3 + 1))
max_i=5
vysledek=0
nahodne_i=$((RANDOM % (max_i-2) + 2))
for i in $(seq 1 $max_i)
do
vysledek=$((vysledek + i*nasobim))
if [ $i -eq $nahodne_i ]; then
hadane=$vysledek
continue
fi
echo -n "$vysledek "
done
echo "co chybi?"
read hadam
if [ "$hadam" -eq "$hadane" ]; then
echo ok
else
echo x chybelo je $hadane
fi
Generuju opět řadu čísel s náhodným předpisem, poněkud jednodušší než předchozí. Jedno z čísel (opět náhodně) vynechávám, nicméně tak, aby to nebylo první nebo poslední.
jana@drak:~$ ./loop.sh
3 9 30 45 co chybi?
15
x chybelo je 18
jana@drak:~$ ./loop.sh
2 6 20 30 co chybi?
12
ok
jana@drak:~$ ./loop.sh
3 9 30 45 co chybi?
18
ok
Rekurze
Na začátku jsem avizovala, že se nebudeme zabývat rekurzí. Nicméně mi nedá ji zmínit jako možnost, ano, existuje jako volání sama sebe, nicméně přinejmenším z hlediska výpočetní rychlosti ji nedoporučuji.