Miután a műsorújságban egy kedvelt osztrák-német krimisorozat címét Regex felügyelőnek olvastam, gondoltam ideje egy ilyen írást készíteni. Vagy lehet, hogy el kéne vándorolnom a gépek közeléből pár hétre. A bejegyzésben továbbiakban a perl-kompatibilis reguláris kifejezésekről lesz szó, amit például PHP-ben a preg_ kezdetű függvényekben használhatunk, mint mintaillesztési kifejezést.

Az alapok

Kezdjük talán ott, hogy milyen különleges karakterek adják meg a nyelv lényegét, és melyikek mikre valók:

.
A pont az a karakter, ami esetünkben bármely más karakterre illeszkedik, tehát ha van egy olyan kifejezésünk, hogy /a.b/, az azt fejezi ki, hogy az "a" és a "b" karakter között bármilyen másik karakter szerepelhet, az új sor karaktereket kivéve (például aab, a b, a=b, a*b, axb).
?
A kérdőjel jelzi, hogy az előtte lévő mintacsoportból nulla vagy egy szerepel a kifejezésben. A csoportokat a kerek zárójel segítségével tudjuk megadni. Például: /aa?b/ illeszkedik ab-re és aab-re is. Az /a(aa)?b/ pedig aaab-re és ab-re.
*
A csillag karakter működésében hasonló, mit a kérdőjel (az előtte lévő mintacsoportra van hatással), az utóbbival ellentétben viszont a jelentése "nulla vagy bármennyi". A példák mezejére áttérve tehát valami ilyesmi kifejezés: /aa*b/ ilyesmikre illeszkedik: ab, aab, aaab, aaaab, stb.
+
A szükséges pussz az előző két metakarakterhez, működésében ugyanolyan, hatásában pedig a "legalább egy, maximum bármennyi" kifejezéssel illethető. Lássunk valami kombináltabb példát: /a.+b/. A minta bármire illeszkedik aminek "a" az eleje és "b" a vége, köztük pedig legalább egy bármilyen karakter van. Például: dsax dbce, a b, a szép hosszú példa aminek a vége b.
{ és }
Ővele egy intervallumot lehet meghatározni három féle módon: {n} - pontosan n darab az előtte álló kifejezésből. {n,} - legalább n darab az előtte álló kifejezésből. {n,m} - legalább n, de maximum m darab az előtte álló kifejezésből. A ? tehát kiváltható a {0,1}-el, a * a {0,}-el, a + pedig az {1,}-el.
^ és $
Ez a kettő annyira összeillik, hogy nem is láttam értelmét a szétválasztásnak. Az előbbi a szöveg elejére, míg a másik a szöveg végére illeszkedik, vagyis a /^a.+b$/ ugyanazokra a sorokra fog illeszkedni, mint az előző példában megadott kifejezés, azzal a különbséggel, hogy itt már meg van kötve, hogy a sornak "a" betűvel kell kezdődnie és "b" betűvel kell végződnie. Előzőben nem volt ez megkötve, elég volt ha a sorban volt egy olyan rész, ahol "a" és "b" karakterek között volt legalább egy karakter (illeszkedő sorok első példája).
|
Ez olyan "vagy" szerű dolog, példából egyszerűbben meg lehet majd érteni, mint abból, ha itt én elkezdenék magyarázkodni bárhogy is, tehát lássuk: a /^a(c|d)b$/ illeszkedik a következőkre: acb, adb.
( és )
Mint azt pár bekezdéssel feljebb említettem, ezzel lehet a kifejezésben szereplő részkifejezéseket csoportosítani, amikre a későbbiekben akár hivatkozni is lehet (ennek módja függ a választott nyelvtől, például Perlben a \1, \2, \3, stb. hivatkozások vannak, míg a PHP preg_replace függvényében a $1, $2, $3, stb. használható).
[ és ]
A szögletes zárójelek között egy karakterekből álló sorozatot adhatunk meg, vagy egy intervallumot, és a kifejezés az ebben benne lévő karakterekre fog illeszkedni. Itt sem maradhatunk példa nélkül: a /a[cd]b/ ugyanazt eredményezi, mint a /a(c|d)b/. A /a[0-9]/ illeszkedik egy olyan részre, ahol egy "a" betűt egy darab számjegy követ. Ezt a típust invertálhatjuk is a ^ karakter nyitózárójel utáni közvetlen alkalmazásával: a /a[^cd]b/ tehát azt fejezi ki, hogy az "a" és a "b" között van egy olyan karakter, ami nem "c" és nem is "d" (akár új sor karakter is lehet).

Kifejezés módosítók

A kifejezés maga tehát valami ilyen forma dolog /itt van valami izé/x, ahol az a bizonyos x a kifejezés módosítók sora, amik a következők lehetnek:

i
A legegyszerűbb, leggyakrabban használt módosító, ami a RegEx alapértelmezett kis- és nagybetű érzékenységét kapcsolja ki ("magyarul" case insensitive lesz :)). Kérdéses mondjuk, hogy egy ilyen módosító a kifejezés kiértékelését mennyire lassítja le, de ez már egy más kérdés.
m
Egy hasznos módosító, ami a ^ és $ metakarakterek viselkedését változtatja meg. Első körben tudni kell azt, hogy a Perl RegEx a kapott szöveget egy sorként kezeli, még akkor is, ha az több új sor karaktert is tartalmaz, így a ^ a szöveg legelejére, a $ pedig a szöveg legvégére illeszkedik. Az m módosító használatával viszont a ^ és a $ karakterek passzolni fognak a sor elejére és a sor végére is.
s
Ismét egy hasznos dolog, ami a . metakarakter viselkedését változtatja meg úgy, hogy az új sor karakterre is illeszkedjen.
x
A whitespace karakterek (tab, szóköz, újsor) teljes mértékbeni figyelmen kívül hagyása, kivéve ha [ és ] között szerepel, vagy ha megfelelően escape-elve van (PHP esetében ez teszi lehetővé a használható eszközök kiterjesztését is).

Kiterjesztett eszközök

Ezek a bizonyos kiterjesztett eszközök az amúgy értelmetlennek számító (? karakterlánccal kezdődnek és egy ) karakterrel fejeződnek be. Ezen belül elég sok fajtájuk van és nem is vagyok teljesen biztos benne, hogy ez támogatott pl. a PCRE által is. Mindenesetre essünk neki a fontosabbak átvételének:

(?#szöveg)
Ezzel kommenteket tehetünk bele a reguláris kifejezésünkbe. Első látásra talán értelmetlennek tűnhet, hogy mi a fenének is akarnánk egy egy soros eléggé átláthatatlan kifejezést még telitűzdelni kommentekkel is, hogy még annyira se legyen átlátható. De ha feldereng bennünk egy korábbi bekezdés szövege, az x módosító arról is gondoskodik, hogy a kifejezésünket több sorba tördelhessük.
(?imsx-imsx)
Ennek segítségével a kifejezésen belül kapacsolhatjuk be vagy ki az egyes módosítókat. Hatásköre az éppen aktuális csoport vége. Pl.: /(?i)ab/ illeszkedni fog az ab, Ab, aB, AB stringekre.
(?:kifejezés)
(?imsx-imsx:kifejezés)
Ez a változat hasonlít a szimpla csoportosításra, azzal a kivétellel, hogy a második típusában a használt módosítókat is be lehet állítani, ezen kívül ezekre a csoportokra nem lehet a későbbiekben a \1, \2, stb. (vagy $1, $2, stb.) cimkékkel hivatkozni.
(?=kifejezés)
Ez egy érdekes eszköz, amit hosszadalmas magyartalan magyarázkodás helyett inkább egy egyszerű példával tudnék jellemezni: /[a-z]+(?=\t)/ illeszkedik egy olyan - csak kisbetűket tartalmazó - karaktersorozatra, melyet egy TAB karakter követ, viszont a TAB karakter így nem kerül bele a találatba (pl.: preg_match esetében a $matches tömbbe).
(?!kifejezés)
A kifejezést lehet vele egyszerűen negálni.

Végezetül még ajánlanék egy hasznos kis segédprogramot, amivel gyakorolhatjuk a reguláris kifejezések írását. Ez a program nem más, mint a The Regex Coach, ami esetenként eléggé meg tudja könnyíteni az ember életét, főleg nagyobb lélegzetvételű kifejezések esetén. További kellemes mintaillesztgetést!