Ismét egy kis parancssoros bugizás - természetesen egy személyes problémából indultam ki, de ennek nyomán sikerült a sed-be is egy kicsit beletanulni. Pontosabban, ez olyan hogy ha használom, akkor persze minden világos, ha egy darab ideig nem használom, akkor pedig vissza tudok jönni saját blogomra okosodni. :-)
Ma a sed használatával ismerkedünk meg, méghozzá konkrét problémákon keresztül. Fogjuk látni, hogy a sed-del sok mindent meg lehet csinálni, amire vannak amúgy célparancsok is a UNIX/Linux/*BSD világban. Gondolok arra, hogy ugyanaz a feladat, amire első gondolat alapján a grep-et használjuk, megoldható ugyanúgy sed-del is például (de valószínűleg a múltkor emlegetett awk-kal is). Tehát lényegében elég egy parancsot kimerítően megismerni az összes lehetőségével, de valahogy én sem ezt teszem, vezeti a megszokás a kezemet - ha egy fájlban szűrni kell, grep-elek, ha a soron belül cserélni, akkor sed utasítást faragok, ha pedig a sztringeket mezőként kell kezelni, csereberélni vagy szimplán kiíratni; akkor az awk-t. Ebből már talán kiderült, hogy a sed fő funkciója nálam az, hogy egy soronként felolvasott fájlban sztring cseréket végezzen el; és a sed a reguláris kifejezések nagy bajnoka, nekem elhihetitek.
A neve egy rövidítés: "My name is Ed, Stream Ed", hogy egy béna szóviccet mondjak. Ettől függetlenül egy kis zseni:
Minden páros vagy páratlan sor kiíratása egy text fájlból
Elkövettem egy apró bakit - szimultán futott két példány egy mérést végző programból, és elfelejtettem külön fájlba irányítani a kimeneteket. Ott volt a fájlban a mérés eredménye, de a páratlan és páros sorokban másra vonatkozott. A misszió ezek szétszedése külön fájlokba. Azt hiszem, a Windows alatt elvéreztem volna. Itt pedig:
bagoj@tarantula:~$ sed -n '1~2p' eredetifajl > paratlan_sorok.txt
Nyilván tartozom egy magyarázattal. :-)
bagoj@tarantula:~$ sed -n '2~2p' eredetifajl > paros_sorok.txt
A sed működése
Tehát a sed részére meg kell határozni két dolgot:
- Mi az, amivel valamit kezdeni akarunk
- Mit akarunk vele kezdeni
Néha az első feladat a nehezebb. Megadhatunk reguláris kifejezést, vagy meghatározhatunk sorokat. A regexp megadása a szokásos /regexp/ néven lehetséges, például a /^$/ az üres sorokra illeszkedik (^ a sor eleje, $ a sor vége, közötte nincs semmi); a /.*$/ bármire (üres sorokra is) és ezt most tényleg estig sorolhatnám. Ami viszont poénos, meghatározhatunk egy teljes részt a fájlban a kezdő és záró regexp-ek segítségével:
/start/,/stop/
Itt a "start"-ot tartalmazó sortól a "stop"-ot tartalmazó sorig fog menni.
A range meghatározása sorok kijelölésével már rafináltabb, és ezt alkalmaztuk az előbb is:
1,10 -> Vesszővel elválasztva simán az 1 - 10-es sorokat határozzuk meg
A legtutibb a regexp és a sor meghatározás kombinációja:
50,$ -> Az ötvenedik sortól a fájl végéig
1~10 -> Kezdjük az első sorral, és 10-es lépésközt használunk, azaz minden tizediket íratjuk ki
1,/start/
Mókás, nem? Az első sortól az első, "start" sztringet tartalmazó sorig fogunk haladni.
Jöhet a második rész, a "mit kezdjünk vele". Erre a sed-ben egybetűs utasítások vannak. Népszerűbbek:
a,i = sor beszúrása a range elé, illetve mögé
p = print, tehát sima kiíratás
s = substitution, azaz csere, --> s/mit/mire/
d = delete, törlés
y = átalakítás, úgy működik mint az s, csak mást csinál
= = Sorszám kiírása (igen, az egyenlőségjel)
n = next (következő sorra ugrik)
q = quit (kilép)
Van még nagyon sok parancs, tud fájlból olvasni, fájlba írni stb. de ezeket most kihagynám. :-) A sedről nyilván könyveket lehetne írni.
Még egy apróság, egyetlen kapcsolót érdemes megtanulni, ez a "-n", amit az első példában is leírtam. A sed ugyanis alapértelmezésben kiírja az összes sort. Ha mi csak bizonyos sorokat akarunk kiíratni, a -n paramétert kell használni hogy ne írjon ki semmit, és a p paranccsal íratunk ki.
Konkrét használat
Rakjuk most össze a range meghatározásokat (=mivel) a parancsokkal (=mit csinálunk). Rövid példák az előzőek felhasználásával:
sed -n '1,10 p' filenév -> Kiírja az első 10 sort
Remélem, kezdtek Ti is belendülni... tegyük fel hogy van egy logfájl, amiben szeretnénk két meghatározott dátum között listázni. Ezt grep-pel nem igazán lehet megoldani, hiszen be kellene írni a két időpont közötti összes egyező dátumot. A logfájl a szokásos módon néz ki, és a /var/log/messages alatt keletkezik:
sed -n '1,/start/ s/alma/korte/g p' -> Az első sortól a "start"-ig lecseréljük az "alma"-t "korte"-re, és az eredményt kiírjuk. A 'g' parancs a 'p' előtt azt jelenti, hogy ha egy soron belül több egyezés van, akkor mindent cseréli. Egyébként csak az első előfordulást cserélné!
Figyeljük meg, hogy mi van, ha a -n kapcsolót és a végén a 'p' parancsot elhagyjuk! (Ekkor csak a megadott sorokban cserél, de a fájl teljes tartalmát kiírja - a megváltozott sorokat természetesen megváltoztatva):
sed '1,/start/ s/alma/korte/g'
May 15 06:26:57 hostname Üzenet
Egy 15Gb-os napi fájlban nehézkes grep-pel nekiállni, ha tudjuk az időszeletet. Nekünk viszont a 08:00 és 08:40 között eltelt idő kell. Írjuk hát ezt:
May 15 06:26:58 hostname Üzenet2
sed -n '/May 15 08:00/,/May 15 08:40/p' /var/log/messages
A kimenetet pedig átirányíthatjuk más sztring feldolgozó parancsokba.
Néhány további példa:
sed '50,100 d' -> Töröljük a fájlból az 50 - 100. sorokat (és a maradék fájlt kiíratjuk).
sed -n '50,100 !p' -> Lényegében ugyanaz, az 50 - 100. sorokat nem írja ki, a többit igen
sed 'n;d' -> Minden második sor törlése. Könnyen el tudjuk képzelni a felsorolt parancsok alapján: Az n-nel lép a következő sorra, és a d-vel kitörli.
Néhány örökzöld hasznos:
sed 's/$/\r/' -> UNIX to DOS (Minden sor vége elé beszúrunk egy LF kódot)
Visszahivatkozás
sed 's/.$//' -> DOS to UNIX (feltéve, hogy mindegyik sor DOS-os! A sorvége előtti karakter töröljük, így ha valamelyik sor nem DOS-os, akkor ténylegesen törölhet egy hasznos karaktert!)
sed 's/\x0D$//' -> A bonyolultabb DOS to UNIX. A sorvége előtti 13-a s kódú karaktert (/r) töröljük
sed 's/[ \t]*$//' -> A szöveg végéről a felesleges whitespace (szóköz, tab-ok stb.) eltávolítása
sed 's/^[ \t]*//;s/[ \t]*$//' -> A szöveg elejéről és végéről a felesleges whitespace leszedése (én pl. programkódok diff-elése előtt ezt meg szoktam csinálni, mivel van az úgy hogy a behúzásokat átcserélem; és mivel ezek a sorok változtak, ezért azokat is kiírja a diff bár nekem az nem hasznos)
sed 's/alma/korte/2' -> Már írtam a 'g'-ről, ami minden előfordulást cserél. Ez itt csak a második előfordulást cseréli egy soron belül!
sed '/^$/d' -> Ahogy már írtam, törli a fájlból az üres sorokat
Természetes, hogy szükség van erre is. Ez ugye annyi, hogy vissza tudunk hivatkozni arra a (változó) tartalomra, amire a regexp vonatkozik. Példa, hogy minden szám mögé akarunk egy nullát írni, azaz 10-zel megszorozzuk őket:
sed 's/[0-9]*/&0/g'
A '&'-jel a visszahivatkozás, tehát visszaírja ugyanazt a számot, amire a mintaillesztés megtörtént, majd mögé biggyeszt egy 0-t.
Működnek a \1 - \9 -ig tartó számozott visszahivatkozók is, ha egyszerre több szövegrészre akarunk matchelni. Egy kicsit bonyolultabb példán mutatom be, hogy mondjuk van egy fájl, amelynek az elején az óra:perc formátum van, amit szeretnénk megfordítani valami miatt. Ekkor illesztünk az órára és a percre, és fordított sorrendben kiírjuk őket. Színekkel jelöltem az összetartozó dolgokat, hogy mi mire vonatkozik.
18:35 Párisba tegnap beszökött az Ősz.
18:36 Szent Mihály útján suhant nesztelen,
18:38 Kánikulában, halk lombok alatt
18:44 S találkozott velem.
sed 's/^\([0-9]*\)\:\([0-9]*\)/\2:\1/'
A félkövérrel jelölt részek tartoznak a sed-hez. :-) Tehát elkezdünk egy s parancsot, jön egy / elválasztójel, majd a caret (^), azaz a sor eleje. A piros rész illeszkedik minden számra, ami egy kettőspontig tart. A teljes piros blokk feloldása:
[0-9] -> egy darab bármilyen számra illeszkedik
[0-9]* -> nulla vagy egy vagy több számra illeszkedik
([0-9]*) -> nulla vagy egy vagy több számra illeszkedik ÉS megjegyzi visszahivatkozásként
\([0-9]*\) -> Mivel a zárójelek speciális karakterek, escape-elni kell azaz visszapert írunk elé
A kettőspont is különleges, tehát az elé is kerül egy '\'
A mintaillesztés után jön megint egy '/'-jel, ami bolddal van. Utána jön a \2:\1, ami kiírja a második illeszkedő karaktersort, egy kettőspontot és az elsőnek illeszkedő karaktersort. Remélem, világos.
Elválasztó jelek
Szerintem sokan nem ismerik a következő, igen szuper kis módszert. Tehát azt láthattuk, hogy a legtöbbet használt 's' (helyettesítés) utasításban az elválasztó jel a '/', tehát
s/alma/korte/
Mi van, ha mondjuk egy fájlnevet kell teljes elérési úttal cserélni? Dolgozhatunk úgy is, hogy escape-eljük a sok perjelet:
sed 's/\/home\/bagoj\/bin/\/usr\/bagojne/'
Ugye, milyen ronda? :-) Emiatt sokan utálják a sed-et, és nehéz is olyan sort írni, ami minden esetben működik. De elválasztójelnek használhatjuk a _, : és | jeleket is!
sed 's:/home/bagoj/bin:/usr/bagojne:g'
Sokkal jobb, nem? Szinte a zsenialitás határát súrolja...
Bár nem volt szándékomban egy átfogó, részletes, egymásraépülő anyagot összehozni, csak összedobáltam ami eszembe jutott, remélem ezzel mégiscsak segítettem. Jó "szedálást"! :-)