ugrás a tartalomhoz

A regexről – másképp

gerzson · 2005. Feb. 9. (Sze), 11.41
A regexről – másképp
Aki valaha is használt már számítógépet az szinte minden bizonnyal találkozott már reguláris kifejezésekkel, sőt használta is azokat. Tették mindezt annak ellenére, hogy tudták volna mivel is van dolguk. Ebben a cikksorozatban elmélyedünk ennek a páratlanul hatékony és egyben páratlanul sok fejtörést okozó eszköznek a lehetőségeiben, miközben tisztázunk néhány homályos pontot, és eloszlatunk néhány gyakori félreértést. Az első részben mellőzzük a szokásos "gyorstalpalós" ismertőket metakarakterekről, módosítókról és egyebekről. Ennél mélyebb összefüggéseiben tárjuk fel a regexek jelentőségét és helyét a programozásban.

A regex mátrix

A reguláris kifejezésekkel mindenki találkozott már, aki valaha is beírt ehhez hasonló parancsot:
windows> dir *.php
linux$ ls *.php
Ezek a műveletek olyan alapszintűek, hogy biztos vagyok az előző állításom igazában. Homokba dughatjuk a fejünket, de a helyzet ettől nem fog változni.
"A regex mindenütt ott van. A regex körbevesz minket."
Akkor mi is az a regex mátrix? Ennek a bevezető cikknek nem célja a történeti áttekintés, a regex fejlődésének tárgyalása, ezt majd a sok tudástól meggyötörten menetközben szedjük magunkra pihenőidőben. A név - reguláris kifejezés - azonban némi magyarázatot igényel(ne).
Az angol szakirodalom "regular expression" néven említi ezt a konstrukciót, ebből keletkezett fél-tükörfordítással a magyar megfelelője. Van elfogadható magyarázat arra, hogy a "reguláris" miért maradt ebben az alakban, de jobb, ha ezt most még nem feszegetjük. (Ellenkező esetben nagy eséllyel itt abbahagyná majdnem mindenki a cikk olvasását, pedig nem azért írtam.) Annyit azonban érdemes tenni, hogy valami értelmes magyar megfelelőt találunk helyette. Ehhez nem árt tudnunk, hogy mire is jó a regex? A regex tulajdonképpen egy szűrő, amivel néhány vagy akár tömérdek adat közül kiválogathatjuk a számunkra érdekeseket. (A rövid definicók általában félelmetes bensőt takarnak, és ez most sem lesz másképp.)

A regex káosz

Ahhoz, hogy pontosan megértsük a regex működését, azonosítsuk a rendszer bemeneteit, kimeneteit és a feladatvégzés helyét! Az előző példában egyértelmű, hogy a "*.php" maga a reguláris kifejezés - regex, de mostantól magunk közt hívhatjuk mintaillesztő kifejezésnek is. (A regexhez hasonló "miki" vagy "milki" rövidítések -- hangzásuk folytán -- nem túl szerencsések.) A bemeneti adatok halmaza is világos: az aktuális könyvtárban levő összes bejegyzés neve. Most már csak az van hátra, hogy azonosítsuk a regexmotort, ami az illesztést elvégzi, vagy legalább az alkalmazást, ami erre neki parancsot ad! Elsőre -- a felfedezés okozta önfeledtségünkben -- rávághatnánk, hogy ezek nem mások, mint a dir és az ls.
Nos, a helyzet korántsem ilyen egyszerű vagy egyértelmű, ezért is időzünk el felette még mindig. A következő példa is biztosan ismerős lesz mindenki számára. Írjunk két rövid programocskát -- két parancsállományt --, amelyek kiírják a kapott paramétereiket. Vizsgáljuk meg a kimenetüket!
windows> echo echo %* > dir.bat
windows> dir.bat *.php
*.php
windows> dir.bat *.bat
*.bat
linux$ echo 'echo $*' > ls; chmod +x ls
linux$ ./ls *.php
*.php
linux$ ./ls *
ls
Windows-on a "program" mindkét esetben a paraméterként kapott regexet írta ki, amiből arra következtethetünk, hogy itt maga a `dir' parancs végzi el a mintaillesztést. A *nix-okon más a helyzet. Az első esetben magát a mintát kapta meg a program, míg a másodszorra már a könyvtárban levő fájlneveket. Ebben a környezetben parancshéj (shell) a program indítása előtt megpróbálja feloldani a mintát, és ha sikerrel jár, akkor lecseréli a paramétert az illeszkedő nevekre. Az igazi csavar azonban az ebben a példában, hogy míg az ls közönséges program *nix-okon, addig a dir a Windows parancsértelmezőjének (shell) a beépített "függvénye", tehát kilyukadunk oda, hogy mindkét esetben a héj végzi el az illesztést, de nem ugyanazon a ponton! Ez a különbség azonban saját programjaink bemenetére van hatással. (Zárójelben jegyzem meg, hogy ez nem minden GNU Linux/Unix héj esetén van így, a példában bash funkcionalitását vettem alapul.)
Ennél a példánál maradva további különbségeket is fel tudunk sorolni a két rendszer között! Listázzuk ki a (kis)betűvel kezdődő állományneveket:
windows> dir [a-z]*
A fájl nem található.
linux$ ls [a-z]*
ls
Ha ugyanarra könyvtárra is futtatjuk a két parancsot, akkor is eltér az eredmény. Ez azért van, mert -- felületesen mondva -- a Windows parancsértelmezője nem ismeri a [] karakterek speciális jelentését, és olyan neveket keres, amelyek '[a-z]' karaktersorozattal kezdődnek, és valljuk be, ilyen elég kevés akad. A két rendszer által használt regexmotor tehát nem is ugyanazt a "nyelvet beszéli", az egyik olyat is megért, amit a másik nem. Ne feledjük azonban, hogy ha a két regex motor ugyanaz is lenne, akkor is amiatt hogy más ponton aktiválódnak, más - más bemenetet szolgáltatnak tesztprogramjainknak.

Fény az éjszakában

Elég sok fogalom keveredett össze az iménti bekezdésekben ideje a dolgokat tisztába tenni!

Először is, létezik maga a reguláris kifejezés/mintaillesztés fogalma. Ez a létező absztrakciók ("lila ködök") egyik legnagyobbika, túl sok minden specifikus nem mondható el róla.
Ennek égisze alatt különféle csoportokról, - jobb híján - a regex "részhalmazairól" beszélhetünk, amelyek még nem egy-egy konkrét megvalósítást jelentenek, inkább azokat összefogó áramlatokat. Ezeket azért különböztetem meg önkényesen, mert azonos célra készült megvalósítások általában több rokonságot és hasonló lefedettséget mutatnak a csoport többi tagjával, mint más csoportok tagjaival. Magyarán szólva, a felhasználási területek mentén is könnyen csoportosíthatók a regex nyelvjárások.

A különböző részhalmazok igencsak sokféleképpen viszonyulnak egymáshoz. A regularitáshoz szükséges minimum funkciókat mindegyiknek teljesítenie kell. Vannak ezen kívül már széles körben elterjedt funkciók, amelyeket szinte mindegyik változat támogat (pl. visszautalás), kivéve a legelemibb alkalmazásokat, mint amilyen a fájlnév-illesztés. Léteznek olyan funkciók is, amelyeket azonban csak néhány részhalmazban találunk meg, mint pl. a hátratekintő vizsgálat, az explicit mohó (másnéven harácsoló) kvantorok vagy a Unicode karakterosztályok támogatása. Meglepő módon azonban néhány ilyen egzotikus lehetőség nem mindig a legnagyobbak kiváltsága: a GNU regex könyvtárat használó egrep program szóeleji határra (\<) és szóvegi határra (\>) illeszkedést nem ismeri még a Perl sem. (Ennek legvalószínűbb oka az, hogy ezek nélkül is jól megvagyunk a sima \b szóhatárral.)

A funkciók megléte/hiánya összefüggésben áll azzal, hogy az adott nyelvjárás milyen metakaraktereket illetve metaszekvenciákat ismer, de nem azonos vele! Vegyük a szöveg elejére illetve végére illeszkedés ismert példáját! Ebben az esetben, a reguláris kifejezésben egy pozíciót "keresünk", nem pedig egyszerűen karaktersorozatra illesztünk. A grep által "beszélt" nyelvjárás erre a '^' és '$' metakaraktereket használja, míg a PHP PCRE-t értő nyelvjárásban használhatjuk még a \A és a \Z jelöléseket is - némi plusz jelentéssel. Mindkét nyelvjárás ismeri tehát a soreleje, sorvég illesztést, de eltérő metakaraktereket használ(hat)nak ennek jelölésére.

regex_felho_r.png

CSS analógia

A Weblabor olvasóinak, webfejlesztőknek a fájlnévmintákon túl máshonnan is ismerős lehet a "regex filozófia":
h1 em { color: blue }
div p *[href] { }
h1.opener + h2 { margin-top: -5mm } 
A példák a W3C CSS 2.1 ajánlásából valók.

"Hogy kerül a csizma az asztalra?" - kérdezhetnénk. A CSS példákban fókuszáljunk a kiválasztókra, mivel csak azok hordoznak itt önmagukban is értelmes jelentést. A CSS kiválasztókat tualjdonképpen ugyanarra használjuk, mint a regexeket: a bemeneti elemek közül mindkettő kiválasztja a valamilyen szabálynak megfelelőket. Ráadásul ezt a szabály(osságo)t nem algoritmikus formában kell megadni valamilyen programozási nyelven egyik esetben sem. Az ilyen tulajdonságú nyelveket a számítástechnikában deklaratív nyelveknek hívják szemben a procedurális nyelvekkel, mint amilyen a C, PHP, Perl stb. is. Ez utóbbiakban nekünk kell kigondolni, és a vezérlőszerkezetekkel olyan lépéssorozatot felépíteni, amivel kívánt célt elérjük. A deklaratív nyelvek leveszik a vállunkról a terhet azzal, hogy tulajdonképpen számukra csak a célt kell megfogalmazni egy jól formalizált alakban, a cél eléréséhez vezető lépéseket ők határozzák meg.

Miből lesz a cserebogár?

A procedurális programozás illetve az algoritmizálás rugalmasságát a vezérlőszerkezetek adják, amelyek segítségével a bemeneti adatoktól függően tudunk végrehajtani, kihagyni vagy megismételni egész műveletsorozatokat. A CSS kiválasztók és a regexek esetében ezt a rugalmasságot a mintákban használható vezérlőelemek biztosítják, mint a Jolly Joker a kártyapakliban. (Természetes, hogy lennie kell ilyen képességű elemnek, hiszen bármire illeszkedni képes kifejezéseket szeretnénk használni mind CSS-ben, mind a regexekben.) Talán nem meglepő, hogy az ábrázolásuk is nagyon hasonló. Az alábbi ábrán azt mutatom meg, hogy a folyamatábra két alapvető szerkezete - az iteráció és a feltételes elágazás -, miként "alakul át" a reguláris kifejezések alapját jelentő konstrukciókká: ismétléssé és alternatív mintává.

A feltételes elágazás és az alternatív minták (a|b), illetve a ciklus és az ismétlés (*) hasonlósága.


Ez az analógia azt is megmutatja, hogy a regexmotorok milyen műveleteket végeznek, amikor ezekkel a reguláris kifejezésekkel dolgoznak. A középső oszlopban a folyamatábrára illeszkedő adatfolyamféleségen látjuk, milyen adatok áramolhatnak a hasonló nevű feldolgozási lépésben a folyamatábrán. Innen már csak egy ugrás a harmadik oszlopban látható állapotgráf, amivel az ilyen egyszerű regexet megértő automatákat szokás jellemezni. Ennek az ábrázolásmódnak az értelmezése a következő. Az automata mindig egy körrel jelzett állapotban van. Az állapotok között az éleken keresztül vezett az út, de mindig csak olyan élen léphetünk tovább egy állapotból egy másikba, amelyik élnek címkéje a soron következő karakterrel megyegyezik. (A szagatott vonalak jelzik, hogyan "fordulnak" az adatfolyam csúcsai az állapotgráf éleivé.)

Zárszó helyett

Nem tudom, hogy kinek mennyi újdonságot hozott a mostani bevezető cikk, mentségemre legyen mondva, az alapozás volt a célom. Rávilágítottam a reguláris kifejezések hétköznapiságára, és az elméletük mögött meghúzódó általános elvekre, amelyeket talán más területekről jól ismerünk. Emellett tisztáztuk azt is, hogy "reguláris kifejezéseket ismerni" elméletben könnyű, gyakorlatilag azonban lehetetlen, hiszen annyiféle megvalósítása és variációja létezik, amennyinek a megismeréséhez egy élet is kevés. Az alapokat elsajátításával azonban az új regexváltoztok megismérese sokkal könnyebb lesz. A következő alkalommal megnézzük, hogy valójában hogyan is történik az illesztés, és milyen munka folyik/folyhat eközben a regexmotorban a felszín alatt.
 
1

Végre egy olyan cikk, amely

Anonymous · 2005. Feb. 9. (Sze), 17.01
Végre egy olyan cikk, amely egy igazán fontos témát dolgoz fel, a téma fontosságához illő mélységben! Grat hozzá!
Apropó! Ha van valami téma amire kíváncsi lennék, azt lehet valahol jelezni?
Tome
2

javaslatok helye

gerzson · 2005. Feb. 9. (Sze), 17.17
Az alábbi fórumot javaslom elsősorban: http://weblabor.hu/forumok/weblabor/weblaborrol

Ha a szerkesztőség felé közvetlenül tennél javaslatokat, akkor írj a info##kukac##weblabor.hu-ra.

Persze nem ígérjük, hogy teljesíteni tudjuk kívánságod, de pl. a fórum esetén lehet, h. mások is tudnak hasznos információkkal/linkekkel szolgálni.
3

Regex?

sajt · 2005. Feb. 14. (H), 18.49
Érdekes, most ez regex, vagy regexp? Most rakerestem a Google-n a helyesirasellenorzoben es naggyabol hasonlo szamu talalatot kaptam. :)
4

Mindkettő

Bártházi András · 2005. Feb. 14. (H), 20.19
Mind a két rövidítés elfogadott.

-boogie-
5

RE: regex?

gerzson · 2005. Feb. 14. (H), 23.50
A cím stílusosan "regexp?" is lehetne, nem?

A következőkben lesz néhány hivatkozás már magyarul is elérhető tartalmakra, ahol a "regex" formát részesítették előnyben az angol mintájára.

Próbáld meg kimondani a "regex"-et és a "regexp"-et szövegkörnyezetben! Szvsz, az elsővel könnyebb boldogulni, mert nincs mássalhangzó-torlódás.
6

Nem tudom

sajt · 2005. Feb. 15. (K), 11.46
Nekem a regexp-re all ra a szam :) Valoszinuleg azert mert en mindig igy hasznaltam.

Es, ha mar itt irok, akkor par szo a cikk-rol. Szerintem egy kicsit elnezted a celkozonseget, mert az elejen olyan dolgokrol irsz, hogy dir *.php, aztan hirtelen olyan melysegekbe esel, amit csak a beavatottak erthetnek. Egyebkent nekem tetszik a cikk.

Ja, es meg valami. Nem tudja senki, hogy hova lett a php kezikonyvbol a regex(p:) Pattern Syntax magyar forditasa? Az egy nagyon jo kis leiras volt.
7

Ahogy tetszik! Ízlések és

gerzson · 2005. Feb. 16. (Sze), 03.52
Ahogy tetszik! Ízlések és pofonok: neked regexp nekem regex(p?) -- néha ugyanis még én is keverem ;)

A célközönséggel kapcsolatban nem gondolom, h. tévedtem volna, de sejtem, mire gondolsz. Elsősorban azért lett ez a bevezető olyan amilyen, mert - kicsit nagyképűen - szeretnék rendet tenni a fejekben. Azt gondolom, h. van néhány közhiedelem és tévhit a regex-el kapcsolatban, amit jó lenne eloszlatni. Ezek legtöbbje a hiányos alapokból és megfelelő összefoglaló irodalom hiányából fakad, ezt a saját magamon tapasztaltam.
Ebben a konkrét cikkben nem az új technikákra helyeztem a hangsúlyt, hanem az elméleti kérdésekre tértem ki:
  • nincs olyan ember, aki ne használt volna regex-et - hazugság
  • nincs olyan, hogy tudok a regex-ül
  • a regex semmivel sem bonyolultabb/misztikusabb, mint a hagyományos programozás, csak más van a középpontban.

A cikk többszöri újraolvasása után szívesen megváltoztatnék néhány részt én is, de ez már így marad. Örülnék azonban, ha leírnád, pontosan mely részek miatt gondoltad, hogy "eltévesztettem a célközönséget"?

A PHP dokumentációval kapcsolatban pedig a doc-hu##kukac##lists.php.net címen érdeklődj! Valószínűleg egy újabb XML átalakítás lehet a háttérben, amitől a magyar, régi szerkezetű fordítás "kiesett", de ez csak egy tipp.
8

Jo tipp ;)

Hodicska Gergely · 2005. Feb. 16. (Sze), 04.02
Felho