Tagadás a reguláris kifejezésben

A reguláris kifejezésben a tagadás azt jelenti, hogy egy karaktert, karakterkészletet vagy közvetlenül következő mintát kizárunk az egyezésből. A leggyakoribb eszköze a karakterosztály elején álló ^, például a [^aei] minden olyan egyetlen karakterre illeszkedik, amely nem szerepel az a, e és i karakterek között.

A teljes minta tagadására a használt regexmotor lehetőségeitől függően negatív előrenézést, negatív hátranézést vagy az adott nyelv keresési operátorát kell használni. A (?!...) azt állítja, hogy a következő szöveg nem felel meg egy mintának, a (?<!...) pedig azt, hogy az aktuális pozíció előtt nem áll a megadott minta.

A tagadás legfontosabb formái

SzintaxisJelentésPélda
[^...]Negált karakterosztály: egyetlen, a felsorolt készletben nem szereplő karakter[^0-9]
(?!...)Negatív előrenézés: a következő rész nem lehet a megadott mintaJava(?!Script)
(?<!...)Negatív hátranézés: az előző rész nem lehet a megadott minta(?<!Borneo)Script
!~Az awkban a kifejezés nem illeszkedik a reguláris kifejezésre$1 !~ /J/

Negált karakterosztály: [^...]

A szögletes zárójelek között megadott karakterek egy karakterkészletet alkotnak. A [^] jelölésben a nyitó szögletes zárójel után álló ^ invertálja, vagyis tagadja a karakterosztályt.

A [^a-f] bármilyen karakternek megfelel, kivéve az a és f közötti karaktereket. A [^c]ar jelentése: minden olyan karakter, amely nem c, és amelyet az a, majd az r követ.

A .NET-regex szintaxisában a [^aei] minta a következő karakterek egyikével egyezik: r, g vagy n a „reign” szövegben. Ugyanez az elv használható számok kizárására a [^0-9], szókarakterek kizárására a \W, számjegyek kizárására a \D, illetve szóközök kizárására a \S segítségével.

Karakterosztály és negatív feltétel különbsége

A [^abc] pontosan egy olyan karaktert keres, amely nem a, nem b és nem c. Nem azt jelenti, hogy a teljes szöveg nem tartalmazhatja ezeket a karaktereket.

Ha például a [^c]ar mintát alkalmazzuk a „car” szövegre, az nem fog egyezni, mert az első karakter c. A „bar” és a „par” viszont megfelelhet a mintának. A karakterosztály tagadása tehát az osztály által elfoglalt egyetlen pozícióra vonatkozik.

A ^ helye döntő

A ^ karakter a karakterosztály elején tagadást jelent, más helyzetben viszont horgonyként működik. A [^0-9] egy nem számjegy karaktert jelent, míg a ^abc azt írja elő, hogy az abc a karakterlánc elején szerepeljen.

A ^ csak akkor tagadja a karakterosztályt, ha közvetlenül a nyitó [ után áll. A [a^z] esetén például a ^ nem tagadó operátor, hanem a készlet egyik közönséges karaktere.

Negatív előrenézés: (?!...)

A negatív előrenézés olyan nulla hosszúságú állítás, amely akkor sikeres, ha az aktuális pozíciót közvetlenül követő szöveg nem felel meg a megadott mintának. Maga az előrenézésben ellenőrzött szöveg nem kerül bele az illesztett részbe.

A (?!regexp) konstrukció hasonló a (?=regexp) pozitív előrenézéshez, de akkor fogadja el a mintát, ha a következő füzérdarab nem felel meg a megadott reguláris kifejezésnek.

A következő csere csak a „Java” szó azon előfordulását cseréli „Borneo” szövegre, amelyet nem követ a „Script” rész:

$text = 'Java szigetén nem használnak JavaScript programokat.';
$text =~ s/Java(?!Script)/Borneo/;

Ennek kimenete: „Borneo szigetén nem használnak JavaScript programokat.”

A (T|t)he(?!\sfat) minta azokat a „The” vagy „the” szavakat keresi, amelyeket nem követ szóköz és a „fat” szó. A Valami(?!\sAmerika) a „Valami” szót találja meg, ha utána nem az „Amerika” szó áll.

Az előrenézés nem fogyaszt karaktert

A pozitív előrenézéshez hasonlóan a negatív előrenézés csak ellenőriz, de nem veszi fel az ellenőrzött részt az egyezésbe. Ezért a \w+(?!\t) jellegű feltételekben a vizsgálatot követő karakter nem lesz része a találatnak.

A negatív előrenézés mindig előre vizsgál. Ha azt írjuk, hogy (?!Borneo)Script, az nem azt jelenti, hogy minden olyan „Script” részre illeszkedünk, amely előtt nem „Borneo” áll. A motor az aktuális pozíción ellenőrzi, hogy ott nem „Borneo” következik-e, majd ugyanott megpróbálja illeszteni a Script részt.

Ezért a (?!Borneo)Script a „BorneoScript” szöveg „Script” részére is illeszkedhet, mert az ellenőrzés pillanatában az aktuális helyen már „Script” áll, nem „Borneo”.

Negatív hátranézés: (?<!...)

A negatív hátranézés azt állítja, hogy az aktuális pozíciót közvetlenül megelőző szöveg nem felel meg a megadott mintának. A (?<!regexp) nulla hosszúságú állítás, amely nem kerül bele a találatba.

A (?<!(T|t)he\s)(cat) olyan „cat” szavakat keres, amelyeket nem előz meg a „The” vagy „the” szó, majd egy szóköz. A (?<!Valami\s)Amerika az „Amerika” szót találja meg, ha előtte nem a „Valami” szó áll.

A negatív hátranézés a negatív előrenézéssel ellentétes irányban dolgozik: az aktuális pozíció előtti szöveget vizsgálja, miközben a találatba csak a ténylegesen illesztett későbbi rész kerül.

Tagadás a teljes illeszkedés szintjén

A karakterosztály tagadása és a negatív lookaround nem ugyanaz, mint annak ellenőrzése, hogy egy teljes karakterlánc nem illeszkedik. A teljes illeszkedés megfordítását az adott programozási nyelv vagy eszköz operátora adhatja meg.

Az awkban az exp !~ /regexp/ kifejezés akkor igaz, ha a reguláris kifejezés nem illeszkedik az exp kifejezésre mint karaktersorozatra. Az alábbi példa minden olyan sort kiválaszt, amelynek első mezője nem tartalmazza a nagy „J” betűt:

awk '$1 !~ /J/' inventory-shipped

A ~ operátorral szemben a !~ nem egyezést vizsgál. Ez a tagadás a teljes regexvizsgálat eredményét fordítja meg, nem egyetlen karaktert vagy egyetlen pozíciót zár ki.

Horgonyok és a tagadás kapcsolata

A ^ és a $ horgonyok önmagukban nem tagadó jelek. A ^ alapértelmezés szerint azt jelenti, hogy az egyezésnek a karakterlánc elején kell kezdődnie, a $ pedig azt, hogy az egyezésnek a karakterlánc végén kell történnie.

A ^ tagadó jelentése kizárólag karakterosztályon belül, annak első pozíciójában érvényes. A [^a] ezért egyetlen, nem a karaktert jelent, míg a ^a olyan a karaktert keres, amely a karakterlánc elején áll.

A .NET-ben a \A csak a sztring elején, a \Z a sztring végén vagy az azt megelőző újsor előtt, a \z pedig kizárólag a sztring legvégén illeszkedik. Ezek nem tagadó szerkezetek, hanem pontosabb pozícióhorgonyok.

Negatív karakterosztályok és rövid jelölések

JelölésJelentés
[^0-9]Bármely egyetlen karakter, amely nem a 0 és 9 közötti számjegy
\DNem számjegy
\WNem szókarakter
\SNem szóköz karakter
[^a-zA-Z]Nem angol ábécébe tartozó kis- vagy nagybetű
[^aeiou]Nem a felsorolt magánhangzók egyike

A \W a \w komplementerének tekinthető, a \D a \d, a \S pedig a \s ellentéte. A pontos karakterkészlet azonban függhet a regexmotortól, a Unicode-beállításoktól és az alkalmazott opcióktól.

A gyakori rövid jelölések közül a \w szókarakterre, a \W nem szókarakterre, a \d számjegyre, a \D nem számjegyre, a \s szóköz jellegű karakterre, a \S pedig nem szóköz karakterre illeszkedik.

Tagadás csoportokkal

A zárójelek segítségével összetettebb feltételek tagadhatók. A ^ karakterosztályon belüli használata csak egy karaktert érint, míg a (?!...) több karakterből álló mintát is kizárhat.

A [^abc] egyetlen olyan karaktert keres, amely nem a, b vagy c. Ezzel szemben a (?!abc) az aktuális pozíción akkor sikeres, ha ott nem kezdődik az „abc” karaktersorozat.

A | alternációt is érdemes csoportba foglalni, ha a tagadásnak több lehetőségre kell vonatkoznia. A (?!foo|bar) azt vizsgálja, hogy az aktuális pozíción ne kezdődjön sem „foo”, sem „bar”. A [^fobar] viszont csak egyetlen karaktert zár ki a felsorolt karakterek közül, nem a „foo” és „bar” szavakat.

Nem tárolt csoport és negatív feltétel

A (?:...) nem tárolt csoport, amely csoportosítja a kifejezés részeit, de nem hoz létre visszahivatkozási csoportot. Hasznos alternációk és ismétlések szervezésére.

A (?:ha)(rom) kifejezés illesztésekor mindössze a teljes találat és a „rom” tárolt csoport jelenik meg. A nem tárolt csoport nem jelent tagadást; csak azt szabályozza, hogy létrejön-e hivatkozható csoport.

A feltételcsoportok között viszont megtalálhatók a negatív lookaroundok: a kif(?!...) negatív előretekintő, a (?<!...)kif negatív visszatekintő feltételt jelent. Ezek a szerkezetek pozicionálásra és kontextusfüggő kizárásra használhatók.

Escape-elés: amikor a tagadó jel literál

A fordított perjel a mellette álló karakter speciális jelentését módosíthatja. A \. tényleges pontot jelent, nem tetszőleges karaktert, a \^ tényleges kalapjelet jelent, a \$ tényleges dollárjelet, a \[ pedig tényleges nyitó szögletes zárójelet.

Ha a ^ karaktert nem tagadásként, hanem szó szerinti karakterként szeretnénk keresni egy karakterosztályon kívül, használjuk a \^ jelölést. Hasonlóan, a \$ nem karakterláncvégi horgony, hanem egy literális dollárjel.

A reguláris kifejezésben a fordított perjel azt jelzi, hogy az azt követő karakter vagy speciális karakter, vagy szó szerint kell értelmezni. Az olyan jelölések, mint a \., \*, \+, \?, \^ és \$ lehetővé teszik a metakarakterek literális keresését.

Teljes és részleges regex

A Google Analytics alapértelmezés szerint teljes regexet támogat. Ez azt jelenti, hogy metakarakterek hozzáadása nélkül a kifejezés csak a pontosan egyező értékeket adja vissza.

Részleges regex létrehozásához és egy adott értéket tartalmazó dimenziók szűréséhez használhat metakaraktereket, például a .* mintát. Ha a város a „San Francisco” regexszel egyezik, akkor a rendszer csak a „San Francisco” értéket adja vissza. A .*San Francisco.* részleges regex a „San Francisco” és a „Dél-San Francisco” értéket is visszaadhatja.

A teljes regex és a tagadó regex nem ugyanaz. A teljes illeszkedés a megadott érték pontos egyezését írja elő, míg a tagadás azt mondja meg, hogy egy karakter, részkarakterlánc vagy előfeltétel ne szerepeljen egy adott helyen.

Google Analytics: kizárás és szűrés

A Google Analytics támogatja a reguláris kifejezéseket, más néven „regex”-eket, így rugalmasabb definíciókat hozhat létre például az adatszűrőkhöz, a kulcseseményekhez, a szegmensekhez, a közönségekhez, a tartalmi csoportokhoz és az egyéni csatornacsoportokhoz.

A Google Analytics rendszerben a reguláris kifejezések olyan karaktersorozatok, amelyek általánosan vagy konkrétan illeszkednek az Analytics-adatok mintájába. Ha például egy új nézetszűrűt szeretne létrehozni a saját alkalmazottjai által generált webhelyadatok kizárására, egy reguláris kifejezés segítségével az alkalmazottjait kiszolgáló összes IP-címről származó adatot kizárhatja.

Ha az IP-címek az 198.51.100.1 és az 198.51.100.25 közötti tartományban vannak, a 198\.51\.100\.\d* kifejezés az egész címtartomány lefedésére használható. A pontokat azért kell \. formában megadni, mert különben a pont bármely egyedi karakterrel egyező helyettesítő karakterként működne.

Ha olyan nézetszűrőt szeretne létrehozni, amely kizárólag két adott városból származó kampányadatokat tartalmaz, létrehozhat egy ehhez hasonló reguláris kifejezést: Budapest|Debrecen. Ez Budapest vagy Debrecen értékére hoz létre egyezést.

Kis- és nagybetűk tagadása

A regexek alap esetben kis-nagybetű érzékenyek, tehát a „The” reguláris kifejezés nem fog illeszkedni a „the” karakterláncra. A Google Analytics-regex alapértelmezés szerint teljes regex, amely megkülönbözteti a kis- és nagybetűket.

A kis- és nagybetűk figyelmen kívül hagyása nem tagadás, hanem illesztési opció. A Perlben például az i módosító állítja be, hogy az illeszkedés ne legyen kis- és nagybetű érzékeny. A /The/gi kifejezés a teljes bemeneti szövegben keresi a „The”, „the” és a kis- és nagybetűk egyéb változatait.

Ha csak bizonyos karaktereket akarunk kizárni, karakterosztály használható, például a [^A-Z] nem angol nagybetűt keres. Ez eltér attól, amikor az i opcióval ugyanazt a mintát kis- és nagybetűtől függetlenül illesztjük.

Ismétlés és tagadás

A * nulla vagy több, a + egy vagy több, a ?" nulla vagy egy, a {n} pontosan n, a {n,} legalább n, a {n,m} pedig legalább n, de legfeljebb m ismétlődést jelöl.

A kvantor önmagában nem tagadást jelent. A [^0-9]+ legalább egy egymást követő nem számjegy karaktert, a (?!foo)+ pedig egy olyan szerkezetet próbál ismételni, amelynek értelmezése a környező mintától és a regexmotortól függ.

Ha egy negatív karakterosztályt ismétlünk, például [^,]*, akkor nulla vagy több, vesszőtől eltérő karaktert keresünk. A [^,]+ legalább egy vesszőtől eltérő karaktert követel meg.

Gyakori hibák tagadás használatakor

A ^ összekeverése a karakterlánc kezdetével

Az egyik leggyakoribb hiba, hogy a ^ karaktert minden helyzetben tagadásként értelmezik. A karakterosztály elején tagad, a karakterosztályon kívül viszont a karakterlánc vagy sor kezdetét jelöli.

A teljes szó kizárása egy karakterosztállyal

A [^cat] nem azt jelenti, hogy a „cat” szó nem szerepelhet. Egyetlen olyan karaktert jelent, amely nem c, nem a és nem t. Egy teljes szó kizárásához negatív előrenézésre, teljes karakterlánc-ellenőrzésre vagy a használt eszköz nem egyezési operátorára van szükség.

A pont escape-elésének elmulasztása

A pont tetszőleges, pontosan egy karakterre illeszkedik, kivéve a sortörést. Ha literál pont karakterre szeretnénk illeszteni, azt a feloldó karakterrel, \. alakban kell megadni.

Ez különösen fontos IP-címeknél, verziószámoknál, fájlneveknél és tizedes számoknál. A 216\.239\.32\.34 a „216.239.32.34” értékre illeszkedik, míg a pontok escape-elése nélkül a minta eltérő karaktereket is elfogadhatna.

A negatív előrenézés irányának félreértése

A (?!...) csak az aktuális pozíció után vizsgál. Nem ellenőrzi automatikusan, hogy mi áll a megtalált rész előtt. Ha az előző környezetet kell kizárni, negatív hátranézésre, például (?<!Borneo), lehet szükség.

Negatív lookaround és pozitív lookaround összehasonlítása

SzerkezetFeltételA vizsgált szöveg a találat része?
(?=check)A következő szöveg megfelel a check mintánakNem
(?!check)A következő szöveg nem felel meg a check mintánakNem
(?<=check)Az előző szöveg megfelel a check mintánakNem
(?<!check)Az előző szöveg nem felel meg a check mintánakNem

A pozitív előrenézés azt állítja, hogy a sztring aktuális pozícióját azonnal követő „ellenőrzés” megtalálható. A negatív előrenézés azt állítja, hogy a következő érték nem „ellenőrzés”. A pozitív hátranézés az aktuális pozíciót közvetlenül megelőző mintát, a negatív hátranézés pedig annak hiányát vizsgálja.

Tagadás a Perlben

A Perl nyelv fejlett mintaillesztési lehetőségeket biztosít. A reguláris kifejezéseket az m operátorral illesztésre, az s operátorral pedig részkarakterláncok cseréjére lehet használni.

A Perlben a (?!regexp) negatív nullahosszúságú előrenézés akkor fogadja el a mintát, ha a következő füzérdarab nem felel meg a megadott reguláris kifejezésnek. A (?<!regexp) negatív hátranézés ugyanennek az előző szövegre vonatkozó változata.

A $text =~ s/Java(?!Script)/Borneo/ kifejezés az első olyan „Java” részfüzért cseréli ki, amelyet nem követ „Script”. Az illesztési és helyettesítési műveleteknél a =~ operátort kell használni.

A Perlben a ^ a füzér elejének, a $ a füzér végének vagy a végén álló újsor karakternek felel meg. Az m opció többsoros módot állít be, így minden sor eleje és vége vizsgálhatóvá válik, míg az s opcióban a pont a soremelés karakterekre is illeszkedik.

A \A és a \Z ugyanazt a szerepet tölti be, mint a ^ és a $, azzal a különbséggel, hogy ezek még az m opció használata esetén sem felelnek meg a füzér belsejében lévő soremelés karakternek.

Tagadás a .NET-ben

A .NET reguláris kifejezéseiben a [^character_group] konstrukció tagadott karakterosztályt jelöl. A [^aei] egyetlen olyan karakterrel egyezik, amely nem található meg a megadott karaktercsoportban.

A .NET támogatja a (?!subexpression) nulla szélességű negatív lookahead-állítást és a (?<!subexpression) nulla szélességű negatív lookbehind-állítást. A lookaroundok sikeres vagy sikertelen egyezést okoznak, de nem léptetik előre a regexmotort, és nem használnak fel karaktereket a találat részeként.

A .NET-ben a karakterosztályok, horgonyok, csoportosítások és kvantorok kombinálhatók. Például a ^\D+$ olyan teljes sztringet keres, amelynek minden karaktere nem számjegy, feltéve, hogy a használt opciók és a motorértelmezés megfelel ennek a vizsgálatnak.

Tagadás awkban

Az awkban a reguláris kifejezéshez illesztett kifejezéseket a ~ és a !~ operátorral lehet vizsgálni. Az exp ~ /regexp/ igaz, ha a reguláris kifejezés illeszkedik az exp értékére, az exp !~ /regexp/ pedig akkor igaz, ha nem illeszkedik.

Az awkban a reguláris kifejezések általában egy sorban való részleges egyezést vizsgálnak. A /foo/ minta minden olyan bemeneti sorra illeszkedik, amelyben a „foo” karaktersorozat bárhol előfordul. A !~ használatával ennek ellenkezője választható ki.

A karakterosztályok awkban is tagadhatók, például a [^0-9] minden olyan karakterre illeszkedik, amely nem számjegy. A karakterlistán belül a ^ csak akkor működik tagadásként, ha az első karakter a nyitó [ után.

POSIX karakterosztályok tagadása

A POSIX karakterosztályok olyan karakterlisták speciális leírásai, amelyek valamilyen közös tulajdonsággal rendelkeznek. Ilyen például a [:digit:] számjegyeket, a [:alpha:] betűket, a [:alnum:] alfanumerikus karaktereket, a [:space:] szóköz jellegű karaktereket jelölő osztály.

Negált POSIX karakterosztály például a [^[:digit:]], amely minden olyan karakterre illeszkedik, amely nem tartozik a [:digit:] osztályba. A pontos viselkedés függhet a lokális beállításoktól és az adott regexmotor támogatásától.

A POSIX karakterosztályok használatával az olyan karakterek felismerése is kezelhető, amelyek nem feltétlenül írhatók le megfelelően az egyszerű [A-Za-z0-9] mintával. Az ékezetes karakterek és más ábécék kezelésére a környezetfüggő karakterosztályok alkalmasabbak lehetnek.

Unicode és tagadás

A .NET-ben a \p{név} egy Unicode általános kategóriájában vagy elnevezett blokkban szereplő egyetlen karakternek felel meg, míg a \P{név} minden olyan karakterrel egyezik, amely nem szerepel az adott Unicode-kategóriában vagy blokkban.

A \P{Lu} olyan karaktereket jelöl, amelyek nem tartoznak a Unicode nagybetű kategóriájába. A \P{IsCyrillic} azokat a karaktereket jelölheti, amelyek nem tartoznak a cirill Unicode-blokkhoz.

A Unicode-alapú tagadás pontosabb lehet, mint az egyszerű ASCII-készletek, de csak akkor használható, ha az adott regexmotor támogatja az Unicode-kategóriákat és a megfelelő karakterértelmezést.

Teljesítmény és tagadó minták

Reguláris kifejezést illeszteni sokkal lassabb, mint hagyományos karakterláncot keresni, ezért ha csak a „kék” szó ékezetes és anélküli változatát keressük reguláris kifejezéssel, az 5-10-szer lassabb lehet, mintha egy sima stringkeresést hajtanánk végre kétszer.

A negatív lookaroundok, a sok ismétlés, a széles .* minták és az egymásba ágyazott alternációk összetett visszalépést eredményezhetnek. A visszalépéses algoritmus rosszabb esetben exponenciális futási időt eredményezhet.

Az egyszerű kifejezések könnyebben átláthatók és módosíthatók. A negatív feltételt célszerű a lehető legszűkebb helyre tenni, a literális pontokat és egyéb metakaraktereket escape-elni, valamint kerülni a szükségtelenül széles karakterosztályokat.

Az atomi csoportok a megfeleltetés után nem lesznek újra kiértékelve, még akkor sem, ha a minta fennmaradó része az egyezés miatt meghiúsul. Ez jelentősen javíthatja a teljesítményt, ha a kvantátorok az atomi csoportban vagy a minta fennmaradó részében fordulnak elő.

Gyakorlati minták

CélMintaJelentés
Nem számjegy[^0-9]Egyetlen karakter, amely nem számjegy
Nem kisbetű[^a-z]Egyetlen karakter, amely nem az angol kisbetűs tartomány része
Nem vesszőig tartó rész[^,]+Legalább egy, vesszőtől eltérő karakter
Java, de nem JavaScriptJava(?!Script)A „Java” csak akkor illeszkedik, ha nem követi „Script”
Nem „Borneo” előtt álló Script(?<!Borneo)ScriptA „Script” csak akkor illeszkedik, ha előtte nem „Borneo” áll
Nem „The” után álló cat(?<!(T|t)he\s)catOlyan „cat”, amelyet nem előz meg „The” vagy „the” és szóköz
Nem számot tartalmazó teljes szöveg^\D+$A teljes szöveg minden karaktere nem számjegy
Nem üres, nem szóköz karakterekből álló rész\S+Legalább egy nem szóköz karakter

A tagadás ellenőrzésének módszere

A reguláris kifejezés minden egyes karaktere össze lesz hasonlítva a bevitt karakterlánc minden elemével egymás után. A minta egy vagy több karakterkonstansból, operátorból vagy szerkezetből áll.

Először döntse el, hogy egyetlen karaktert szeretne kizárni, egy karaktertartományt, egy teljes részkarakterláncot, egy előző környezetet vagy a teljes regex találati eredményét. Egyetlen karakterre a [^...], következő szövegre a (?!...), előző szövegre a (?<!...), teljes eredményre pedig a megfelelő nem egyezési operátor használható.

Ezután ellenőrizze, hogy a ^ valóban a karakterosztály első karaktere-e, a pontok megfelelően escape-eltek-e, a horgonyok nem kerültek-e összetévesztésre a tagadással, és a keresés teljes vagy részleges regexként működik-e.

A reguláris kifejezéseket érdemes egyszerűen tartani, mert így más felhasználók könnyebben átláthatják és módosíthatják őket. A fordított törtjellel (\) feloldhatja egy karakter reguláris kifejezések helyettesítő karaktereként való használatát, amikor azt szeretné, hogy az adott karaktert szó szerint értelmezzék.

noimgs

tags: #tagadas #a #regularis #kifejezesbe