Shell-programozás
(→Konfigurálható script: a find használata) |
(→Alapértelmezések használata: Hivatkozás változó értékére) |
||
37. sor: | 37. sor: | ||
done |
done |
||
</pre> |
</pre> |
||
+ | |||
+ | == Hivatkozás változó értékére == |
||
+ | |||
+ | Alapvetően <tt>$változónév</tt>, de ha pl. sztringet akarunk fűzni a változó tartalmához, akkor a hozzáfűzött sztring összefolyna a változónévvel. Ennek elkerülésére: <tt>${változónév}</tt>. |
||
+ | Ez a szintaxis amúgy egy sereg további lehetőséget rejt magában; írhatunk mindenféle mágikus karaktert a változónév elé és mögé, amitől érdekesebbnél érdekesebb dolgok történhetnek. Hamarosan látunk erre példát. |
||
== Alapértelmezések használata == |
== Alapértelmezések használata == |
A lap 2007. október 29., 23:46-kori változata
Ezen az oldalon összeszedek néhány olyan megoldást, ötletet, scriptrészletet, amit gyakran jól használhatunk a rendszeradminisztráció során. Nem célom a shell, mint programozási nyelv teljes bemutatása. Kizárólag Bourne jellegű shellekkel foglalkozom, és azon belül is főként zsh-val.
És azért persze nem maradhat ki a sed sem.
Tartalomjegyzék |
1 Az idézőjelek használatáról
A "dupla" idézőjelek között a shell nem értelmezi speciálisan a *, ?, [, ], {, }, ;, <, >, stb. karaktereket (nincs globbing), viszont pl. működik a változókra való hivatkozás és a külső parancs kimenetének behelyettesítése.
Pl.:
echo "files matching \"*.txt\": $(echo *.txt)"
Az 'aposztrófok' között a shell (szinte?) semmilyen speciális karaktert nem értelmez, még a dollárjelet sem, úgyhogy tetszőleges szöveget írhatunk aposztrófok közé betű szerint:
echo 'Value of $PATH='"$PATH"
- Magát az aposztrófot sehogyan sem tudjuk aposztrófok közé írni, mivel a backslash is elveszíti a szokásos escape-funkcióját.
- A kétféle idézőjelen belül mindig szabadon használhatjuk a másik fajtát:
echo "Couldn't change directory" echo 'files matching "*.txt":' $(echo *.txt)
A `backtickek` közé írt parancssor standard outputját a shell behelyettesíti oda, ahol a backtickes kifejezés szerepelt, de jobb ehelyett a $(zárójeles) alakot használni, mivel az szabadon egymásbaágyazható.
#!/bin/sh DIR=$(date +%Y%m%d) OPER=${@:-dist-upgrade} mkdir $DIR cd $DIR || exit 1 for i in $(echo n | apt-get -d -u $OPER 2>&1 | grep '^[[:space:]]' | tr -d '*'); do ls | grep -q ${i}_ || dpkg-repack $i done
2 Hivatkozás változó értékére
Alapvetően $változónév, de ha pl. sztringet akarunk fűzni a változó tartalmához, akkor a hozzáfűzött sztring összefolyna a változónévvel. Ennek elkerülésére: ${változónév}. Ez a szintaxis amúgy egy sereg további lehetőséget rejt magában; írhatunk mindenféle mágikus karaktert a változónév elé és mögé, amitől érdekesebbnél érdekesebb dolgok történhetnek. Hamarosan látunk erre példát.
3 Alapértelmezések használata
Ha egy változó értékét egy script felhasználója megadhatja, de nem kötelező megadnia, akkor gyakran szeretnénk valamilyen alapértelmezett értéket adni a változónak.
Az alábbi példa rossz:
FOO=defaultérték FOO=$1
Ha a scriptet paraméter nélkül hívták, FOO értéke az üres sztring lesz.
Ehelyett használhatjuk az alábbi bonyolult megoldást:
if [ -z "$1" ]; then FOO=defaultérték else FOO="$1" fi
Ez működik, de nem túl tömör.
Az if-től megszabadulhatunk a következő módon:
[ -z "$1" ] && FOO=defaultérték || FOO="$1"
De még ennél is egyszerűbb, ha csak annyit írunk, hogy
FOO=${1:-defaultérték}
4 Konfigurálható script
- A konfigurációkezelés kézenfekvő módja a VÁLTOZÓ=érték párokat tartalmazó script-töredék source-olása a "." paranccsal.
- Ha kezelünk parancssori argumentumokat:
- A script elején töltsük be a konfigurációt; azokat a defaultokat, amiket sem a konfiguráció, sem a parancssor nem ír felül, valahogy így juttathatjuk érvényre:
FOO=${1:-${FOO:-defaultérték}}
(itt ugye $1-nek volt a legnagyobb precedenciája, aztán jött a configfile, aztán a kódba drótozott default).
- A script elején töltsük be a konfigurációt; azokat a defaultokat, amiket sem a konfiguráció, sem a parancssor nem ír felül, valahogy így juttathatjuk érvényre:
- Ha nem kezelünk parancssori argumentumokat, még egyszerűbb a dolog:
- a script elején állítsunk be minden változót az alapértelmezésre, majd
- töltsük be a konfigurációt. Ami szerepel benne, az felülírja a defaultot, ami nem, az meg marad default.
5 A find használata
A find(1) nemcsak fájlok és könyvtárak keresésére jó, hanem arra is, hogy egy műveletet sok, valamilyen tulajdonság alapján kiválasztott fájlon elvégezzünk.
Erre kézenfekvő megoldás a ciklus:for i in $(find . -uid 1001); do chown 1002 "$i"; donede ennél kifinomultabbak is lehetünk, legalább háromféleképpen:
- Az xargs segítségével.
find . -uid 1001 -print0 | xargs -0 chown 1002
- Ez azért jobb, mert
- csak annyi chown-parancssor fog generálódni, amennyi szükséges (nem annyi, ahány találat van), valamint
- biztosan helyesen működik akkor is, ha a megtalált fájlok nevében szóköz, enter, idézőjel vagy egyéb nyalánkság van.
- A find beépített mechanizmusaival.
find . -uid 1001 -exec chown 1002 {} \;
- Ezt a megoldást nem nagyon szeretem, mert nem lehet tetszőlegesen rugalmasan összeállítani a végrehajtandó parancssort, mert a find közvetlenül hívja meg a megadott parancsot, nem a shellen keresztül. Végeredményben egyetlen programot hívhatunk meg, aminek a parancssorába valahová beszúrhatjuk a megtalált fájl nevét.
-
find . -uid 1001 -exec chown 1002 {} +
- Ez majdnem ugyanaz, mint az xargs-os megoldás, azzal a különbséggel, hogy a megtalált fájlok listája nem kell, hogy a parancssor végén legyen.
- A find printf akciója segítségével konstruálhatunk parancssorokat:
find . -uid 1001 -printf "chown 1002 %f\n"|sh
- Ez talán a legrugalmasabb megoldás, viszont annyi parancssorunk lesz, ahány találatunk.
Néhány konkrét példa a find használatára:
Azonos tartalmú fájlokat összehardlinkelő script (csak ésszel szabad használni!)
- Cél: hozzon létre egy ..hardlinks nevű könyvtárat;
- azon belül 0-99 nevű könyvtárakat;
- ezeken belül azokhoz a fájlméretekhez tartozó könyvtárakat, amelyek százzal osztva az adott alkönyvtár nevét adja maradékul (tehát az 1-es könyvtárban lesz az 1, a 101, 201, az 1001 s.í.t.).
- Ennek az az értelme, hogy nem lesz borzasztóan sok bejegyzés egyik konkrét könyvtárban sem (vagy legalábbis kevesebb lesz, mint ha mindet ugyanoda ömlesztenénk), így nem állítjuk kihívások elé a fájlrendszert.
- Ezeken a könyvtárakban a fellelt adott méretű fájlok SHA1 hash-éről elnevezett fájlokat, amelyeket összehardlinkel a fájllal, valamint
- symlinkeket, amelyek a fájlok helyére mutatnak és nevük tartalmazza a hash-t és az eredeti fájlnevet is.
#!/bin/zsh # # Usage: MINSIZE=n MAXSIZE=k ln-dup-files dir [dir2 [...]] OLDSUM=0 OLDNAME="" # Ha nincs megadva a MINSIZE, keressük meg a legkisebb file-t if [[ "$MINSIZE" = "" ]]; then MINSIZE=$(find $@ -type f -printf "%s\n" | sort -n | head -1) fi # Hasonlóképpen MAXSIZE esetén a legnagyobbat if [[ "$MAXSIZE" = "" ]]; then MAXSIZE=$(find $@ -type f -printf "%s\n" | sort -n | tail -1) fi # Ezt azért csináljuk, hogy a findnak mindenképpen megadhassuk alább a mérettel # kapcsolatos feltételeket; elegánsabb lenne úgy, ha aszerint, hogy melyik # méretkorlát adott, más-más find parancssort használnánk. # Bevitelkor a mezőhatár az újsor karakter legyen (a readnek fog ez kelleni) IFS=" " # Itt jobb lenne a NUL karaktert használni, mert az nem szerepelhet fájlnévben, # míg újsor igen. # Keressük a megadott alkönyvtárakban az összes fájlt, amelynek mérete a megadott # határok közé esik, és írjuk ki a teljes elérési utat, majd a fájlméretet find $@ -type f \( -size ${MINSIZE}c -o -size +${MINSIZE}c \) -a \( -size -${MAXSIZE}c -o -size ${MAXSIZE}c \) -printf "%p\n%s\n" \ | while read file; do # Ha sikerült olvasnunk egy fájlnevet, olvassuk be a hozzá tartozó méretet is, # amit a következő sorba írtunk (ahelyett, hogy NUL karaktert raktunk volna a # név és a méret közé) read SIZE if [[ "${file//..hardlinks/}" = "$file" ]]; then # Ha a fájlnév nem azzal kezdődik, hogy ..hardlinks SUM=$(sha1sum "$file" | cut -d' ' -f1) || exit 111 # Számoljuk ki a hash-t DESTDIR="..hardlinks/$[SIZE%100]/$SIZE" # Döntsük el, melyik könyvtárban kell létrehozni a hardlinket [[ -d "$DESTDIR" ]] || mkdir -p "$DESTDIR" || exit 1 # Ha nem létezik, hozzuk létre # Nézzük meg, van-e már ilyen hashről elnevezett fájl ott; ha igen, akkor # azt kell odahardlinkelni a most megtalált fájl helyére. Ha nem, akkor # a most megtalált fájlt kell erre a helyre hardlinkelni. [[ -f "$DESTDIR/$SUM" ]] \ && ln -f "$DESTDIR/$SUM" "$file" \ || ln -f "$file" "$DESTDIR/$SUM" # Mi ennek a fájlnak a teljes elérési útja? A symlink létrehozásához kell fullname="$(readlink -f "$file")" # Kis voodoo: állítsuk elő a symlink nevét hash.utolsókönyvtárnév_fájlnév.link alakban ln -sf "$fullname" "$DESTDIR/${SUM}.${fullname:h:t}_${file:t}.link" fi done
Itt láttunk többféle varázslatot is (ezeket nem kell megtanulni, csak tudni arról, hogy ilyesmik vannak, és amikor kellenek, ki lehet nézni őket a dokumentációból):
-
if [[ "${file//..hardlinks/}" = "$file" ]]; then
megnézi, hogy ha a $file tartalmából kihagyjuk azt, hogy ..hardlinks, akkor ugyanazt kapjuk-e (tehát szerepelt-e benne ez a sztring; hiszen ha szerepelt, akkor elhagyva belőle nem ugyanazt kapjuk). Ezzel egy grepet helyettesítettünk, és mivel így a ciklusmagban úsztunk meg egy külsőprogram-futtatást, jelentős az időnyereség. -
readlink -f "$file"
ezzel tudunk "kanonicizálni" egy útvonalat (relatívból abszolút, symlinkek kiiktatva). Sajnos külső program, nem shell builtin. -
${fullname:h:t}_${file:t}
a dirname és a basename megúszása zsh-san. A :h csak az útvonalat hagyja meg a változó fájlnévként értelmezett tartalmából, a :t pedig csak a fájlnevet. Az útvonalra alkalmazva a :t-t az adott fájl elérési útjában szereplő utolsó könyvtárnevet kapjuk.