HTML

Bagoj úr blogja

Kíváncsi Bagoj befigyel a Linux belsejébe, illetve különféle Linux terjesztéseket próbál ki. Ha jó napja van, scriptet ír Neked.

Friss topikok

Még rondább, de frankó - SED, a Stream EDitor

2009.05.22. 11:22 bagoj ur

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
bagoj@tarantula:~$ sed -n '2~2p' eredetifajl > paros_sorok.txt
Nyilván tartozom egy magyarázattal. :-)

A sed működése

Tehát a sed részére meg kell határozni két dolgot:

  1. Mi az, amivel valamit kezdeni akarunk
  2. 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
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
A legtutibb a regexp és a sor meghatározás kombinációja:

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
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'
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:

May 15 06:26:57 hostname Üzenet
May 15 06:26:58 hostname Üzenet2
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:

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)

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
Visszahivatkozás

 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"! :-)

1 komment

Címkék: bash parancssor

A bejegyzés trackback címe:

https://bagojur.blog.hu/api/trackback/id/tr991123005

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

Psycho Dad 2009.05.22. 20:41:46

Egy újabb gyöngyszem, köszönjük!
Többször olvastam már fórumozó emberektől, hogy a sed milyen jó, de akárhányszor nekiálltam volna megtanulni mindig túl bonyolultnak tűnt az általam talált anyagok alapján, viszont ezen leírás után kezdek hinni nekik. :)
Ha valakinek legközelebb gondja akad a használatával tuti ezt a cikket fogom linkelni. ;)

A vim-mel vagyok még hasonló helyzetben, lassan nem ártana annak a kezelését is elsajátítanom.