Shell-programozás

A Unix/Linux szerverek üzemeltetése wikiből
A lap korábbi változatát látod, amilyen KornAndras (vitalap | szerkesztései) 2007. november 6., 01:16-kor történt szerkesztése után volt.

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.

Ha nem tudjuk, hogy a változó értékben nem lehet szóköz vagy egyéb speciális karakter, mindig idézőjelek között hivatkozzunk rá!

Az alábbi script üres $1 esetén hibaüzenetet ad, szóközt tartalmazó $1 esetén pedig hülyeséget csinál; ha ügyesek vagyunk, ilyen jellegű hibákat tartalmazó scriptbe akár kódot is injektálhatunk alkalmas paraméterezéssel. A zsh az ilyen hibák egy részétől megvéd (mert külön kérés nélkül általában nem bontja szavakra a változók értékeit, amikor behelyettesíti őket), de ez nem jelenti azt, hogy a zsh-ban szabadon lehagyható az idézőjel az ismeretlen tartalmú változó neve mellől!

#!/bin/sh
if [ $1 -gt 0 ]; then
    echo positive
fi

Sok scriptben láthatunk ahelyett, hogy [ -z "$1" ] vagy [ "$1" = "" ] olyat, hogy [ x$1 = x ]. Ez Rossz, Értem? Ha $1-ben szóköz van, összezavarja a [ parancsot (merthogy a shellben ez is egy parancs, nem szintaktikai elem).

3 C-jellegű aritmetika

foo=1
((foo++))
echo $foo
# 2-t ír ki

Vagyis nem kell szenvednünk az expr paranccsal, és a foo=$[foo+1] jellegű szintaxist sem kell erőltetnünk.

Egyébként C-szerű for-ciklust is csinálhatunk:

for ((i=0;i<10;i++)) {
    echo $i
}

Bár a preferált szintaxis ez:

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

4 Parancssor feldolgozása

Nyilván szeretnénk olyasmi argumentumfeldolgozást, mint a GNU programokban: --hosszú-opció=érték stb.

Erre legalább két jó módszer van; általában rossz módszer az, ha előírjuk, hogy mi legyen az első, a második, harmadik stb. argumentum szerepe. Jobb a felhasználóra bízni, milyen sorrendben kényelmes neki megadni a kapcsolókat (pl. lehet, hogy nem az összeset akarja használni).

Az egyik jó módszer: getopts (bashben is van, de lehet, hogy nem pontosan úgy működik, mint az itt bemutatott zsh-féle).

#!/bin/zsh -e
#
# usage:
#
usage='build-vserver [ -a architecture ] [ -c contextid ] [ -d distribution ]
        [ -h hostname ] [ -f iface ] [ -i IP ] [ -n netmask ]
        [ -l lvprefix ] [ -m {mirrorhost|proto://mirror/path} ]
        [ -g volumegroup ] [ -v vservername ] [ -x ssmtp_mailhub ]
        [ -z ]
        [ -u volume-config-string [ -u volume-config-string [ ... ]]]'
# itt most egy csomó sort kihagyunk
while getopts :a:c:d:h:f:i:n:l:m:g:v:u:x:z OPT; do
    case ${OPT} in
        a)
            ARCH="$OPTARG"
            ;;
        c)
            CTX="$OPTARG"
            ;;
        d)
            DIST="$OPTARG"
            ;;
        h)
            HOSTNAME="$OPTARG"
            ;;
        f)
            IFACE="$OPTARG"
            ;;
        i)
            IP="$OPTARG"
            ;;
        n)
            MASK="$OPTARG"
            ;;
        l)
            LVPREFIX="$OPTARG"
            ;;
        m)
            MIRROR="$OPTARG"
            ;;
        g)
            VOLUMEGROUP="$OPTARG"
            ;;
        v)
            VSERVER="$OPTARG"
            ;;
        u)
            VOLUMECONF=($VOLUMECONF $OPTARG)
            ;;
        x)
            MAILHUB="$OPTARG"
            ;;
        z)
            VOLUMECONF=()
            ;;
        *)
            echo "${usage}" >&2
            exit 2
            ;;
    esac
done
  • A getopts paraméterlistájában a kettőspont azt jelzi, hogy az előző kapcsolóhoz tartozik argumentum; a z-nek nincs, ezért nincs utána kettőspont (nem azért, mert ő az utolsó).
  • Az első kettőspont arra jó, hogy ne adjon hibaüzenetet, ha nemlétező kapcsolót talál (ezt az esetet mi magunk kezeljük a case-ben).

Egy másik jó módszer: while, shift, case. Az alábbi példa egy olyan shellfüggvény-gyűjteményből származik, ami iptables-tűzfalscriptek írását teszi kevésbé fáradságossá:

    while [[ ! "$1" = "" ]]; do
        case "$1" in
            -c)
                ;&
            --chain)
                shift
                local CHAIN="${1:-${PORT}_input}"
                ;;   
            -a)
                ;&
            --aclfile)
                shift
                local ACL="${1:-$MYACLDIR/${PORT}}"
                ;;   
            -n)
                ;&
            --name)
                shift
                local FRIENDLYNAME="${1:-tcp/$PORT}"
                ;;   
            *)
                echo buildtcpchain ignoring unknown parameter \""$1"\".
                ;;   
        esac
        shift
    done
  • Látható, hogy így tudunk hosszú kapcsolókat is kezelni, nemcsak rövideket, meg olyan kapcsolókat is, amelyeknek két argumentuma van, stb.

5 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}

6 Konfigurálható script

  • A konfigurációkezelés kézenfekvő módja a VÁLTOZÓ=érték párokat tartalmazó script-töredék ("configfile") source-olása a "." paranccsal.
  • Ha kezelünk parancssori argumentumokat:
    • A script elején töltsük be a konfigurációt:
      [ -r "$CONFIGFILE" ] && . "$CONFIGFILE"
      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, itt "defaultérték").
  • 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.

7 A shell opciói

A legtöbb shellben mind a parancssorban, mind működés közben temérdek opciót tudunk állítgatni (elsősorban ki- ill. bekapcsolni). A parancssorban az opciókat egyszerűen a shell neve után írjuk:#!/bin/sh -e, magában a shellben pedig a set -e, set +e parancsokkal tudjuk kapcsolgatni őket (itt konkrétan az e opciót).

Ezek közül az opciók közül különösen hasznos az alábbi kettő:

  • -e: sikertelen visszatérési értékű parancssor végrehajtása után a script kilép
    • Akkor jó, ha nem foglalkoztunk igényes hibakezeléssel, viszont
    • esetleg katasztrófával járna, ha a script némelyik sikertelen művelet után úgy folytatná a munkáját, mintha mi sem történt volna. Pl:
cd "$1"; rm -rf *.csv
Ha a script nem tud beváltani a megadott könyvtárba, az aktuális munkakönyvtárból kezdene törölni.
  • Az alapértelmezés szerinti +e állapotban a script nem lép ki, ha valamelyik parancssor végrehajtása sikertelen (de amúgy szintaktikailag helyes).
  • -x: minden parancssort kiír a script végrehajtása során.
    • Hibakereséskor nagyon hasznos.

8 Mezőkre osztott input feldolgozása

A /etc/passwd kettőspontokkal elválasztott mezőket tartalmaz. Olvassuk ki egy egyszerű shellscripttel a bela user emberi nevét!

OLDIFS="$IFS"
IFS=":"
while read username x uid gid gecos homedir shell; do
    [ "$username" = "bela" ] && echo "A bela neve: $gecos" && break
done </etc/passwd
IFS="$OLDIFS"
  • Persze igazából mindegy nekünk, milyen mezők jönnek még a gecos nevű után, úgyhogy a while-sort így is írhattuk volna:
     while read username x uid gid gecos mindenmas; do 
  • Az IFS értékét célszerű visszaállítani az alapértelmezettre, ha elállítottuk, nehogy a script egy későbbi részében meglepetés érjen.
  • Szépséghiba, hogy a gecos-mező a usernek nemcsak a nevét, hanem néhány egyéb adatát is tartalmazza, vesszőkkel elválasztva. Ezzel most nem törődünk.

(Persze ennek a feladatnak a valódi megoldása nem a fenti, hanem mondjuk az, hogy getent passwd bela|cut -d: -f5.)

A read amúgy egy echo-ból is tud olvasni, így feldarabolhatjuk a valahonnan olvasott vagy a user által megadott sztringet a mezőhatárok mentén: echo "$változó" | read mező1 mező2 mező3 ...

9 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"; done
de 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 adják 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.

Töröljük most ebből a ..hardlinks könyvtárból azokat a fájlokat, amelyekhez már nem tartozik másik hardlink!

find ..hardlinks -type f -links 1 -print0 | xargs -0 rm

A find még rengeteg szempont alapján tud keresni: jogosultságok, dátumok, reguláris kifejezések stb. Részletesebben l. man find.

10 Barátunk, a sed

sed == stream editor. Alapvetően az stdinről olvasott szöveg-streamen végez programozható átalakításokat, és az eredményt az stdoutra írja. Persze tud fájlból is dolgozni.

  • Programozható, de elég write-only.
  • Megírható benne a hanoi tornyai, vagy egy inverz lengyel notációt használó számológép; minden bizonnyal egy webszerver is.
  • Kiválóan alkalmas arra, hogy sor-szinten strukturált szövegformátumokat egymásba átalakítsunk.
  • Egyszerre valósítja meg a cut, a grep, a tr, a head és a tail parancs (és még valószínűleg több másik) funckionalitását.

Itt most nem tanuljuk meg teljes mélységében, csak kicsit megkarcoljuk a felszínt néhány konkrét alkalmazás bemutatásával.

10.1 Reguláris kifejezések

A reguláris kifejezés egy szövegminta, pl. egy rendszám: három nagybetű, kötőjel, három számjegy. Ez így nézne ki: [A-Z][A-Z][A-Z]-[0-9][0-9][0-9].

Arra jó, hogy jellegzetes mintákat keressünk szövegben; pl. egy szövegfájlban szereplő összes magyar rendszámot gyűjtsük ki. Vagy pl. egy naplófájl alapján számoljuk össze, hány esemény történt 21:00 és 22:00 között, napokra lebontva.

Többféle szintaxis van (elterjedt pl. a POSIX és a perl); itt most a GNU egrep által támogatott bővített (extended) szintaxist ismerjük meg.

A reguláris kifejezéseket rekurzívan szokás definiálni:

  • Reguláris kifejezés minden olyan karakter, amelynek nincs speciális jelentése; pl. a. Ez pontosan egy darab a betűre illeszkedik. A speciális jelentéssel rendelkező karaktereket a backslash ("\") karakter megfosztja a speciális jelentéstől.
  • A szögletes zárójelek segítségével karakterosztályt definiálhatunk: [A-Fa-f0123456789]. Ha a karakterosztály első karaktere a kalap ("^"), akkor az osztály egy darab olyan karakterre illeszkedik, amely nincs a felsoroltak között; ha nem, akkor egy olyan karakterre, amely a felsoroltak között van.
    • A tartományokkal vigyázzunk; a locale-től függően [a-d] jelentheti [abcd]-t és [aBbCcDd]-t is.
    • Vannak előredefiniált karakterosztályok, pl. [[:alnum:]]; ezeket kombinálhatjuk egyéb karakterekkel: [,.%:[:alnum:]_/*]. Ezeknek az az előnye, hogy locale-függőek, tehát pl. magyar locale esetén az [[:alpha:]] osztálynak részei a magyar ékezetes betűk is, míg angol locale esetén nem.
  • A . (pont) karakter egy darab tetszőleges karakterre illeszkedik.
  • A ^ (kalap) a sor elejére illeszkedik, tehát pl. a ^a minden olyan sorra illeszkedik, amely a-betűvel kezdődik.
  • A $ (dollárjel) a sor végére illeszkedik, tehát pl. a ^a$ minden olyan sorra illeszkedik, amely pontosan egy darab a-betűt tartalmaz.
    • Ritkábban szokott kelleni, de létezik: \< a szó eleje, \> a szó vége, a fentiekhez hasonlóan.

A következő posztfix "ismétlőoperátorok" értelmezettek:

  • A csillag karakter az őt megelőző reguláris kifejezés értelmét módosítja úgy, hogy nulla vagy bárhány illeszkedést megenged. a* jelentése: üres sztring, vagy tetszőleges hosszúságú kizárólag a betűkből álló sztring.
  • A plusz karakter hasonló, de az üres sztringet nem engedi meg. a+ jelentése: legalább 1 karakter hosszúságú kizárólag a betűkből álló sztring.
  • A kérdőjel az őt megelőző reguláris kifejezést opcionálissá teszi. a? jelentése: nulla vagy egy darab a betű.
  • a{n,m}: legalább n, legfeljebb m darab a betű. m elhagyható, ekkor csak legalább n. Ha a vesszőt is elhagyjuk, akkor pontosan n, tehát a rendszám pl. [A-Z]{3}-[0-9]{3}.

Egyéb szabályok:

  • Két reguláris kifejezés konkatenáltja azokra a sorokra illeszkedik, amelyekben az első kifejezésre illeszkedő sztringet a másodikra illeszkedő sztring követ.
  • Két reguláris kifejezést összekapcsolhatunk a | (pipe) infix operátorral. Az így létrehozott kifejezés minden olyan sorra illeszkedik, amely a két eredeti kifejezés közül legalább az egyikre illeszkedik. (Pl. alma|barack azokra a sorokra illeszkedik, amikben az "alma" vagy a "barack" szó, vagy mindkettő előfordul (sorrendtől függetlenül).

Precedenciaszabályok:

  1. ismétlés (alma* illeszkedik pl. arra, hogy "alm", "alma", "almaa", "almaaa", s.í.t.)
  2. összefűzés (alma|barack jelentése értelemszerű, nem pedig "alm" majd "a" vagy "b" majd "arack")
  3. VAGY-kapcsolat

Ezeket zárójelezéssel felülbírálhatjuk, pl. ^((alma|barack)*|(körte.*fókazsír))$.

A zárójeles kifejezésekre "visszahivatkozhatunk" a \szám konstrukcióval: ^(alma|barack)\1*$ (olyan sor, amiben csak az alma, vagy csak a barack szó szerepel, legalább egyszer).

Néhány példa:

  • páros szám: [0-9]*[02468][^0-9]|$
  • két nagybetűvel kezdett szó egymás után (pl. név): \<[[:upper:]][[:lower:]]* [[:upper:]][[:lower:]]*\>

10.2 grep-emuláció seddel

sed -n '/regex/p'

Amit érdemes megfigyelni:

  • -n kapcsoló: nem ír ki minden sort, csak azokat, amiket a p paranccsal kiíratunk.
  • /regex/: feltétel; az adott reguláris kifejezésre illeszkedő sorokra hajtja végre a megadott parancso(ka)t.
    • A sed igazából címzésnek tekinti.

Az illeszkedő sorok megkettőzése:

sed -n '/regex/{p;p}'

Vagyis két "print" parancsot is végrehajtunk.

Ha a többi sor is kell, de az illeszkedők kétszer:

sed '/regex/p'

10.3 Sorok törlése

A grep -v emulációja: sed '/regex/d'. A d parancs törli az illeszkedő sort.

10.4 Az s, mint svájcibicska sed-parancs

  • s/foo/bar/ foo első előfordulását bar-ra cseréli minden sorban;
  • s/foo/bar/g az összeset;
  • s/foo/bar/2 csak a másodikat;
  • /baz/s/foo/bar/g az összeset, de csak a "baz"-t tartalmazó sorokban;
  • /baz/!s/foo/bar/g az összeset, de csak a "baz"-t NEM tartalmazó sorokban.

Ebben az a pláne, hogy "foo" és "baz" lehet reguláris kifejezés, "bar"-ban pedig hivatkozhatunk ennek a részeire.

Pl. rendszámban számok és betűk felcserélése, a konkrét rendszám ismerete nélkül, általánosan:

sed -r 's/([A-Z][A-Z][A-Z])-([0-9][0-9][0-9])/\2-\1/g'
  • -r: bővített reguláris kifejezések használata (enélkül \ kell a zárójelek elé).

A / helyett írhatunk bármilyen más karaktert is, csak mindhárom helyen ugyanazt kell használni. Pl. ssfoosbars. :)

10.5 sed példák

  • szóközök törlése sorok végéről: sed 's/[[:space:]]*$//'
  • blokk kommentezése (minden sor elejére hashmark beszúrása: sed 's/^/#/'
  • fájlok csoportos átnevezése:
find . -name "*.mp3" | while read i; do mv -i "$i" "$(echo $i | sed 's/^[0-9]*//;s/_-_/-/g;s@./albums/@singles/@')"; done
* backslash-sel végződő sorokhoz fűzzük hozzá a következő sort: <pre>sed -e :a -e '/\\$/N; s/\\\n//; ta'
  • keressük azokat a sorokat, amikben AAA, BBB és CCC tetszőleges sorrendben előfordul:
    sed '/AAA/!d; /BBB/!d; /CCC/!d'
  • regexp első előfordulásától kezdve az összes sort írjuk ki: sed -n '/regexp/,$p'
  • írjuk ki a 8-12. sorokat: sed -n '8,12p' vagy sed '8,12!d'
  • írjuk ki az 52. sort: sed -n '52p' vagy sed -n '52!d' vagy sed '52q;d'
  • írjuk ki azokat a sorokat, amelyek regex1 első és regex2 első előfordulása közé esnek: sed -n '/regex1/,/regex2/p' (ha p helyett d, és -n nélkül, akkor csak ezeket a sorokat NE)

További példák pl. a http://sed.sourceforge.net/sed1line.txt címen találhatók.

Személyes eszközök