Shell szkriptek: for...do...done

Az előző részekben már egypár fontos dolgot megtanultunk a szkriptekről. Amit már tudunk, hogy alapfunkcióként felsorolunk egy csomó parancsot, és azokat egymás után végrehajtja. Ez alapvetően senkinek nem okozott gondot, hiszen ugyanazokat kell beírni, amiket a parancssorba írnánk. Ma a hurkokról fogunk beszélni, Már itt felhívom mindenki figyelmét, hogy ez kezdő szintű cikk, és nem programozókat képezek, hanem csak annyi a célom, hogy egyszerű kis szkriptet meg tudjunk írni, magunknak. Így minden gurunak (vagy magát annak képzelőnek) jelzem, hogy nem is szeretnék ennél mélyebben, szakmaibban belemenni, és tudom, hogy sokan okosabbak nálam :).
Ok, mi is az a hurok? A hurok az egy olyan megoldás, amikor ismétlődő, mechanikusan és egyértelműen meghatározható paramétereket, adatokat, elemeket kap a szkript, melyekkel végre kell hajtania a feladatot. Ha ezeket az adatok, paraméterek elfogynak, akkor vége a munkájának léphet tovább. Ez sem más, mint amikor kapsz egy bevásárló listát, és szép sorban megveszed amit kell. Ilyenkor az elemek a listában felsorolt beszerzendők, és te vagy a szkript, aki végrehajtja őket.

A legegyszerűbb megoldás, ha simán felsorolod azokat az elemeket, melyekkel dolgozni kell a szkriptnek. De ez a felsorolás lehet egy parancs kimenetele is, egy tömb, vagy akármi, amit szeretnél.

A szerkezete nagyon egyszerű:

for item in [LIST]
do
[COMMANDS]
done

A for sorban gyártjuk le, vagy adjuk meg azokat az elemeket, amikkel a do utáni parancs dolgozni fog, majd ha elfogyott a az összes elem, akkor – mivel nincs több – a szkripted továbblép. Ha ezután még van teendője, azaz felsorolt parancs, akkor azt csinálja, ha nincs, akkor kilép, és vége.

Kicsit érthetőbb lesz, ha egy példát hozok:

#!/bin/bash
for nev in Laci Mari Kati
do
echo "Haverok: $nev"
done

Mindig meg kell adni valami olyant, amivel hivatkozunk az adott adatra, ez a nev. Ezentúl ez a „nev” lesz a lista éppen aktuális elemének a jele. Ez egy változó érték, hiszen előbb a nev az első elem, majd a második, és ha végiglépdelt az összesen az utolsó. A neve is „változó”, amit gyakran használni is fogunk. Így tudunk egyszerűen hivatkozni egy olyan értékre, ami folyamatosan változik. A hivatkozás egyszerű: $változóneve.
Az egyszerűség kedvéért csak kiírattam a listát az ismert echo paranccsal. Ez kezdeti időben nagyon jó ötlet, még akkor is, ha valójában nem egy kiíratás a cél. Így a terminálban látni fogod a tényleges kimenetet, amit a parancs kap, és ellenőrizni tudod, hogy valóban azt kapja, amit szeretnél. Egy listánál nem gond, de ha már nem ilyen egyszerű dolog áll az in mögött, akkor jobb ha ténylegesen is látod, hogy mi az ami történik.
A programocska fordítása magyarra: Légy szíves vedd a nev változónak az értékét (for nev) a felsoroltakból (in) addig amíg el nem fogy. Majd tedd meg egyesével azt, amit a do utáni részben megadtunk. A $nev változót egyesével helyettesítsd be, majd ha végeztél, akkor fejezd be (done) és azt kell majd tenned, ami a done után jön. Jelen esetben semmit.
Ha lefutott a szkript, akkor egyértelmű lesz, hogy egyesével végigmegy a listán és végrehajtja mindegyiken sorba a do utáni parancsot, az echo-t.
Hm… ez OK, kényelemes, hiszen egyszer kell begépelni a listádat és amíg megvan a szkript, addig ezt nem kell újra.
Egy nagyon egyszerű példa. A fentieket írd át úgy, hogy hozzon létre a szkript három könyvtárat a nevekkel. Ez akkor lehet praktikus, ha egy adott könyvtár szerkezetet nagyon sokszor létre kell hoznod.
Szekvenciát, sorozatok, melyet a gép maga generál. Ilyenkor egy szűkebb körben, de jól hasznosíthatóan a program maga állítja elő az adott listát. Ez lehet egy számsorozat, vagy betű sorozat is.

for i in {0..3}
do
echo "Számok: $i"
done

Próbál ki, és meglátod, hogy előállítja az összes számot nulla és három közt. De használható a seq parancs is, csak azért, hogy lássuk_

for i in $(seq 1 5); 

Nem nagy ügy gondolhatod, de nekem egy honlapról kell leszednem egy meghatározott tól-ig sorszámú fájlt:

#!/bin/bash
for i in $(seq 6000 15000);
do
chromium-browser "http://xxx.xxxxxx.xx/new/content.php?id="$i"&d=1&ct=d" ;
done

Azért ez már kézzel, egyesével rákacsingatva, vagy beírogatva a szkriptbe a számokat elég macera lenne…
De egy A-tól Z-ig tartó könyvtár szerkezet is gyorsan létrehozható: 

#!/bin/bash
for nev in {A..Z}
do
mkdir $nev
done

Ha nem egyesével kell növelni a számokat, hanem lépésközönként, akkor a {START..END..INCREMENT} formátum lesz a megoldás.

for i in {0..20..5}
do
echo "Számok:
$i"
done

Persze lehet kissé bonyolultabban is megadni, de számunkra fenti megoldások legtöbb esetben elegendőek. Aki ismeri a C típusú nyelveket, azoknak

for ((i = 0 ; i <= 10; i++)); do
echo "Counter: $i"
done

ez a szerkezet ismerős lehet. Bár ezt is lehet használni, de első lépésben elegendő az első kettő is.
Mi a hiba ebben? Gyakorlatilag a fenti megoldások jók, de van egy hibájuk. Ha a done után akarunk hivatkozni a listánk elemére (akár mi adtuk meg, akár úgy generáltattuk le a géppel) már nem lesz eredménye. Csak az adott szerkezetben értelemezi a program. Próbáljuk is ki:

#!/bin/bash
for nev in Laci Mari Kati
do
echo "Haverok: $nev"
done
echo "Haverok-újra: $nev"

Azt várnánk, hogy az előzőekben felsorolt Laci Mari Kati sorozatot ismételné meg. De nem. Pedig már egyszer megadtuk neki, hogy a nevet vegye a a felsorolásból. Olyan mintha elfelejtette volna a listánkat!

Az i változó értéke az utolsó érték lesz: Kati. Azt vártuk volna, hogy újra kiírja az egész sorozatot, de nem. Ez jól mutatja a folyamatosan változó „változó” tulajdonságát. Úgy képzeld el, mint egy dobozt. Amibe előbb az első érték kerül, a Laci, majd ha újat kap a program, akkor már a Kati eltűnik, és az új, azaz Mari kerül a dobozba. Így csak akkor lesz valóban változó a változó a következő, a done utáni részben, ha azt újra felsoroljuk neki.

#!/bin/bash
for nev in Laci Mari Kati
do
echo "Haverok: $nev"
done
for nev in Laci Mari Kati
do
echo "Haverok-újra: $nev"
done

De mi van, ha én hosszabb programnál szeretném, ha a végéig folyamatosan megtartsa az összes értéket? Ne kelljen minden esetben ugyanazt felsorolni. Akkor a for… do… done együttesen kívül kell elraktározni a felsorolást. Így mindig hivatkozhatunk rá, és nem csak az adott részfeladatban.

EMBEREK=('Laci' 'Mari' 'Kati')
for haverok in "${EMBEREK[@]}"; do
echo "Haverol: $haverok"
done

Majd ellenőrizzük le:

EMBEREK=('Laci' 'Mari' 'Kati')
for haverok in "${EMBEREK[@]}"; do
echo "Haverok: $haverok"
done
for haverok in "${EMBEREK[@]}"; do
echo "Haverok-újra: $haverok"
done

És máris megjegyezte, illetve pontosabban: már tudomására hoztuk, hogy a haverok változót az EMBEREK felsorolásból (tömbből) vegye. Kicsit bonyolultnak tűnik, de ha követjük a mintát: kettős idézőjelbe tesszük, hogy egybe kezelje az egészet a program, majd szabályosan a $ jellel megmondjuk, hogy egy változót hozunk létre a {} közti felsorolással, a tömb elemeire való hivatkozással. A tömb elemeire pedig a TÖMBNEVE[@] formában hivatkozhatunk.
Aki észrevette, hogy az EMBEREK=('Laci' 'Mari' 'Kati') tömb elemeit egyszeres idézőjelbe raktam. Ez elméletileg nem kötelező, ha egy tagú az elem, de jobb ha megszokjuk, hogy így írjuk, mert ha több tagú az elem (pld. Laci Maci) akkor nem fogja tudni a program, hogy az egy „haver”, hanem kettőnek veszi. Próbáld ki!

Pár példa, ami hasznosabb, mint a fenti egyszerű, de bemutatásra jó programocskák.

A következő példa azt mutatja be, hogyan használhatod a ciklusokat az aktuális könyvtár összes fájljának átnevezéséhez. Egy nálam gyakori feladata a szóközök aláhúzásjelre való cseréje:

for file in *\ *; do
mv "$file" "${file// /_}"
done

Az első sor létrehoz egy ciklust, az összes fájl listáját a nevében lévő szóközzel. A *\ * létrehozza a listát. Ezt így érdemes megegyezni, hiszen sokszor kell egy könyvtár összes fájljára hivatkoznunk, és az összessel valami műveletet végrehajtani.
A második sorban adjuk meg a műveletet, amit végre kell hajtani. Itt egy sima csere művelet van, amit szintén érdemes megjegyezni. A {file//amit/amire} csere funkció nagyon hasznos, ha sokszor akarunk karaktereket, név részletet cserélni a fájlokon. Az mv parancsot ismerjük, átnevezi a fájlt. Már itt érdemes átgondolni, hogy egy átmozgatásnál, átmásolásnál is hasznos lehet ez, ha közben átnevezést is szeretnénk.
Hasonlóképp gyorsan megváltoztathatjuk a kiterjesztéseket is, ami jó példa, hogy egy kiterjesztésű fájlokkal miképp lehet műveleteket végezni.

for file in *.jpeg; do
mv -- "$file" "${file%.jpeg}.jpg"
done

Akit a kapcsos zárójel közti műveletek jobban érdekelnek, annak érdemes a shell paraméter kiterjesztéseket olvasgatni, amivel igencsak sokféle dolgot meg tudunk oldani. Igaz nem csak így, de sok esetben egyszerűbb ezeket használni, mint esetleg egy külön programot meghívni. Aki figyelt, észrevehette, hogy ahol kicsit komolyabb dolgot tettünk, ott már nem a 

for ...in
do
parancs
done

szerkezetet használtam, hanem a jobban átláthatóbb, illetve kissé szabályosabb

for ... in; do
parancs
done

megoldást. Érdemes ezt a másodikat használni.

Ennyi, de folytatni fogjuk :)

Ez is a blogomban jelent meg :)

Hozzászólások