LINUXSOFT.cz Přeskoč levou lištu
Uživatel: Heslo:  
   CZUKPL

> Perl (20) - Regulární výrazy - magické závorky

Dnešní díl popisuje použití kulatých závorek pro změnu priority, seskupování a pamatování.

5.12.2005 06:00 | Jiří Václavík | Články autora | přečteno 29128×

V regulárních výrazech mají závorky hned několik důležitých a jinak nenahraditelných významů. Mezi jejich hlavní funkce patří seskupení, priorita a paměť. Rozeberme si postupně tyto tři vlastnosti.

Priorita

Stejně jako pro operátory existuje tabulka priorit i pro metaznaky. Určuje, ke kterým znakům se váže určitý metaznak. Čím výše v tabulce, tím je metaznak prioritnější.

MetaznakyVýznam
( ), (?: )závorky
*, ?, +, {}, *?, ??, +?, {}?kvantifikátory
aa, \x, ^, $ apod.posloupnost znaků, kotvy
|alternativy

Priorita se dá měnit kulatými závorkami.

Seskupování

Kvantifikátor jsme zatím používali pouze pro znak nebo třídu znaků, která ho předcházela. Pomocí závorek můžeme docílit toho, aby se vztahoval na nějakou delší část regulárního výrazu. Srovnejme si tyto 2 vzory:

  /(xy){3}/;
  /xy{3}/;

V 1. případě jsou znaky xy seskupeny. To znamená, že se kvantifikátor vztahuje na celý řetězec xy v závorkách. Lze to vysvětlit také jako změnu priority - kvantifikátor má menší prioritu než závorky a proto je aplikován na celý uzávorkovaný výraz. Takovému regulárnímu výrazu vyhoví řetězec, který obsahuje xyxyxy. Další řádek je stejný až na to, že chybí závorky a znaky xy seskupeny nejsou. Kvantifikátor je prioritnější než posloupnost znaků a vztahuje se pouze na znak y. Vzoru tedy vyhoví xyyy.

Pamatování obsahu závorek

Vše, co uzávorkujete kulatými závorkami si Perl, pokud mu to nezakážete, pamatuje. To je vlastnost, se kterou lze dělat opravdu nevídané věci.

Výraz $text =~ /(...)/ vrací úsek textu, který vyhověl regulárnímu výrazu. Tímto způsobem můžeme do pole velice pohodlně získat třeba seznam slov nebo čísel. Právě seřazený výtah čísel ze zadaného textu tiskne následující ukázka.

  $, = ", ";
  $text = "333 stříbrných stříkaček stříkalo přes 33 stříbných střech";
  @cisla = ($text =~ /(\d+)/g);
  print sort @cisla; #tiskne 33, 333

Obsah závorky se ukládá okamžitě a takto uložený řetězec se dá použít ještě ve vzoru. To, co je v 1. závorce je přístupné v \1, další závorka \2 a tak dále až do \99 (dále ne, protože by se takový zápis překrýval s osmičkovým zápisem znaku. \100 je zavináč, \101 je znak A apod.). Tento mechanizmus nám otevírá spoustu dalších možností.

Pokusme se zapsat vzor, který vyhoví řetězci o 5 stejných číslicích. Jinými slovy vzor, kterému, pokud začíná na 1, vyhoví pouze 11111, pokud začíná na 2, vyhoví pouze 22222, atd. Pokud vzor nezačíná číslicí, nevyhoví nikdy.

Jako 1. znak hledáme číslici. Zápis množiny číslic musí být v závorkách, abychom její hodnou neztratili. Dále použijeme zapamatovanou hodnotu jako množinu znaků a otestujeme ji 4x. Nakonec označíme začátek a konec řetězce.

  print "MATCHED" if "55555" =~ /^(\d)\1{4}$/; #vyhovuje
  print "MATCHED" if "555555" =~ /^(\d)\1{4}$/;#nevyhovuje
  print "MATCHED" if "11112" =~ /^(\d)\1{4}$/; #nevyhovuje
  print "MATCHED" if "xxxxx" =~ /^(\d)\1{4}$/; #nevyhovuje

Tato vlastnost se hodí zejména při nahrazování, které bude rozebráno v příštím díle.

Podobně lze pomocí závorek získávat hodnoty i mimo samotný vzor. Obsahy jsou přístupné ve speciálních proměnných $1, $2$99. Nyní z řetězce opět vyseparujeme všechna čísla. Použijeme cyklus, každou iteraci bude 1 číslo zapamatováno a v těle cyklu vytištěno.

  while ("333 stříbrných stříkaček stříkalo přes 33 stříbných střech" =~ /(\d+)/g){
      print "Číslo: $1\n";
  }

Pořadí (u zápisu \n i $n) se uděluje na základě pozice otevírací závorky. Tento systém řeší případné víceúrovňové zanoření. Nějaké podřetězce tak mohou být uloženy i vícekrát. Nic tedy nebrání například zápisu ((\d)) - ta stejná číslice bude uložena do více proměnných. Zanořování si ilustrujeme na následujícím úseku kódu. Ten vypíše první a poslední číslici nejméně trojmístného zadaného čísla a také toto celé číslo.

  <STDIN> =~ /^((\d)\d+(\d))$/;
  print "1. číslice: $2\n";
  print "poslední číslice: $3\n";
  print "celé číslo: $1\n";

Poznámka - Pokud řetězec nebude vyhovovat vzoru, proměnné zůstanou prázdné - obsah závorek se ukládá, jen pokud byl test vyhodnocen pravdivě. Problémy pak mohou nastat, pokud v proměnných $1, $2 atd. zbyly nějaké hodnoty z předchozího testu. Jde o velice nenápadnou chybu a může trvat dlouho, než je odhalena.

Poznámka - Jaký je tedy rozdíl mezi \n i $n? \n je do paměti ukládáno okamžitě s uzavírací závorkou, takže je možné tímto způsobem zapamatovanou hodnotu použít ještě ve vzoru. Oproti tomu proměnné $n se načtou vždy až po úspěšném srovnání řetězce se vzorem.

Ukládání do \n, resp. $n lze zabránit speciální syntaxí pro zápis závorek. Místo levé (otevírací) závorky použijte posloupnost znaků (?:. V takovém případě se nic ukládat nebude a proměnná $n se bude chovat jako ostatní nedefinované proměnné, což dokazuje kód:

  "123456789" =~ /^(?:\d*)$/;
  print "Hodnota uložena\n" if defined $1;

Vždy, když není pamatování potřeba, je lepší použít právě verzi bez zapamatování. Zvyšuje se tím rychlost vyhodnocení regulárního výrazu.

Hezky lze také využít pamatování v souvislosti s kontextem. Tato vlastnost umožňuje například snadno vyseparovat z rodného čísla den, měsíc a rok narození. Stačí jen uzavřít příslušné části regulárního výrazu do závorek a regulární výraz přiřadit do nějakého seznamu.

  $rc = "3012013522";
  ($rok, $mesic, $den) = $rc =~ /^(\d\d)(\d\d)(\d\d)\d{4}$/;
  print "Narozen $den.$mesic.19$rok.";

Poznámka - Příklad je samozřejmě zjednodušený - nebere v úvahu ženská rodná čísla a narozené jindy než mezi roky 1900 a 1999.

Nejdříve se vyhodnotil pravý operand přiřazení. Tak jsme získali seznam, který jsme přiřadili do už pojmenovaného seznamu. Problém opět nastává, pokud srovnávaný řetězec nevyhoví regulárnímu výrazu. Lze to však řešit například umístěním podmínky za příkaz.

  print "Narozen $den.$mesic.19$rok." if ($rok, $mesic, $den) = $rc =~ /^(\d\d)(\d\d)(\d\d)\d{4}$/;

Speciální proměnné

V proměnné $& je část testovaného řetězce, která se shoduje se vzorem. Tímto způsobem si můžeme mimo jiné hezky ilustrovat hladovost kvantifikátorů.

  "číslo 101 je prvočíslo" =~ /\d+.{2,10}/;
  print $&;
  
  "číslo 101 je prvočíslo" =~ /\d+.{2,10}?/;
  print $&;

1. zápis je hladový. Spolkne 10 znaků za číslem 101: "101 je prvočí". Pokud za kvantifikátor přidáme otazník, stává se sytým a pohltí pouze nejmenší možný počet znaků, které vyhoví: "101 j".

Další proměnné $` a $' tisknou tu část testovaného řetězce, kterou nevytiskla proměnná $&. $` tiskne část před ("číslo ") a $' část za ("e prvočíslo").

  "číslo 101 je prvočíslo" =~ /\d+.{2,10}?/;
  print $`;
  print $';

V proměnné $+ je uložen poslední závorkami zapamatovaný řetězec.

Poznámka - Je dobré mít na paměti, že proměnné $&, $`, $' a $+ značně zpomalují program.

Speciální proměnná @- si pamatuje v prvku s indexem 0 první pozici v testovaném řetězci, která vyhovuje vzoru. V dalších prvcích pak pozice začátků zapamatovaných podřetězců. Proměnná @+ dělá to samé, ale pamatuje si místo začátků konce.

Funkce split a regulární výrazy

Oddělovač, který je parametrem této funkce, lze uvést i jako regulární výraz. To je v mnoha případech opravdu užitečné. Mějme nějaký text, ze kterého načteme do pole seznam slov.

  $, = "\n";
  @pole = split (/\W+/, "Text, ve kterém je interpunkce!");
  print @pole;

Vše mezi slovy (tedy oddělovače - mezery a interpunkce - lépe řečeno vše, co vyhovuje vzoru) je navždy ztraceno. Být tomu tak vždy nemusí. Proč, to si budeme demonstrovat na jiném příkladu. Budeme mít nějaký řetězec, ve kterém jsou promíchány čísla a právě čísla zvolíme jako oddělovač.

  $, = " "; $\ = "\n";
  print split (/\d+/, "abc01def2ghijk34567l89mnop");
  print split (/(\d+)/, "abc01def2ghijk34567l89mnop");
  print split (/(?:\d+)/, "abc01def2ghijk34567l89mnop");

V 1. případě se vytiskne seznam získaných podřetězců tak, jak jsme to dosud znali. Zajímavé to začíná být až na dalším řádku. Vzor je uveden v závorkách, které zde mají ten efekt, že se do výsledného seznamu tisknou i oddělovače! Skutečným oddělovačem je tedy (v tomto konkrétním případě) hranice mezi podřetězcem vyhovujícím vzoru a jiným znakem. Nic z původního řetězce tak není ztraceno. Posledním případem je speciální druh uzávorkování, které této vlastnosti zamezuje - oddělovače se chovají jako v 1. případě.

  $ perl split.pl
  abc def ghijk l mnop
  abc 01 def 2 ghijk 34567 l 89 mnop
  abc def ghijk l mnop
  $

Je též možné ozávorkovat pouze část vzoru. V tom případě bude do výsledného seznamu přiřazena příslušná část oddělovače.

  print split (/(\d+)45/, "***12345***12345***123***12345678***");

Podtržené podřetězce ukazují oddělovače. Ve výsledném seznamu ale bude jejich část (znaky 45) odmazána, protože jsou mimo závorky (částí oddělovače ale samozřejmě zůstávají).

Dále můžete také vyzkoušet závorky různě vnořovat, zdvojovat apod. V takových případech budou ve výsledném seznamu všechny uzávorkované řetězce. A to i přesto, že některé části pak budou ve výsledném seznamu vícekrát.

Verze pro tisk

pridej.cz

 

DISKUZE

Nejsou žádné diskuzní příspěvky u dané položky.



Příspívat do diskuze mohou pouze registrovaní uživatelé.
> Vyhledávání software
> Vyhledávání článků

24.4.2014 7:06 /MaReK Olšavský
Staré hry (opravdu staré z 80. let 20. století) jsou stále velmi oblíbené a hrajeme je především v emulátorech. Pokud máte rádi takto staré hry a chcete tip na nejbližší dny, určitě i prodloužené víkendy díky svátkům, stojí za povšimnutí distribuce Puppy Arcade 11, která na USB klíčence nabídne emulátory nejběžnějších platforem z éry domácích počítačů, i s dostatečnou zásobou her.
Přidat komentář

24.4.2014 7:06 /MaReK Olšavský
V srpnu 2013 otevřela IBM svou architekturu Power, v rámci OpenPower Foundation, a nyní představila řadu Power serverů, v něž vkládá naděje, pro udržení architektury Power při životě. Servery si pochopitelně rozumí s GNU/Linuxem. BTW: Postrádám jedině verzi v tower-case, jež by vyhovovala menším firmám, které nechtějí (nebo nemají kam postavit) rackovou skříň.
Přidat komentář

24.4.2014 7:06 /MaReK Olšavský
CERN se nemalou měrou podílí na vývoji Sceintific Linuxu (fork RHELu, podobně jako CentOS), přičemž Red Hat je pro CERN důležitou distribucí, na níž běží téměř 600 důležitých serverů.
Přidat komentář

24.4.2014 7:06 /MaReK Olšavský
Aktuální Weekly Feature Digest 26, jenž shrnuje novinky PC-BSD, fakticky ohlašuje práci na novém desktopu (správci oken) Lumina. Je desktopů a správců oken snad málo?
Přidat komentář

23.4.2014 7:29 /MaReK Olšavský
Čínská firma Lemaker představila konkurenci Raspberry Pi, SBC Banana Pi s 2 jádrovými SoC Allwinner A20 (Cortex-A7) o frekvenci 1 GHz, a (konečně) jedním SATA konektorem. GPIO header by měl být kompatibilní s R-Pi i možnosti připojení jsou stejné.
Přidat komentář

23.4.2014 7:29 /MaReK Olšavský
Bezpečnostní chyba „Heartbleed“ v OpenSSL se dostala i do hlavních zpráv světových médií a pravděpodobnost objevení další je nenulová. Naprostá dominance (OpenSSL má téměř monopolní postavení) pak znamená ohrožení opravdu velkého počtu uživatelů. Stávající OpenSSL je vyvíjené dlouhou dobu a Theo de Raadt se rozhodl pro vytvoření forku LibreSSL, který přinese i značné vyčištění API.
Přidat komentář

22.4.2014 6:47 /MaReK Olšavský
Svobodný software a softwarově definované sítě/datacentra se doplňují. O možných změnách stávajících rolí v datacentrech, píše Dan Kusnetzky.
Přidat komentář

22.4.2014 6:47 /MaReK Olšavský
Zatím není úplně jasné, zda je reálné, aby Chromebooky pronikly do enterprise sféry, přestože se Google a Citrix snaží o zpřístupnění kritických aplikací z platformy Windows.
Přidat komentář

   Více ...   Přidat zprávičku

> Poslední diskuze

7.4.2014 6:31 / MaReK Olšavský
Re: No, nevím

4.4.2014 10:22 / Petr Ježek
No, nevím

4.4.2014 10:18 / Petr Ježek
ATI

24.3.2014 13:54 / Michal Linha
Re: Delimiter

24.3.2014 13:46 / Michal Linha
Re: Delimiter

Více ...

ISSN 1801-3805 | Provozovatel: Pavel Kysilka, IČ: 72868490 (2003-2014) | mail at linuxsoft dot cz | Design: www.megadesign.cz | Textová verze | zákony online