Cyklus while
Zatiaľčo cyklus for
iteruje cez zoznam shellovských slov, niekedy sa hodí cyklus, ktorí sa „točí” dovtedy, pokiaľ je splnená booleovská podmienka. A presne na toto sa hodí while
.
Pre podmienku platí rovnaká idea ako pri if
e: splnená je vtedy, ak exit code príkazu je rovný nule. Najlepšie sa to overí v kombinácii s test
om, či jeho kratším variantom [
.
Simulácia céčkoidného for
cyklu
Ukázať si to môžeme na simulácii klasického céčkoidného cyklu for
, čo je jediná možnosť, ktorá je kompatibilná s POSIXovou normou (a zároveň je variantom GNU seq
či BSD jot
)
i=0
while [ $i -lt 10 ]
do
echo $i
i=$(( $i + 1 ))
done
Cyklus prebieha dovtedy, pokiaľ je premenná i
menšia než 10 (pozor na nezvyklý operátor „menší než”: -lt
. Tradičný znak <
má úplne inú rolu — presmerováva vstup.)
Príkaz pre inkrementáciu využíva aritmetické prostredie $(...)
v kombinácii s čítaním a priradením do premennej.
i=$(( $i + 1 ))
Načítavanie súboru
Cyklus while
sa dá umne použiť na spracovanie riadkov súboru. Stačí ho skombinovať s príkazom read
.
Vytvorme si ukážkový súbor mien a počtom bodov, ktoré získali z testu zo shellového skriptovania:
thompson 23
root 57
novotnyr 76
ritchie 78
kernighan 87
Skúsme nájsť priemerný počet bodov.
Budeme potrebovať
- jeden
while
- jeden
read
- jedno presmerovanie súboru
- a voliteľne jednu kalkulačku
bc
Príkaz read
Samotný príkaz read
načítava riadok zo štandardného vstupu. Skúste si to
read
Následne zadajte napr Ahoj svet
a enterujte.
Čo sa stalo? Na prvý pohľad nič, ale v skutočnosti read
načítal váš riadok Ahoj svet
do implicitnej premennej s názvom REPLY
:
echo $REPLY
Výsledkom je, samozrejme,
Ahoj svet
Samozrejme, možno určiť názov premennej:
read POZDRAV
A následne
echo $POZDRAV
Dokonca možno uviesť viacero premenných! read
rozseká vstupný riadok podľa medzier (presnejšie na základe obsahu premennej IFS
, ale o tom neskôr) a každé slovo priradí do premennej. Ak je slov viac než premenných, prevyšujúce sa ocitnú v poslednej premennej.
read MENO PRIEZVISKO
Zadajte:
Dennis Ritchie
Následné
echo $PRIEZVISKO, $MENO
vypíše samozrejme
Ritchie, Dennis
Zadajte teraz
Dennis MacAlistair Ritchie
Slov je viac než premenných a teda nadbytok skončí v premennej PRIEZVISKO
:
MacAlistair Ritchie, Dennis
Kombinácia while
/read
Vo chvíli, keď read
nedokáže načítať riadok (napr. narazil na koniec súboru), jeho exit code bude nenulový. To je presne vhodná vec do cyklu while
.
while read MENO BODY
do
echo "$MENO ziskal $BODY bodov"
done < mena.txt
Keďže položky v súbore mena.txt
sú oddelené medzerou, read
ich radostne načíta.
Všimnite si ešte Yodovskú syntax pre špecifikáciu mena súboru: najprv uvedieme while
/read
, do ktorého presmerujeme obsah súboru mena.txt
. Na prvý pohľad sa zdá byť neintuitívne zadávanie vstupu až za while
om, ale je to veľmi efektívny, a v niektorých situáciách dokonca jediný korektný prístup.
Alternatívna, menej efektívna a niekedy nekorektná, syntax:
cat mena.txt | while read MENO BODY
do
echo "$MENO ziskal $BODY bodov"
done
Menšia efektivita je zjavná kvôli použitiu cat
, korektnosť je otázna. (Presnejšie: toto funguje. Ale pri rátaní priemerov už narazíme.)
Skript, verzia 1.0
Teraz už môžeme napísať skript:
#!/bin/sh
POCET=0
SUCET=0
while read MENO BODY
do
POCET=$(($POCET + 1))
SUCET=$(($SUCET + $BODY))
done < mena.txt
echo "Priemerny pocet bodov: " $(( $SUCET / $POCET ))
Deklarujeme dve premenné: POCET
pre celkový počet položiek a SUCET
, ktorá bude sčítavať celkový počet bodov. Na inkrementáciu, resp. spočítavanie použijeme klasické aritmetické prostredie $((...))
. Zároveň ho využijeme aj pri výpise priemeru.
Skript už radostne funguje: s jedným detailom — aritmetické prostredie shellu totiž vôbec nepodporuje desatinné čísla. Výsledkom je teda 64
namiesto správneho 64.2
.
Výpis s desatinnými číslami
Ak to silou mocou chceme opraviť, môžeme použiť kalkulačku bc
:
echo 321 / 5 | bc
Výsledkom je, prekvapivo:
64
Kalkulačka štandardne funguje bez desatinných čísiel, presnejšie so škálou 0, t. j. s 0 desatinnými miestami. Môžeme zapnúť matematický mód:
echo 321 / 5 | bc -l
Výsledok zrazu začne fungovať s 20 desatinnými číslami:
64.20000000000000000000
Ak nás otravuje prehnaná škála, môžeme ju nastaviť ručne. bc
je v skutočnosti veľmi mocný nástroj, ktorý dokáže prijímať zo štandardného vstupu príkazy a spracovávať ich.
Skúste si spustiť bc
z príkazového riadku v tzv. interaktívnom móde:
novotnyr@s:~$ bc
bc 1.06.95
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
Skúste zadať
scale = 2
A následne
321 / 5
Výsledkom bude, prirodzene
64.20
Ak to chcete urobiť zo shellu, stačí príkazy vyecho
vať a oddeliť bodkočiarkou:
echo "scale=1; 321 / 5" | bc
Skript, verzia 2.0
Teraz už môžeme napísať lepší skript: Všetko ostane ako predtým, akurát nahradíme finálny výpočet:
echo "scale=1; $SUCET / $POCET" | bc
Kompletný skript:
#!/bin/sh
POCET=0
SUCET=0
while read MENO BODY
do
POCET=$(($POCET + 1))
SUCET=$(($SUCET + $BODY))
done < mena.txt
echo "Priemerny pocet bodov: "
echo "scale=1; $SUCET / $POCET" | bc