Perl (16) - Regulární výrazy - začínáme

Dneškem začíná v rámci seriálu Perl miniseriál o nesmírně mocném nástroji - regulárních výrazech.

19.10.2005 07:00 | Jiří Václavík | přečteno 58522×

Regulární výrazy (anglicky regular expressions, někdy zkráceně označované jako regexp, regex nebo RE) jsou reprezentací regulárních jazyků v teorii konečných automatů.

Jejich aplikace (ve smyslu zda řetězce odpovídají vzoru) se poprvé objevila v unixových nástrojích pro editaci a vyhledávání v řetězcích - tedy sed, vi, grep, awk a další (více o historii například na Wikipedii). Nejsou mezi sebou úplně kompatibilní, protože většinou bylo k původním regulárním výrazům něco přidáno (potom tedy reprezentují nadmnožinu regulárních jazyků). V praxi se to projevuje tak, že to, co funguje awku, nemusí fungovat v grepu nebo může mít jinou syntaxi apod.

Mezi nástroje, které umí regulární výrazy se samozřejmě řadí i Perl. Jen těžko byste hledali jiný jazyk, který by podporoval regulární výrazy odpovídající tak silné množině jazyků. Navíc spojením kvalitního skriptovacího jazyka s regulárními výrazy získáváme do svého arzenálu nesmírně silný nástroj. Toto je jeden z hlavních důvodů, proč se Perl těší oblibě, kterou má. Proto ani zde nebudeme regulárními výrazy šetřit a pokusíme se je přiblížit opravdu podrobně.

Co je to regulární výraz?

Regulární výraz si můžeme představit jako speciální řetězec, který je šablonou vystihující určitý jazyk (tj. množinu textových řetězců). Každý textový řetězec takové šabloně buď vyhovuje nebo ne. S touto množinou vyhovujících řetězců můžeme manipulovat - hledat její prvky v textu nebo je nahrazovat jiným řetězcem.

Vzpomeňme si, že jsme na regulární výrazy již natrefili při popisu funkcí grep a split. To jsme ale jen uvedli příklady a dále je nerozebírali.

Formální definice regulárních výrazů

Nechť X = {x1, ..., xn} je nějaká konečná neprázdná abeceda a l označuje prázdné slovo. Pak množinou všech regulárních výrazů RE(X) nad abecedou X je nejmenší množina slov v abecedě {x1, ..., xn, {}, l, *, +, ., (, )}, pro kterou platí:

Regulárním výrazem tedy je například (((a*((b+c).d)*)+e)*f). Některé závorky a tečky lze po dohodě vynechat a můžeme psát ((a*(b+cd)*)+e)*f.

Hodnotou regulárního výrazu r je jazyk L = [r], pro který platí:

Regulární výrazy odpovídají regulárním jazykům. Regulární výrazy v Perlu jsou nadmnožinou formální definice. Třeba proto, že obsahují i jazyk L = {0n1n}, který regulární není. Ačkoliv to není formálně v pořádku, budeme je nazývat regulárními výrazy i nadále.

Regulární výrazy versus žolíkové znaky

Regulární výrazy jsou někdy zaměňovány se žolíkovými znaky. Obecně, žolíkový znak může vyhovovat jednomu nebo více znakům. Oproti tomu regulární výraz je řetězec s jasně určenými podmínkami a žádný samotný znak zde nezastupuje více znaků. Například * v žolíkových znacích znamená libovolný řetězec, ale v regulárních výrazech libovolný počet opakování. Žolíkové znaky se používají téměř výhradně k hledání v názvech souborů.

Regulární výrazy v Perlu

Základní syntaxe

Regulární výraz (od teď již jen v perlové terminologii) je výraz, který má dvě možné vyhodnocení - buď true nebo false. To znamená, že následující zápis vrací 1, pokud se v řetězci vyskytuje podřetězec "vzor", v opačném případě prázdný řetězec.

"řetězec" =~ m/vzor/

Regulárnímu výrazu m/vzor/ vyhoví takové řetězce jako "vzory", "re vzor" nebo "vzor". Operátor =~ slouží k porovnávání vzorů. S přiřazováním má společný opravdu jen ten znak rovnítka, jinak jde o odlišné operace.

Následující díly seriálu se tedy budou věnovat prakticky pouze tomu, co napsat místo vzoru, abychom vytvořili požadovanou šablonu. Dodejme, že v Perlu 6 dojde ke kompletnímu přepracování regulárních výrazů a už nepůjde jen o úpravu jednoho řetězce. Budou mít podstatně intuitivnější strukturu, více integrované do jazyka a navíc ještě o něco silnější.

Další možná uvození

Uvozující m není povinné, navíc můžeme stejně jako u nám už známých q a qq lomítka nahradit jiným znakem. Negovaný operátor =~ se zapisuje jako !~.

print "řetězec" =~ m"vzor";
print "řetězec" =~ m!vzor!;
print "řetězec" =~ /vzor/; #nejběžnější užití
print "řetězec" !~ /vzor/;

Existuje několik speciálních ohraničení pro regulární výrazy. Použijí-li se apostrofy, neprobíhá vkládání obsahu proměnných. Nelze tedy psát:

$vzor = "[abc]{2}";
"aa" =~ m'$vzor'; #$vzor není nahrazen obsahem proměnné, ale je brán jako řetězec '$vzor'

Další možností je ohraničení otazníky (potom uvedení m není povinné). V takovém případě dojde k použití vzoru pouze jednou (i v testu cyklu s modifikátorem g). Teprve až je zavolána funkce reset, může se porovnání opakovat. Tuto konstrukci uvádíme pouze pro zajímavost a nelze ji doporučovat. My se budeme striktně držet používání lomítek.

Užití v testech podmínek

Porovnávání řetězců se užívá jako test při rozhodování.

$veta = "Existuje 10 druhů lidí - ti, kteří znají binární kód, a ti, kteří ne.";

if ($veta =~ m/10 druhů/){
    print "'10 druhů' je ve větě!\n"
}

if ($veta =~ m/777/){
    print "'777' je ve větě!\n"
}

"10 druhů" se v řetězci vyskytuje, proto byl v tomto případě výraz vyhodnocen jako true. Nezáleží na tom na jaké pozici nebo co je okolo.

Použití výchozí proměnné

Je také možné uvést jako test podmínky jen čistě regulární výraz v uvozovacích znacích. Implicitně je porovnáván s výchozí proměnnou. Příklad má stejný účinek jako předchozí, ale využívá této vlastnosti:

$_ = "Existuje 10 druhů lidí - ti, kteří znají binární kód, a ti, kteří ne.";

if (/existuje/){
    print "'existuje' je ve větě!\n"
}

if (/Existuje/){
    print "'Existuje' je ve větě!\n"
}

Poslední ukázka tiskne řetězec "'Existuje' je ve větě!". Na velikosti písmen tedy implicitně záleží. Způsob, kterým lze toto chování změnit, si představíme v jednom z příštích dílů.

V případě zkráceného zápisu pomocí výchozí proměnné je též možné negovat. Stačí přidat vykřičník před úvodní /.

if (!/2 druhy/){
    print "'2 druhy' není ve větě!\n"
}

Metaznaky

Mezi lomítka lze napsat všechny znaky znaky kromě takzvaných metaznaků. Mají totiž nějakou jinou funkci. Patří mezi ně *, +, ., ?, ^, $, (, ), {, }, [, ], |, /. Lze je nahradit předřazením zpětného lomítka. Pokud si nejste jisti, zda má znak speciální význam a není alfanumerický (tedy pokud je z množiny \W), můžeme mu předřadit zpětné lomítko vždy. Sekvence zpětného lomítka a nealfanumerického znaku se chová vždy jako znak bez speciálního významu.

Proměnné v regulárních výrazech

Než se začne regulární výraz vyhodnocovat, jsou nahrazeny proměnné ve vzoru svým obsahem (což ale samozřejmě neplatí u ohraničení pomocí apostrofů).

$cisla = "2005";
print "0002005000" =~ /$cisla/;

Druhý řádek předchozího kódu bude fungovat takto:

print "0002005000" =~ /2005/;

Návratová hodnota výrazu řetězec =~ vzor

Tuto část lze zatím klidně přeskočit, protože je zde řada věcí, které jsme zatím neprobírali. Je to ale téma, které by v prvním dílu o regulárních výrazech chybět nemělo.

skalární kontext

Ve skalárním kontextu vrací v případě úspěchu 1, v případě neúspěchu nepravdivou hodnotu.

seznamový kontext

O něco složitější je to v seznamovém kontextu. Při neúspěchu bez modifikátoru g vrací prázdný seznam. V případě úspěchu vrací seznam zapamatovaných hodnot, pokud jsou, jinak vrací seznam (1). S modifikátorem g vrací výraz pole vyhovujících podřetězců.

$retezec = "12345";
@return1 = $retezec =~ /\d\d\d/;
@return2 = $retezec =~ /(\d)(\d)(\d)/;
@return3 = $retezec =~ /(\d)/g;

Pole @return1 obsahuje prvek (1). Žádná hodnota nebyla zapamatovaná. Oproti tomu v poli @return2 už takové hodnoty byly: jeho prvky (1, 2, 3) jsou právě zapamatovanými hodnotami. Poslední pole obsahuje (1, 2, 3, 4, 5), protože se vzor aplikoval celkem 5×.

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=947