Perl (22) - Regulární výrazy - přepínače

Jak použít v regulárních výrazech přepínače?

21.12.2005 06:00 | Jiří Václavík | přečteno 23397×

Přepínače (volby) mění chování regulárního výrazu. Uvádějí se na konec regulárního výrazu (tedy za poslední lomítko, je-li uvozovacím znakem). Jejich počet není omezen. Lze je definovat globálně nebo, jak poznáme v příštím díle, lokálně.

Kompletní seznam přepínačů pro hledání a nahrazování je v tabulce:

PřepínačVýznam
evyhodnocuje náhradu jako výraz (jen u nahrazování)
gpamatuje si pozici, na které skončilo poslední vyhledávání
inezáleží na velikosti písmen
mmetaznaky ^ resp. $ jsou na začátku resp. konci všech řádků
opřekládá vzor jen jednou
smnožina značící se tečkou zahrnuje i znak nového řádku
xspeciální syntaxe regulárních výrazů s komentáři

S některými jsme se již setkali, s některými zatím ne. Nyní jejich použití komplexně shrneme.

Velikost písma

  print "MATCHED" if "Perl" =~ /perl/i#vyhovuje

Regulární výraz vrátí true. V řetězci se sice "perl" nikde nevyskytuje, ale protože s přepínačem i se nerozlišuje velikost písmen, vyhoví i "Perl".

Vícenásobné prohledávání

Uvedením přepínače g si Perl zapamatuje, na které pozici byl nalezen výskyt a příště pokračuje od ní. To se dá využít v testu cyklu. Používá se k vyhledávání nebo nahrazování všech výskytů (implicitně je hledán jen 1. výskyt).

  $retezec = "www.linuxsoft.cz";
  while ($retezec =~ /linux/g){
      $i++;
  }
  print "Počet výskytů slova linux v řetězci je $i\n";

Existuje i speciální varianta přepínače g a to gc. Jejím použitím zabráníte resetu hodnoty v případě nezdaru při porovnávání.

V seznamovém kontextu lze přepínač g užít, chceme-li získat seznam všech zapamatovaných řetězců nebo řetězců, které vyhověli vzoru.

  @slova = ($retezec =~ /\w+/g);

Komentáře v regulárních výrazech

Přepínač x aktivuje speciální zápis regulárních výrazů. Čitelnost výrazu přitom rapidně roste. Jsou totiž ignorovány mezery a znaky nového řádku v regulárních výrazech. To mimo jiné umožňuje další příjemnou věc, kterou je možnost využití komentářů. Pozor si dejte jen na lomítko, popř. jiný zvolený uvozovací znak v komentáři.

Připomeňme si vzor, kterému vyhoví dvanáctihodinový čas:

  /^((0\d)|(1[0-2]))[:\.][0-5]\d[:\.][0-5]\d[ ]?[pa]m$/i;

Na 1. pohled asi těžko poznáte, co by měl takový výraz vyjadřovat. S přepínačem x to bude za pár sekund jasné:

  $cas = "12:22:11 am";
  print "MATCHED\n" if $cas =~ /
      #regulární výraz pro formát dvanáctihodinového času
      ^
      ((0\d)|(1[0-2]))   #HODINA - číslo mezi 00 a 12
      [:\.]              #oddělovač hodin a minut
      [0-5]\d            #MINUTA - číslo mezi 00 a 59
      [:\.]              #oddělovač minut a sekund
      [0-5]\d            #SEKUNDA - číslo mezi 00 a 59
      [ ]?               #nepovinná mezera
      [pa]m              #určení doby - dopoledne nebo odpoledne; přepínač i zajišťuje, že nezáleží na velikosti písmen
      $
      /xi;

Toto, jak se dozvíte příštím díle, není jediný způsob, jak vkládat do regulárních výrazů komentáře.

Jednorázová kompilace regulárního výrazu

Zejména pro urychlení programu se používá přepínač o. Regulární výraz v nějakém cyklu s přepínačem o je přeložen vždy pouze jednou a tento překlad je pak použit v každé iteraci. Tedy bez ohledu na hodnoty proměnných, které, jak víme, lze do vzorů také zakomponovat. Proměnné, uvedené v regulárním výrazu, se mohou měnit, a pak se tedy mění i samotný regulární výraz. Přepínač o tomu z výše uvedeného důvodu zamezuje. Každou iteraci je použit regulární výraz, který vznikl kompilací v první iteraci.

Ukládání regulárních výrazů

S přepínačem o souvisí jiná věc. Existuje možnost předkompilace - použití konstrukce qr//. Pokud takový regulární výraz přiřadíte do proměnné, lze ji používat místo onoho regulárního výrazu.

  $reg = qr/\d\d\d/;
  print "MATCHED" if "12" =~ $reg;   #nevyhovuje
  print "MATCHED" if "123" =~ $reg#vyhovuje
  print "MATCHED" if "77a" =~ $reg;  #nevyhovuje
  print "MATCHED" if "7744" =~ $reg; #vyhovuje

Je též možné takový regulární výraz v proměnné zařadit do jiného regulárního výrazu.

  print "MATCHED" if "7744" =~ /^$reg$/;   #nevyhovuje
  print "MATCHED" if "7744" =~ /^\d$reg$/; #vyhovuje

Zkusíte-li proměnnou, ve které je regulární výraz uložen, vytisknout, bude vypadat výstup v našem případě takto: (?-xism:\d\d\d).

Náhrada jako výraz

S touto vlastností můžete plodit divy. U nahrazování jsme psali náhradu jako text. Uvedení přepínače e umožní napsat náhradu jako výraz (má to mnoho společného s eval). To by nebylo nic objevného, kdyby v ní nešlo používat zapamatované proměnné. Právě v tom tkví kouzlo.

Uvedeme si 2 příklady. Přepínač e se dobře vysvětluje pomocí funkce reverse. Převrátíme pořadí písmen ve všech slovech řetězce:

  $retezec = "prisel jsem - videl jsem - zvitezil jsem";
  $retezec =~ s/(\w+)/reverse $1/ge;
  print $retezec; lesirp mesj - lediv mesj - lizetivz mesj

V proměnné $1 máme uložena postupně všechna (je použit také přepínač g) slova a na každé je aplikována funkce reverse.

Pojďme dál a zkusme něco složitějšího. V řetězci, který budeme zpracovávat, se vyskytují ceny v amerických dolarech. Upravíme tento řetězec regulárním výrazem tak, abychom dolary nahradili českými korunami a to se vším všudy. Musíme tedy přepočítat částku v dolarech na částku v korunách a zaměnit symbol měny. Navíc je nutné ošetřit případné desetinné ceny.

  $usd2czk = 24.133; #kurz k dolaru z 19.12.2005
  $retezec = "Cena: 20.5\$";
  $retezec =~ s/((\d+(.\d+)?)(\$|USD))/$2*$usd2czk.CZK/ge;
  print $retezec;

Předpokládali jsme, že symbol měny se píše vždy za hodnotu a mezi hodnotou a symbolem není mezera. Tisknut je řetězec "Cena: 494.7265CZK". Je zde ponecháno zbytečně mnoho desetinných míst. Pomocí funkce sprintf je lze oříznout tak, aby byly vytištěny vždy právě dvě, případně doplněné nulami.

  $retezec =~ s/((\d+(.\d+)?)(\$|USD))/sprintf("%.2f", $2*$usd2czk).CZK/ge;

Pokud si zkusíte nahradit vstupující text za nějaký složitější, kde se vyskytuje cena v dolarech vícekrát, jsou nahrazeny všechny. Zajímavé by také mohlo být získávání aktuálního kurzu za běhu.

Přepínač e má ještě jednu zajímavou vlastnost. Lze ho uvést pro 1 regulární výraz vícekrát. Ilustrujme si to na této ukázce.

  $xxx = "XXX";
  $_ = "***\$xxx***";
  s/(\$\w+)/$1/ee;
  print;

Počáteční stav s 2 přepínači e:

  s/(\$\w+)/$1/ee;

V 1. kroku je jedno e spotřebováno na nahrazení proměnné $1 svým obsahem, tedy řetězcem '$xxx'.

  s/(\$\w+)/$xxx/e;

Zbývá nám ještě poslední e, které vyhodnotí výraz $xxx - tedy nahradí proměnnou $xxx jejím obsahem.

  s/(\$\w+)/XXX/;

Příště budeme v regulárních výrazech pokračovat a podíváme se na zoubek rozšířeným vzorům.

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