Reguláris kifejezések, regex, regular expression - a mumus, amitől félnek

Reguláris kifejezések, regex, regular expression. Ezek a szavak, amitől sokan félnek. Különös karakter sorok, amiket a nagy guruk használnak, és egyszerű ember át sem látja… Igen, ez így is van. Legalábbis profi szinten megírtakat egy sima felhasználó nem egyszerűen tudja dekódolni, megérteni. ^\[0-9a-z\\.-\]+@(\[0-9a-z-\]+\\.)+\[a-z\]{2,4}$ Bár az első látásra ezeket nehéz megtanulni, de alapokat érdemes elsajátítani. A magyarul szabályos kifejezéseknek fordítható eszközök lényegében (nagyon leegyszerűsítve) olyan karakterek, amikkel helyettesíteni tudunk egy, a program által másképp nehezen értelmezhető kifejezést, szabályt. Én, mint ember minden további nélkül megértem, hogy azokat a fájlokat keressem meg, amiben az első akárhány karakter szám, függetlenül a számok értékétől és a mennyiségétől. Cseréljem ki az összes nagybetűt kicsire a szövegben. Keressem meg a telefonszámokat az adott szövegben és másoljam ki, vagy az összes email címet, mert spamelni akarok… Gondoljuk át mi is a email cím? Betűk, számok, egyéb karakterek, majd egy kukac, de az is lehet, hogy cselesen nem a @ jelet használja, hanem azt írja oda, hogy (kukac), majd megint betűk, számok, azután egy pont és azután meg szintén jönnek a betűk… Na, ezt te látod, de egy gép nem. Így kitalálták ezeket a a regex-eket, amikkel sok ilyen, nekünk egyértelmű, de programoknak már kevésbé egyértelmű szabályokat adni.

Miért kell(ene) minden Linuxosnak ezeket a regex-et ismerni?

Mert jellemzően az a tévhit terjedt el, hogy csak a programok belsejében használják a programozók, vagy csak a terminálguruk a sed, grep és hasonló félelmetes(en jó) parancssoros megoldásban. Már azért is jó lenne, ha alapszinten ismernéd, mert ezekben a parancssoros eszközökben jó hasznát veszed, de pld. a bemutatott átnevezőben is igencsak sokszor kellenek. Jogos a felvetés, hogy ha neked nem kellenek a CLI programok, akkor minek ismerd a regex-eket? Mert sok szövegszerkesztőben, ami grafikus, a csere funkcionál kapunk olyan lehetőséget, ami ezeket is felhasználja. Illetve a fejlettebb fájlkereső, átnevező eszközökben is használhatod ezeket. A Krename, vagy az általam bemutatott FileRenamer is teljes mértékben ismeri ezeket a lehetőségeket.

Mi a probléma a reguláris kifejezésekkel?

Alapvetően semmi, de mint ahogy megszoktuk a kép nem ennyire szép, mint ahogy az elején gondolnánk. Itt is többféle, nem teljesen egyforma megvalósítás létezik. Bár az eltérések a mi szintünkön maximum bosszantóak, de érdemes megnézni ha egy program, vagy script nem úgy működik, ahogy szeretnénk, hogy a program éppen milyen reguláris kifejezések ismer. Az eltérések minimálisak, így nem lesz problémán ezzel, jellemzően a programozóknál jelentkezik az eltérő programnyelveknél.

Alapok elegendőek?

Igen, egy átlag felhasználónál nem gyakori, hogy az alapokon kívül a haladó szintet is használná. De azokat mindenképp érdemes kijegyzetelni, vagy akár az interneten található összefoglalókat letölteni, kinyomtatni, mert ha megkedveled ezeket nagyon sok helyen használni tudod.

Az első lépés a reguláris kifejezésekkel

Ezt már a legtöbb Linux felhasználó megtette akkor, amikor az fájl törlésnél ilyen, vagy hasonló megoldást használt: rm \*.jpg, azaz minden jpg kiterjesztésű fájlt szerette volna kezelni. Az, hogy mit teszel, keresel, vagy cserélsz, vagy töröl ebből a szempontból mindegy: egy viszonylag bonyolult dolgot egy reguláris kifejezéssel adtál meg.

Ebből is látni, hogy a reguláris kifejezések használat egyszerű. Megfogalmazod pontosan azt, amit ki akarsz fejezni, majd ahhoz megadod a reguláris kifejezésekkel összerakott "kódot". Ezt az elgondolást követem a bemutatáskor is. Ha szeretnéd tesztelni, és egy programot is megismerni, akkor javaslom a rnm-et, ami egy fájl átnevező program. Készíts egy könyvtárat, abban megfelelő fájlokat hozz létre, melyek neve illeszkedik a mintára és valóban teszteld le. De akár egy szövegszerkesztőben a keresésnél is megadhatod a minta reguláris kifejezéseket, olyan szövegre, amiben illeszkedő szavak vannak. Azaz nem csak olvasni kellene, hanem gyakorolni is.

* Ez a legfontosabb reguláris kifejezés. Lefordítva bármiből, bármennyi. Azaz, ha azt adod meg, hogy

rnm -rs '/_/ /g' ./*.*

akkor az minden, bármilyen karaktert tartalmazó fájlra, bármilyen kiterjesztésre, vagy annak a hiányára végre lesz hajtva. Maga a csere, vagy akármilyen művelet ebből a szempontból mindegy, a ./ a könyvtárat jelöli, amiben vagyunk abban keressen.

rnm -rs '/_/ /g' ./*.jpg

Itt már a bármennyi, és bármi addig a karakterig igaz, amit azután megadtunk, azaz csak a PONTOSAN egy pont, majd PONTOSAN jpg karaktersorig. Nagyon hasznos a konkrét kiterjesztések megadásához: csak azokkal a fájlokkal dolgozik, aminek a kiterjesztése .jpg.

Egy, vagy több konkrét karakter megadásához

Ez már ismert (.jpg), de variálható a * karakterrel.

rnm -rs '/_/ /g' ./a*.jpg

Csak az „a”-vel kezdődő, bármilyen hosszú, és bármit tartalmazó, de .jpg végű fájlokkal foglalkozik.

Ezek után

rnm -rs '/alma*/ASD/g' ./*.jpg

feloldása egyszerű lesz: minden olyan részt a fájlnévben (vagy szövegben, a szövegszerkesztőben használod) ami almafa, almahéj, almamag, almaakármidelehetbármi lecseréli ASD-re.

A pont hasznos karakter

.

A pont egy érdekes megoldás, mert ez pontosan egy, de meg nem határozott karakter jelöl. Mire illeszkedik? Bármire, egy karakterre, kivéve az új sor karakterre.

.lma lehet alma, ilma, 1lma, vagy bármi. De lehet zöldalma is, hiszen nem adtuk meg, hogy csak az elején lehet egy karakter és azt kövesse lma. Így ha cseréled a .lma karaktersort, akkor a

rnm -rs '/.lma/ASD/g' ./*.jpg

a zöldalmából is kicseréli a .lma-ra illeszkedő almát…

Több pontot is használhatunk, így megadhatjuk pontosan azt a karakterszámot, amit bármivel akarunk helyettesíteni.

rnm -rs '/_..._/ASD/g' ./*.jpg

Kicseréli azokat a pontosan HÁROM karaktert tartalmazó részt, amit _ határol.

Egy karaktert már cseréltünk, az aláhúzást, szóközre: rnm -rs '/\_/ /g' ./\*.jpg. De mi van ha van olyan fájl, amiben több aláhúzás is követi egymást, de csak egy szóköz lenne az ideális? Itt használhatunk számszerűsítőket, ami konkrétan megadja az előtte lévő kifejezés előfordulási darabszámát:

\\_* nulla vagy több alkalommal forduljon elő az aláhúzás jel. Ezzel cserénél vigyázni kell, mert sok esetben nem a vár eredményt adja. Előbb próbáld ki az adott programmal, az adott programozási nyelvel. Ez egy általános tipp, mert mindig és mindet előbb kipróbálunk, csak azután engedünk rá az éles munkára!

_+ egy vagy több, azaz legalább egy _, de bármennyi aláhúzás karaktert egyben lecserél. Ezzel meg is oldottuk a "bármennyi aláhúzás van egymás után az legyen egy üres helyre cserélve" kérdést.

_? Nulla vagy egyszer fordul elő. Ezzel cserénél óvatosan bánjunk, elég érdekes eredményt ad, ami logikus is. De alkalmas olyan keresésre, amiben azokat a szavakat válogatjuk le, amiben egyszer talál aláhúzást, vagy nincs benne.

_{2} Pontos kétszer fordulhat elő, azaz __, de nem _ vagy ____

_{2,5} Kettő és öt közti bármilyen darab lehet: __ ___ ____ _____

_{2,} Kettő vagy több alkalommal fordulhat elő. Az egy aláhúzás nem, a több már igen! Igen alkalmas pld. a szóközök számának a rendbetételére, ha lecseréled a kettő, vagy több szóközt egyre.

Ideális lenne, ha meghatározhatnánk, hogy csak a betűket, vagy csak a számokat kezelje. Azaz pár általánosan használt, egynemű karakterhalmazt egyszerűen lehessen megadni.

[a-z] egy, akármilyen kisbetűt jelöl [A-Z] egy, akármilyen nagybetűt jelöl [A-Za-z] egy betű, mérettől független [0-9] egy, akármilyen szám.

Az „egy, akármilyen szám” és a + nagyon alkalmas az összes számot kitörölni a fájlnévből.

Ezt finomíthatod is, ha csak a [0-3] számmal, vagy a [a-e] azaz a, b, c, d, e karaktereket akarod kezelni.

De nem csak tól-ig szerkezetet adhatsz meg, hanem sima felsorolást is:

A klasszikus magyar példa: b[éai]ka, ami illeszkedik a béka, baka, bika szóra is, de nem jó a boka szóra.

Kizárás, azaz a nem olyan, mint amit definiáltunk megadása: ^

De nem mindegy, hogy hol van, ezt csak elszeparált, azaz zárójeles szerkezetben használhatod így. Mert ez a "eleje valaminek" értelemben lesz lefordítva, ha nem teszed zárójelbe. Elég trükkösen tudják egyes programok ezt használni, így ezt is ki kell próbálni.

Az előbbi példánál maradva: b[^i]ka, bármi olyan szó, amiben b-t nem i követi, azt pedig a ka követi. Béka, baka, blka, boka, bxka stb. illeszkedik. Bár ez így önmagában nem túl érdekes, de időnként jól jön.

Megoldható, hogy a „nem szám”, vagy „nem kis betű” karaktereket is kezelheted.

Miben más a „nem szám” [^0-9] mint a [A-Za-z]? Itt már ami nem szám, az lehet bármilyen más karakter is, nem csak betű, hanem írásjel is például. Bár ez nagyon egyszerű példa, de jól mutatja a regex-ek egyik tulajdonságát: nagyon szó szerint veszik a parancsaidat és jól át kell gondolni amit akarsz. Az egyik ilyen példa, amikor a számokat és egyéb karaktereket el akarod tüntetni és közben a szóközt is kikapja.

Van pár, de nem mindenhol alkalmazható hasonló egyszerűsítés:

\d egy számjegy, én a [0-9]-et jobban szeretem \D egy NEM számjegy, hanem bármi más \s szóköz, ami időnként jól jön, ha valami előtt, után szóköz van és azt fontosnak tartod \S nem szóköz, bármi más \t tabulátor \w egy szó, ami csak betűket tartalmaz \W egy nem szóként azonosítható karaktersor

Ezek nagy része kiváltható, de szintén igaz, hogy ha tudod, hogy van ilyen, akkor használni is tudod majd, ha kell. És még sok egyéb is létezik, de jelenleg ezek is messze túlmutatnak az igényeinken.

Jó lenne, ha azt is meghatározhatnám, hogy a minta elején, vagy a végén van egy regex helye, és csak ott keresse a program. Tipikus megoldás, ha arra van szükséged, hogy a fájlnév elejéről, vagy végéről levágd az üres helyet, vagy akár egy bizonyos darab karakter.

^ jellel (AltGr + 3) az elején lévő egyezést keresi csak.

A ^ökör kifejezésnek megfelel a ökörnyál minta, a hatökör viszont nem. Figyelj rá, itt nincs szeparálva egy zárójellel. Mit jelent a ^[0-9] és mit a [^0-9] kifejezés? Gondold át! Azt is, melyik jelöli a linux-al kezdődő szavakat, és melyik nem: ^linux* vagy a [^linux]* kifejezés.

Nemrég kaptam egypár fájlt, aminél az első karakter _ volt. Ami rondán néz ki. Így le akartam vágni. De a fájlokban is volt elválasztónak (sok rendszer rosszul kezeli a fájlnevekben a space karaktert, így már automatikusan kicseréltetjük aláhúzásra) ilyen. Más sejthető, hogy mit kell tenni

rnm -rs '/^_//g' ./*.*

Itt „az elején van” értelemben használatos a ^ jelet, a [^_] pedig "minden más, ami nem aláhúzás" értelemben. Na ezért szoktak erős ellenérzést táplálni a regex-ekkel szemben… Annyira egyszerű eltolni egy pici semmiségen a kifejezést, hogy sok esetben elmegy a kedve az embernek ettől.  Öntevékenyen oldd meg, hogy az első három, csak szám karaktert vágja le, de csak akkor, ha az szám. Nem nehéz, csak fel kell használni a jelenlegi tudásunkat.

A ^ testvére a $ (Alt Gr + é) ez a "végén keress"-nek felel meg. Azaz, ha egy szabályt csak a végére akarsz illeszteni, akkor ezt használod. Az előzőhöz hasonló kulturálatlan fájlnevű csomagot is kaptam. Ahol a végén (valószínűleg töröltek valamit, és időnként nem túl jól sikerült) ott fityegett egy space: fájl_neve .jpg ez elég ronda, és zavaró is ha scriptekkel dolgoztatom fel a fájlokat, mert sok esetben az üres helyet nem, vagy csak elég furán kezelik.

rnm -rs '/ $//g' ./*.*

meg is oldotta.

Most keresd ki azokat a szavakat egy szövegből, melyek öt betűsek, és p-vel kezdődnek, és k a vége. ^p...k$ az egyik megoldás, De a három pontot helyesíthetjük .{3} -al is. Ezeket a ^ és $ horgonyoknak hívják, mert az elejére, végére horgonyozzuk le a szabályt.

Vagy kapcsolatok a szabályos kifejezésekben

Azt már tudjuk, hogy egy helyre több lehetséges egyezést megengedő karaktert is rakhatunk. A pék, pók szavakat a p[éó]k megoldással kereshetjük meg, de mi van ha a kalap és a kabát szavakat együtt keressük meg valami különös okból? Ilyenkor nem két karakter közül lehet választani, hanem egy hosszabb választási felsorolást, egy vagylagos megoldást kell alkalmazni.

| jelet használjuk és () zárójel közé szeparáljuk el a választási lehetőségeket.

Azaz a ka(lap|bát) lesz a megoldás a problémára. Bár elvileg a vagylagos felsorolásnak tudtommal nincs határa, de az átláthatóság miatt érdemes mértéket tartani, és fél tucat választási lehetőség fölé ne menjünk. Bár működik, hogy az összes csúnya szót kicseréld ...-ra egy menetben a szövegben, de azért jobb ha előtte részletekben teszteled.

Ennyi? Nem, de ennyivel el tudsz indulni, és ha megtetszik, akkor már az interneten ezerszám találsz ennél jobb, bővebb és sokkal több példával illusztrált leírást.

A témát rnm programmal próbáltam ki 2020 márciusában. Az rnm a PCRE2-es szabványos kifejezéseket szereti, és elég sok saját, belső szabványos kifejezése is van. Ezekre nem tértem ki, csak a rnm cikkben emlegetett csere a fájlnévben funkciót használtam.