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 typuhttps?://.+
(zajímají mě odkazy typuhttp://něco
ihttps://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ě.
http://regjex.com/
moj oblubeny online regexp tester
Počkej si na třetí nebo možná čtvrtý díl, tam to bude.
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
Další díly budou ještě lepší. Tohle jsou vesměs staré známé věci, ty skoro neznámé teprve přijdou 🙂
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 🙂