Regulární výrazy (2.) – základní operátory

V prvním díle série o regulárních výrazech jsem lehce naťukl, co to vlastně ty regulární výrazy jsou a na jaké problémy se musíte připravit, pokud chcete regulární výrazy používat. Také jsem ale říkal, že přes to všechno jsou regexpy nesmírně užitečným nástrojem, který stojí za to umět používat. V dnešním díle už dojde na lámání chleba: pustíme se do základních regulárních výrazů.

Pozn.: V popisu syntaxe vycházím zejména z originální dokumentace na stránkách Regular Expressions.info, příklady jsou z mojí praxe.

Prostý text

Při běžném vyhledávání zadáte hledané slovo a text, ve kterém to slovo hledáte, a počítač vám najde první výskyt hledaného slova v tomto textu. Vyhledávání pomocí regulárních výrazů funguje v zásadě stejně – s tím důležitým rozdílem, že některé znaky nebo jejich kombinace mají speciální význam, který právě dělá regulární výrazy tak užitečnými. Tyto speciální znaky jsou: . (tečka), * (hvězdička), + (plus), ? (otazník), ^ (stříška), $ (string), (, ), [, ], {, } (všechny tři typy závorek)1, | (roura) a \ (zpětné lomítko). Všechny ostatní znaky značí „samy sebe“.

Příklad: Výraz pepak vyhledá text „pepakova stránka“, stejně jako by to fungovalo v běžném vyhledávání.

Pozor, pokud není stanoveno jinak (použitím modifikátorů, které mění režim vyhledávání), je všechno hledání tzv. case-sensitive, čili záleží na velikosti písmenek („pepak“ najde něco jiného než „Pepak“ nebo „PEPAK“).

Zpětné lomítko \ má v regulárních výrazech asi deset různých významů, a jedním z nich je ten, že dovoluje vložit speciální znaky výše jako prostý text: prostě napíšete zpětné lomítko následované speciálním znakem a program to pochopí tak, že chcete vyhledat přímo ten speciální znak, se zanedbáním jeho významu. Chcete-li tedy v textu vyhledat (c), napíšete regulární výraz jako \(c\). To platí pro všechny speciální znaky – zpětné lomítko vyhledáte jako \\. (Pokud je zpětných lomítek víc v řadě, vyhodnocují se postupně zleva.)

Pomocí zpětného lomítka můžete také zapsat řídící znaky (s ASCII/Unicode kódem nižším než 32) nebo obecně jakýkoliv znak, který zrovna neumíte přímo napsat na klávesnici, ale znáte jeho číselný kód: Dělá se to sekvencí \xab, kde ab je kód znaku v hexadekadické soustavě – tj. např. \x40 pro zavináč (@) nebo \x1b pro Escape.

Některé často používané řídící znaky mají svůj speciální symbol, aby si člověk nemusel pamatovat jejich kódy: CR (Carriage Return, návrat kurzoru na začátek řádku) se zapisuje jako \r, LF (Line Feed, posun na další řádek) jako \n (pod Windows je zvykem, že řádky končí oběma těmito znaky, tj. \r\n). Tabulátor má znak \t, \e je jiný výraz pro Escape. Existuje ještě několik dalších zkrácených zápisů, které už ale nejsou tak časté a mě osobně připadá přehlednější v takových případech použít přímo kód. Ale pokud máte zájem, můžete si je dohledat na výše odkazované stránce.

Pozor na jednu věc: Řídící znaky sice z hlediska regulárních výrazů nejsou speciální a tudíž se vyhledávají „normálně“, ale neplatí to úplně stoprocentně. Speciálně znaky konce řádku (CR a LF) svoje chování dost mění podle toho, v jakém režimu zrovna hledání běží. Editory například standardně fungují v režimu, kdy každý řádek textu zpracovávají zvlášť, a v takovém případě pochopitelně ani CR ani LF nenajdete, protože v hledaném textu vlastně „nikdy nejsou“.

Velmi užitečnými symboly jsou \Q a \E (vždy v tomto pořadí): \Q říká, že „od tohoto místa až do nejbližšího \E budou všechny znaky chápány doslovně“. Tzn. pokud zrovna chcete vyhledat „.+*?“, můžete to místo dlouhého a nepřehledného zápisu se zpětnými lomítky (\.\+\*\?) zapsat přehledněji jako \Q.+*?\E.

 

1) U těch závorek to není úplně přesné. Regular-Expressions.info nezařazuje pravou hranatou závorku mezi speciální znaky vůbec a složené závorky jen v případě, že má jejich obsah jeden ze speciálních tvarů. Je však třeba zdůraznit, že toto je zrovna jeden z případů, kdy se jednotlivé implementace regulárních výrazů liší a považuji za bezpečnější chovat se ke všem závorkám, jako kdyby speciálními znaky byly – není mi známa implementace, která by s tímto „opatrným“ přístupem nefungovala.

Tečka

Znak tečky (.) v regulárním výrazu znamená „libovolný znak“. Regexp m.ž najde jak text „muž“ tak „mlž„, ale nenajde „mž“ – bylo řečeno, že mezi „m“ a „ž“ má být libovolný znak, ale to je něco jiného než „libovolný nebo žádný znak“ – za chvíli se k tomu dostaneme. Pozor, u znaků CR a LF platí obdobná výhrada, jakou jsem napsal výše – v normálním režimu by bylo přesnější říci, že tečka znamená „libovolný znak kromě CR a LF“; pokud má tečka skutečně znamenat „libovolný znak“, musí se zapnout speciální režim.

Množiny znaků

Množiny znaků reprezentují jeden znak v textu, ale dávají na výběr z více možností, kterých tento znak může nabývat. Možných zápisů je několik, začneme tím základním

[abcd] – v hranatých závorkách je uveden seznam možných znaků, z nichž v textu bude jeden. Výraz [tuv]ůl najde slova „stůl“ nebo „vůl„, ale nenajde třeba „kůl“ (protože před „ůl“ musí být jedno z písmen „t“, „u“ nebo „v“, ale „k“ tam povoleno není).

Pokud by mělo být povolených znaků více a vám se nechtělo je všechny vypisovat, můžete si usnadnit práci znakem - (mínus), kterým se zapisují rozsahy: [a-z] značí všechna písmena od „a“ do „z“, [1-4] jsou číslice „1“ až „4“. Pozor ale na to, že se tu pohybujeme v rámci tabulky ASCII, resp. ANSI (podle implementace) – zápis [a-e] přesněji vyjadřuje „znaky s ASCII kódem větším nebo rovným 97 a menším nebo rovným 101“ (97 je ASCII kód pro „a“, 101 pro „e“), bez nějaké návaznosti na naši abecedu – konkrétně znaky s diakritikou jsou v ANSI až někde daleko vzadu za „z“, takže [a-e] sice obsahuje „a“, „b“, „c“, „d“ a „e“, ale už ne „á“, „č“ nebo „ď“. Ze zpřesněné definice a pohledu do ASCII tabulky také vyplývá, že zatímco [A-z] obsahuje všechna písmena latinské abecedy (plus několik dalších znaků jako [ nebo ^), „skoro stejná“ [a-Z] neobsahuje nic (protože „Z“ je v ASCII před „a“, tudíž neexistuje znak, který by byl současně za „a“ a před „Z“).

Oba přístupy výše lze zkombinovat. Můžete třeba napsat [0-9xy] (nebo, se stejným významem, [y0-9x] či [xy0-9]) ve smyslu „jakákoliv číslovka nebo písmena x a y“.

Někdy se hodí opačný přístup – místo uvedení znaků, které chcete, uvést znaky, které nechcete. Uděláte to tak, že na úplný začátek závorek dáte znak stříšky (^): [^0-9] je „cokoliv kromě číslovek“, [^aeiouy] značí „všechno, jen ne samohlásky“.

Pokud vás zajímá, jak do množiny dostat pravou hranatou závorku, mínus nebo stříšku v jejich „přirozeném“ významu, tak opět pomocí zpětného lomítka: Výraz [\]] najde pravou hranatou závorku, výraz [0-9\-] najde „jakoukoliv číslici nebo mínus“. Lze se obejít i bez zpětného lomítka – pokud speciální znaky napíšete na místo, kde „nedávají smysl“, budou chápány ve své přirozené podobě: []^-] najde kterýkoliv ze znaků ], ^ a - (proč: protože když otvíráme množinu znakem [, nedává smysl ji zase hned zavřít, protože by nikdy nemohla být vyhledána; stříška ve smyslu negace má být jako první znak, a mínus vyznačující rozsah musí být ukončeno koncovým znakem rozsahu). Ale stejně jako výše – že to tak jde napsat ještě neznamená, že je také rozumné to dělat; doporučuji raději využít zpětného lomítka.

Stejně jako u řídících znaků i u množin existují pro některé často používané množiny zkrácené zápisy: \d značí „všechny číslice“ (tj. totéž jako [0-9]), \w všechny alfanumerické znaky a podtržítko ([a-zA-Z0-9_] – tj. znaky, ze kterých se v programovacích jazycích tradičně mohou tvořit identifikátory) a \s všechny „bílé znaky“ (mezera, tabulátor, CR a LF – [ \t\r\n] – opět se stejnou výhradou jako u tečky). Ve variantě s velkým písmenem (\D, \W, \S) pak jako negace – „všechno kromě číslic“, „všechno kromě alfanumerických znaků a podtržítka“ a „všechno kromě bílých znaků“.

Výslovně upozorňuji, že se zde jedná jen o písmena latinské abecedy, tzn. žádná diakritika! Symboly pro „všechna písmena, včetně národních“ také existují, ale dostaneme se k nim až o dost později.

Kotvy

Uznávám, zní to jako hloupě použitý doslovný překlad, ale následující skupina symbolů skutečně „ukotvuje“ regulární výraz na nějaké pevné místo.

Stříška ^ symbolizuje začátek řádku, respektive začátek řetězce, a dolar $ naopak konec řádku (řetězce). Pokud hledám v textu „Mám rád regexpy“, tak výrazy ^M i [aeiouy]$ budou spokojeny („Mám rád regexpy“, resp. „Mám rád regexpy„), ale ^[aeiouy] nenajde nic (protože text nezačíná žádnou samohláskou).

^$ značí prázdný řádek – hledáme začátek řádku a hned po něm konec řádku; mezi nimi nemá být nic.

U kotev se opět setkáváme s tím, že regulární výrazy mohou být vyhodnocovány v několika režimech, a podle režimu se mění význam některých symbolů. Stříška a dolar jsou například závislé na tom, jestli je text zpracováván po řádcích (což je typické pro editory a některé programovací jazyky) nebo jako jeden dlouhý řetězec znaků (to je zase typické pro druhou skupinu programovacích jazyků). V prvním případě (po řádcích) symbolizují stříška a dolar začátek a konec řádku, v druhém případě (všechno v jednom) začátek a konec textu.

Kvantifikátory

„Kvantifikátory“ rozumíme symboly, které stanovují počet výskytů předchozího znaku. Až dosud jsme nic takového neřešili – a znamenalo „jeden znak a“, . byla „jeden libovolný znak“ a [aeiouy] byla „jedna samohláska“. Kvantifikátory slouží právě k tomu, aby se dalo říct „libovolný počet libovolných znaků“ nebo „dvě nebo víc po sobě následující samohlásky“. Kvantifikátor vždy následuje bezprostředně za symbolem, jehož množství stanovuje.

Základní kvantifikátory jsou:

  • Plus +: Kvantifikovaný symbol se může vyskytovat v libovolném množství, ale nejméně jednou. [0-9]+ najde libovolné celé číslo, [a-z]+a libovolné slovo (ale jen z latinských písmen!) končící na „a“, ovšem kromě samotného „a“ (protože před „a“ musí být aspoň jedno písmeno, právě díky plusu). Velmi užitečný výraz pro zpracování elektronických knih je ^.+$, který znamená „libovolný neprázdný řádek“ (chceme začátek řádku, potom aspoň jeden libovolný znak, a potom konec řádku).

  • Hvězdička *: Funguje téměř stejně jako plus, ale vezme jakýkoliv počet výskytů, včetně nuly. Používá se tehdy, když se nějaký symbol vyskytovat může, třerba i opakovaně, ale nemusí. Vrátím-li se k příkladu s plusem, tak výraz [a-z]*a najde libovolné slovo končící na „a“, včetně „a“ samotného (protože před tím koncovým „a“ sice může být libovolný počet písmen, ale díky hvězdičce tam být nemusí).

  • Otazník ?: Kvantifikovaný symbol se vyskytuje jednou nebo vůbec. Otazník hodně používám v YouTube Downloaderu ve výrazech typu https?://.+ (zajímají mě odkazy typu http://něco i https://něco, které zpracovávám takřka stejně – tudíž je evidentní, že na tom, jestli za „http“ bude nebo nebude „s“, je mi celkem jedno – vyhovuje mi obojí.

  • {číslo}: Tento zápis říká, že se předchozí symbol má vyskytovat právě tolikrát, kolikrát jsem napsal do závorek. Příkladem je "[0-9]{5}" – tj. libovolné pěticiferné číslo v uvozovkách. Pozor na to, že u tohoto typu zápisu obvykle potřebujete mít kolem kvantifikovaného výrazu ještě nějaké další prvky, jinak dostanete i některé nečekané výsledky – kdybych předchozí výraz upravil na [0-9]{5}, tak najde i šesti a víceciferná čísla (protože ta začínají, nebo končí, nebo ho obsahují, tím pěticiferným).

  • {od,do}: V tomto zápisu mě zajímá počet výskytů v určitém rozsahu. "[0-9]{3,5}" tak značí tří, čtyř nebo pěticiferné celé číslo v uvozovkách.

  • {,do} a {od,}: Pokud jednu z číslic vynecháte, dosadí se na ni nula (v prvním případě) nebo nekonečno (v druhém případě).

V souvislosti s kvantifikátory musím zmínit jednu naprosto klíčovou věc, která je však často zanedbávána a vede pak k docela nepříjemným výsledkům: regulární výrazy jsou ve svém standardním režimu „hladové“ (greedy). To znamená, že když narazí na kvantifikátor, snaží se „pohltit“ (zpracovat) co nejvíc znaků jde. Příklad vydá za tisíc slov:

Procházím nějakou WWW stránku a hledám v ní odkazy na videa, tj. texty typu <a href="http://www.server.cz/video"> (ve skutečnosti je to o něco složitější, ale pro demonstraci to stačí takhle). Tudíž se nabízí použít na to výraz \shref=".+" (mezera, „href“, rovná se, uvozovky, a pak libovolný počet libovolných znaků až do následujících uvozovek). Jenže tady právě zaúřaduje hladovost regexpu: Co když mám ve stránce adresy dvě (<a href="http://www.server.cz/video">... nějaké texty atd... <a href="http://www.jinyserver.cz/jinevideo">)? Hladovost říká, že se má počítač snažit zpracovat co nejvíc znaků, v tomto případě teček (tj. libovolných znaků). Místo očekávaného výsledku href="http://www.server.cz/video" tak dostanu href="http://www.server.cz/video">... nějaké texty atd... <a href="http://www.jinyserver.cz/jinevideo", protože to také vyhovuje zadanému výrazu a protože tak bylo zpracováno víc znaků, požadavek na hladovost tomu dal přednost.

Cesty, jak tomu zabránit, jsou v principu dvě: Mohu zpřesnit vyhledávací výraz, aby se včas zastavil; v tomto případě by stačilo místo tečky (libovolný znak) použít [^"] (cokoliv kromě uvozovek), protože to se zarazí na prvních uvozovkách a dál to nemůže jít. No a druhá cesta je přepnout automat zpracovávající regulární výraz do tzv. ungreedy nebo lazy (líného) režimu. Jde to udělat na úrovni celého výrazu, ale to si nechám na později; prozatím postačí informace, jak do lazy režimu přepnout jeden konkrétní kvantifikátor: stačí za něj zapsat otazník. +? je tak „líné plus“ (jeden nebo víc symbolů, ale snažit se jich zpracovat co nejmíň), *? je „líná hvězdička“ a ?? je „líný otazník“. Totéž platí i o složených závorkách, i ty jde „zlenivět“ otazníkem. Chybný výraz výše bych tak mohl přepsat na funkční \shref=".+?".

Líné kvantifikátory jsou velice užitečné všude, kde se mohou hledané výrazy i několikrát opakovat. Přitom ale nejsou příliš známé a používané, zdá se, že většina lidí preferuje cestu zpřesňování výrazů. Myslím, že to je škoda – lenost je stejně účinná, ale obvykle mnohem jednodušší 🙂

 

V příštím díle seriálu se podíváme na to, co všechno jde provádět se závorkami. Bude toho hodně.

Podobné příspěvky:

5 komentářů “Regulární výrazy (2.) – základní operátory”

  1. avatar x napsal:

    http://regjex.com/

    moj oblubeny online regexp tester

  2. avatar pepak napsal:

    Počkej si na třetí nebo možná čtvrtý díl, tam to bude.

  3. avatar petr napsal:

    ty množiny znaků jsou hezký, ale jak udělat vyloučení třeba slova? u písmen ze slova „vlk“ to jde pomocí [^vlk], ale jak udělat skutečně vyloučení celého slova? dík

  4. avatar pepak napsal:

    Další díly budou ještě lepší. Tohle jsou vesměs staré známé věci, ty skoro neznámé teprve přijdou 🙂

  5. avatar Rudy Korinek napsal:

    Opet vynikajici clanek pepaku! 🙂 vcetne prvniho dilu. Uz jsi par dni lamu hlavu nad cistenim tagu v html na Macovi, preci uenom tam nemam PSPad a tenhle „serial“ mne moc pomaha! Dekuju 🙂

Leave a Reply

Themocracy iconWordPress Themes

css.php