Hlavní navigace

Regulární výrazy (5)

Pavel Satrapa

Na pátou část seriálu jsem si pošetřil snad nejsilnější prvek regulárních výrazů - jejich paměť. Regulární výraz si totiž dokáže zapamatovat řetězec, který vyhověl jeho části, a později jej použít. Největší služby tento mechanismus odvede při nahrazování.

Zapamatuj a vzpomeň si

Prostředky pro zapamatování jsou směšně jednoduché. Část, kterou si má regulární výraz podržet v paměti, prostě ohraničíte konstrukcemi \( a ). Jistě si vzpomínáte, že v Perlu nemá nealfanumerický znak předcházený závorkou nikdy speciální význam, takže tam se ke stejnému účelu používají obyčejné závorky. Takových úseků můžete v regulárním výrazu mít hned několik. Dokonce je lze i vzájemně vnořovat.

Když později chcete použít zapamatovaný řetězec, napište \číslo, kde číslo je pořadové číslo zapamatovaného úseku. Pořadová čísla začínají jedničkou a rozhoduje o nich pořadí levé (otevírací) závorky zapamatovávané sekvence.

Příklad:

Podrobíte-li řádek ze souboru /etc/passwd regulárnímu výrazu ^\([^:]*):[^:­]*:\([^:]*), bude \1 obsahovat přihlašovací jméno a \2 jemu odpovídající identifikátor (U­ID).

O dobrodiní paměti jste ochuzeni u programů egrep a awk. Jejich algoritmus pro srovnávání regulárních výrazů nepodporuje zapamatování (a z principiálních důvodů ani podporovat nemůže). V příští části se k této otázce vrátím podrobněji.

Použití při hledání

Použití zapamatovaných částí při vyhledávání je poměrně vzácné. Řečeno mluvou politiků: společenská potřeba takové služby je celkem malá. Příklady sice existují, nicméně bývají takové školometské. Tak si nějaké zkusíme.

Příklad:

Hezkou a zcela neužitečnou ilustrací zapamatování je hledání palindromů (slov, která se nezmění, když je čtete pozadu). Tak například výrazu

\<\(.\)\(.\).\2\1\>

vyhoví právě a pouze pětiznakový palindrom (např. radar či rotor). První dva znaky slova si zapamatuje, za nimi následuje třetí libovolný znak, čtvrtý musí být stejný jako zapamatovaný druhý a pátý znak se musí shodovat s prvním.

Příklad:

Zkusím něco, co by alespoň vzdáleně připomínalo reálný život. Řekněme, že máte výstupy z jakéhosi algoritmu – na každém řádku sadu čísel oddělených mezerami. Každý řádek zároveň končí správným výsledkem. Pokud algoritmus pracuje správně, poslední dvě čísla na řádku jsou totožná. Hledáme tedy řádky, ve kterých se poslední číslo liší od předposledního. K řešení poslouží grep s negovanou podmínkou (volba -v):

grep -v ' \([^ ]\+\) \+\1$'

Vzor začíná mezerou před předposledním číslem. Za ní následuje neprázdná posloupnost nemezerových znaků ([^ ]\+), která se zapamatuje. Po ní následuje alespoň jedna mezera ( \+) a znovu stejná posloupnost, za kterou už je jen konec řádku.

Použití při nahrazování

Daleko častěji se zapamatované řetězce vyskytují v příkazech pro nahrazování. Díky nim si můžete ze vstupních dat vytáhnout informace, které vás zajímají, a poskládat si je do tvaru, který potřebujete.

Příklad:

Běžný problém všedního dne: potřebujete u skupiny souborů změnit příponu z .htm na .html. Pro podobné účely sice existují různá udělátka, ale je třeba si je doinstalovávat a práce s nimi nebývá úplně snadná. takže se podívejme, jak poslouží standardní nástroje, které najdete v každém Unixu.

Postup je jednoduchý: obstaráte si seznam jmen souborů, každé jméno pak změníte na příkaz mv staré nové a tyto příkazy provedete. Popsaný postup lze realizovat třeba takto:

ls *.htm > seznam
sed 's/\(.*\)/mv \1 \1l/' seznam > akce
chmod a+x akce
./akce
rm seznam akce

Uznávám, že prosté připojení „l“ na konec jména souboru je dosti snadnou modifikací. Složitější věci však znamenají jen úpravu příkazu s. Například změnu přípony z .doc na .txt by zajistilo s/\(.*)\.doc/mv \1­.doc \1.txt/ – zapamatuje si jen vlastní jméno souboru a přípony jsou explicitně vyjmenovány.

Příklad:

A teď něco drsnějšího. Chtěl bych ze souboru /etc/passwd vyrobit seznam domácích stránek uživatelů. Takže potřebuji řádky transformovat z původní podoby

uživatel:heslo:UID:GID:vlastní jméno:...

na

<A HREF="/~uživatel">vlastní jméno</A>

Kýženým substitučním příkazem, který to zařídí, je

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

Jak vidíte, prostřednictvím závorek lze předepsat počet opakování i rozsáhlejšímu úseku regulárního výrazu - zde se třikrát opakuje skupina [^:]*:. V takovýchto situacích závorky většinou neslouží k zapamatování (i když si pochopitelně něco zapamatují), ale čistě k vymezení opakované části. Ta má - bez ohledu na skutečný počet opakování - jen jediné pořadové číslo. Proto se závěrečné zapamatované jméno uloží jako \3.

Problémem regulárních výrazů je, že jsou velmi kompaktní. Vyznat se ve výše citovaném substitučním příkazu zabere chvíli času (zpravidla více, než jej vymyslet). V Perlu si můžete vytvoření seznamu rozložit: nejprve řádek rozkrájíte v místě výskytu dvojteček a z výsledného pole pak použijete první a pátý prvek (indexy 0 a 4):

while ( $radek = <> ) {
    @uzivatel = split(/:/, $radek);
    print "<A HREF=\"/~$uzivatel[0]\">";
    print "$uzivatel[4]</A>\n";
}

Program uložte třeba do souboru htmlseznam a spusťte

perl htmlseznam /etc/passwd

Shrnutí

výraz význam
\(výraz) zapamatuje si text vyhovující výrazu
\1 první zapamatovaný řetězec
Našli jste v článku chybu?

26. 3. 2015 7:29

Petr (neregistrovaný)

Tohle fungovat nebude, protoze to nenajde parovy tag, ale prvni a posledni tag v dokumentu. Tedy treba zacatek prvniho odstavce a konec posledniho odstavce. Spravne to ma byt:

<(.*)>.*?<\1>

26. 3. 2015 7:22

Petr (neregistrovaný)

Tak ja mam priklad z praxe, kdy potrebuji najit v textu retezce jako "1BA az 1BD" (nazvy rozvadecu) a nahradit je za "1BA, 1BB, 1BC, 1BD" pricemz cislo se muze menit. Vyskyty "1BA az 2BD" musi zustat nepovsimnuty, protoze nemaji stejne cislo.

DigiZone.cz: Co chtějí operátoři při přechodu na DVB-T2?

Co chtějí operátoři při přechodu na DVB-T2?

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Podnikatel.cz: K EET. Štamgast už peníze na stole nenechá

K EET. Štamgast už peníze na stole nenechá

DigiZone.cz: Placené VoD a obsah zdarma

Placené VoD a obsah zdarma

Lupa.cz: Kdo pochopí vtip, může jít do ČT vyvíjet weby

Kdo pochopí vtip, může jít do ČT vyvíjet weby

DigiZone.cz: Recenze Westworld: zavraždit a...

Recenze Westworld: zavraždit a...

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

120na80.cz: Co všechno ovlivňuje ženskou plodnost?

Co všechno ovlivňuje ženskou plodnost?

DigiZone.cz: Rádio Šlágr má licenci pro digi vysílání

Rádio Šlágr má licenci pro digi vysílání

120na80.cz: Na ucho teplý, nebo studený obklad?

Na ucho teplý, nebo studený obklad?

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

Vitalia.cz: I církev dnes vyrábí potraviny

I církev dnes vyrábí potraviny

Měšec.cz: U levneELEKTRO.cz už reklamaci nevyřídíte

U levneELEKTRO.cz už reklamaci nevyřídíte

Lupa.cz: Proč firmy málo chrání data? Chovají se logicky

Proč firmy málo chrání data? Chovají se logicky

Vitalia.cz: Pamlsková vyhláška bude platit jen na základkách

Pamlsková vyhláška bude platit jen na základkách

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Podnikatel.cz: Chtějte údaje k dani z nemovitostí do mailu

Chtějte údaje k dani z nemovitostí do mailu

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Vitalia.cz: Spor o mortadelu: podle Lidlu falšovaná nebyla

Spor o mortadelu: podle Lidlu falšovaná nebyla