Shell-programozás
(→Alapértelmezések használata: konfigurálható script) |
(→Konfigurálható script: a find használata) |
||
79. sor: | 79. sor: | ||
** a script elején állítsunk be minden változót az alapértelmezésre, majd |
** 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. |
** töltsük be a konfigurációt. Ami szerepel benne, az felülírja a defaultot, ami nem, az meg marad default. |
||
+ | |||
+ | == A <tt>find</tt> használata == |
||
+ | |||
+ | A <tt>find(1)</tt> 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: <pre>for i in $(find . -uid 1001); do chown 1002 "$i"; done</pre> de ennél kifinomultabbak is lehetünk, legalább háromféleképpen: |
||
+ | |||
+ | * Az <tt>xargs</tt> segítségével. <pre>find . -uid 1001 -print0 | xargs -0 chown 1002</pre> |
||
+ | * 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 <tt>find</tt> beépített mechanizmusaival. <pre>find . -uid 1001 -exec chown 1002 {} \;</pre> |
||
+ | ** 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. |
||
+ | * <pre>find . -uid 1001 -exec chown 1002 {} +</pre> |
||
+ | ** Ez majdnem ugyanaz, mint az <tt>xargs</tt>-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 <tt>find printf</tt> akciója segítségével konstruálhatunk parancssorokat: <pre>find . -uid 1001 -printf "chown 1002 %f\n"|sh</pre> |
||
+ | ** Ez talán a legrugalmasabb megoldás, viszont annyi parancssorunk lesz, ahány találatunk. |
||
+ | |||
+ | Néhány konkrét példa a <tt>find</tt> 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. |
||
+ | <pre> |
||
+ | #!/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 |
||
+ | </pre> |
||
+ | |||
+ | 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): |
||
+ | |||
+ | * <pre>if [[ "${file//..hardlinks/}" = "$file" ]]; then</pre> 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 <tt>grep</tt>et helyettesítettünk, és mivel így a ciklusmagban úsztunk meg egy külsőprogram-futtatást, jelentős az időnyereség. |
||
+ | * <pre>readlink -f "$file"</pre>ezzel tudunk "kanonicizálni" egy útvonalat (relatívból abszolút, symlinkek kiiktatva). Sajnos külső program, nem shell builtin. |
||
+ | * <pre>${fullname:h:t}_${file:t}</pre> a <tt>dirname</tt> és a <tt>basename</tt> 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. |
A lap 2007. október 29., 23:39-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 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}
3 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.
4 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.