☰ menu
Pavel Satrapa

Regulární výrazy - část 7
Speciality Perlu

Na poslední díl našeho seriálu jsem si pošetřil speciality jazyka Perl. Pokud se vám při zahlédnutí jména Perl povážlivě zvedá hladina adrenalinu, podívejte se alespoň na konec článku. Najdete tam odkazy na literaturu a srovnávací tabulku speciálních znaků, kteréžto materiály by se vám mohly hodit.

Perl je do značné míry srdeční záležitost. Znám řadu jeho vášnivých zastánců a zrovna tak jeho zuřivé odpůrce. Ten jazyk je pohledem teoretika mimořádně odporný, ale zároveň neuvěřitelně mocný a praktický. Docela výstižně (a velmi vtipně) to myslím vyjádřil Jeffrey E. F. Friedl ve své knize o regulárních výrazech:

Síla Perlu může být ničivou zbraní v rukách zkušeného uživatele, zdá se však, že v Perlu sbíráte zkušenosti tak, že se opakovaně střílíte do nohy.

Dnes se ale nehodlám věnovat jazyku jako takovému. Raději se soustředím na jeho speciality v oboru regulárních výrazů. A v tomto směru je v současnosti jasná jednička, ať se to jeho odpůrcům líbí nebo ne.

Uvolněná syntax

Regulární výrazy jsou velmi kondenzované, což je bohužel činí obtížně srozumitelnými. Obecně si troufám prohlásit, že regulární výraz je často snadnější vytvořit než jej sám po sobě pochopit.

Do jazyka Perl (přesněji řečeno do verze 5, ale ta už je na světě pět let, takže ji lze považovat za standard jazyka) proto přibyla možnost rozvolnění zápisu regulárních výrazů. Nepřidává žádné nové konstrukce, jen je umožňuje srozumitelně zapsat.

Uvolněnou syntax zapíná volba x v operátoru pro hledání či nahrazování (operátory m a s). Regulární výraz se pak zapisuje do složených závorek podobně jako blok programu, ignorují se v něm mezery a konce řádků (pokud jim nepředchází zpětné lomítko) a komentáře zahájené znakem #.

Příklad:
V části o zapamatování jste se mohli setkat s nechutnou substitucí

s/([^:]*):([^:]*:){3}([^:]*).*/<a href="/~\1">\3<\/a>/

která z řádků v /etc/passwd vyráběla seznam domácích stránek uživatelů. S využitím uvolněné syntaxe bychom ji mohli přepsat následovně:

s{  
   ([^:]*):  #přihlašovací jméno (1)
   ([^:]*:){3}  #přeskočíme heslo, UID a GID
   ([^:]*)  #vlastní jméno (3)
   .*  #přeskočíme zbytek
}{<a href="/~\1">\3</a>}x

Nepřipadá vám to o dost srozumitelnější? Upozorňuji, že uvolněná syntax se týká pouze regulárního výrazu, nikoli nahrazujícího řetězce. V něm se projeví pouze tím, že je uzavřen do složených závorek.

Drobnosti, které potěší

Perl nabízí řadu konstrukcí, které sice nepřinášejí nějakou zásadní inovaci stran schopností regulárních výrazů, ale v běžném životě silně potěší. Asi nejčastěji používané jsou kategorie znaků. Z těch nejběžnějších lze jmenovat:

Zápis Význam Odpovídá
\d číslice [0-9]
\D nečíslice [^\d]
\w alfanumerický znak [a-zA-Z_0-9]
\W nealfanumerický znak [^\w]
\s prázdný znak [\ \t\n\r]
\S neprázdný znak [^\s]

Tyto speciální znaky celkem výrazně přispívají ke srozumitelnosti regulárních výrazů. Navíc pokud svůj program zahájíte příkazem use locale, budou mezi alfanumerické znaky zařazeny i akcentované znaky národní abecedy.

Příklad:
Výměnu prvních dvou slov na řádku pak zajistí celkem mírumilovný příkaz

s/(\w+)(\W+)(\w+)/\3\2\1/

Kromě Perlu už převzaly podobné znakové kategorie i současné verze dalších nástrojů (konzultujte s dokumentací). Kromě nich je leckde podporován i zápis kategorií podle POSIXu, kde se například číslice zapisuje jako [:digit:], alfanumerický znak jako [:alnum:] a prázdné místo [:space:]. Tyto kategorie se však zapisují mezi [], takže například řádek začínající číslicí se vyjádří pomocí ^[[:digit:]], což už má k eleganci poměrně daleko (totéž v Perlu: ^\d). Perl POSIXové kategorie podporuje až od verze 5.6.

Syté (též líné) opakování pomůže řešit problémy s nadměrnou žravostí opakovacích konstrukcí. Zapisuje se jednoduše: za opakovací znak (*, +, ? či {min,max}) připojíte otazník. Přípustný počet opakování se tím nijak nezmění, ale na rozdíl od klasické varianty se snaží, aby opakování bylo co nejméně. Takže třeba oblíbený řetězec v uvozovkách lze hledat pomocí ".*?". Je to přehlednější, ale pomalejší než obvyklé řešení "[^"]*".

Třetí užitečnou drobností jsou závorky bez zapamatování. Slouží výlučně k vymezení části regulárního výrazu (např. pro opakování nebo omezení působnosti "nebo"). Vyhovující řetězec se neukládá, takže vám ubyde špetka starostí při počítání indexů těch zapamatovaných. Tyto speciální závorky se zapisují ve tvaru (?:...).

Příklad:
Ještě jednou přepracuji příklad pro transformaci řádku z /etc/passwd na položku v seznamu domácích stránek. S využitím popsaných konstrukcí by mohl vypadat takto:

s{  
   (.*?):  #přihlašovací jméno (1)
   (?:.*?:){3}  #přeskočíme heslo, UID a GID
   (.*?):  #vlastní jméno (2)
   .*  #přeskočíme zbytek
}{<a href="/~\1">\2</a>}x

Tentokrát se v části přeskakující heslo, UID a GID vyhnu zapamatování, takže pod čísly 1 a 2 mám uloženy skutečně jen ty informace, které mne zajímají. Dvojtečka z konstrukce (?: se v tomto případě bohužel nepěkně plete s oddělovačem položek, ale s tím se nedá nic dělat. Líné opakování mi umožnilo poněkud zjednodušit výrazy pro jednotlivé části řádku.

Vyhlížení

Dost silný nástroj pro některé speciální případy nabízí tak zvané vyhlížení (anglicky lookahead). Existuje ve dvou odrůdách: pozitivní vyhlížení se zapisuje v podobě (?=výraz) a je splněno, pokud následující část řetězce vyhovuje výrazu. Negativní vyhlížení (?!výraz) naopak uspěje, pokud výrazu nevyhovuje. Důležité je, že vyhlížení se jen podívá, zda se vzor dá či nedá najít, ale neposouvá zkoumanou pozici dál (jemu odpovídající řetězec je vždy prázdný).

Příklad:
Vzoru \d+(?= Kč) vyhoví neprázdná posloupnost číslic, ale jen pokud za ní následuje řetězec " Kč". Ten však sám o sobě není zahrnut do vyhovujícího řetězce. Takže pokud příkaz

s/(\d+)(?= Kč)/???/g

vypustíte na řetězec

Párátka 20 CX Turbo za 25 Kč kus

obdržíte výsledek

Párátka 20 CX Turbo za ??? Kč kus

Za příklad negativního vyhlížení může posloužit (?!000)\d\d\d, kterému vyhoví libovolná trojice číslic s výjimkou 000.

Faktem je, že vyhlížení není principiálně nezbytné. Zpravidla je lze nahradit jinými konstrukcemi jazyka. Například zachování " Kč" za skupinou číslic by se dalo zařídit pomocí zapamatování. Test na trojici číslic, ne však 000 by se dal rozložit do dvou nezávislých testů a podobně. V některých případech však vyhlížení dává zajímavé možnosti.

Příklad:
Hezkou vychytávkou využívající vyhlížení je oddělování řádů ve velkých číslech. Mezi trojice číslic se mají vložit mezery, takže z "12345678" vznikne "12 345 678". Vtip je v tom, že se trojice počítají odzadu, na což regulární výrazy nejsou příliš zařízené. Vyhlížení umožňuje následující řešení:

s{  
   (\d{1,3})  #za první skupinu patří mezera
   (?=  #ale jen když následuje
      (?:\d\d\d)+  #alespoň jedna trojice číslic
      (?!\d)  #a tím číslo končí
   )  
}{\1 }gx

Díky vyhlížení se můžete přesvědčit, že počet číslic, které následují za aktuální pozicí ve zpracovávaném řetězci, je násobkem tří. Vyhlížení zároveň zajistí, že se tato pozice nezmění a že se při příštím opakování (díky volbě g se provádí pro všechny výskyty) se bude pokračovat za naposledy vloženou mezerou.

Literatura

Pokud je mi známo, vyšla zatím jediná kniha věnovaná výlučně regulárním výrazům. Napsal ji Jeffrey E. F. Friedl a jmenuje se Mastering Regular Expressions (vydalo nakladatelství O'Reilly & Associates v roce 1997, ISBN 1-56592-257-3). Není to pochopitelně vyslovená oddechovka, ale v rámci možností vysvětluje vlastnosti regulárních výrazů, vhodné a nevhodné techniky pro jejich vytváření. Vzhledem k rozsahu přes 300 stran si může dovolit jít dost do hloubky.

Cenné služby odvede kniha Unix Power Tools, jejímiž autory jsou Jerry Peek, Tim O'Reilly a Mike Loukides. Vyšla také u O'Reilly & Associates v roce 1997 (2. vydání, ISBN 1-56592-260-3). Obsahuje velmi slušnou kapitolu o regulárních výrazech a kromě ní řadu tipů a triků na využívání programů, které s nimi pracují. Osobně ji považuji za jednu z nejpřínosnějších knížek, které jsem kdy měl v ruce.

Přehledová tabulka

Jako závěrečnou přílohu vám nabízím stručný přehled regulárních výrazů v nejběžnějších nástrojích. Konkrétně se jedná o GNU grep a egrep verze 2.3, GNU awk verze 3.0.4, vim verze 5.5 a Perl verze 5.005_03. Tabulka je k dispozici v těchto formátech: PostScript, PDF, HTML.

<-předchozí úvod

(c) Pavel Satrapa, 2000