Skriptovanie v shelli: cyklus `while` a trik `while`/`read`

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 ife: splnená je vtedy, ak exit code príkazu je rovný nule. Najlepšie sa to overí v kombinácii s testom, č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 whileom, 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 vyechovať 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

Pridaj komentár

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