Perl (21) - Regulární výrazy - nahrazování

Do této chvíle jsme regulární výrazy používali pouze k vyhledávání. To je ale jen polovina toho, co skutečně dokáží.

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

Jak již o regulárních výrazech víme, slouží nejen pro vyhledávání vzorů, ale také pro nahrazování jejich částí. Představme si, že máme nějaký textový řetězec a potřebujeme z něj vymazat všechna čísla. Už známe několik způsobů, jak tento problém řešit. Celý řetězec by se dal rozdělit na znaky a každý získaný znak porovnat s možnými číslicemi. Další možností je rozdělit řetězec funkcí split s číselným oddělovačem. Nicméně to je všechno zbytečně pracné.

Regulární výrazy mají pro takové případy další možnou syntaxi. Chceme-li nahrazovat, použití regulárního výrazu se s prostým vyhledáváním v několika detailech liší. V zápisu se místo úvodního m používá s a jeho uvedení je povinné. Následuje vzor a řetězec, kterým bude úsek vyhovující vzoru nahrazen. Protože zde je o položku více, je třeba oddělovač navíc.

  s/vzor/náhrada/

Ač je zřejmé, že tomu nemůže být jinak, podotkněme, že náhrada je vždy prostým řetězcem a nikoliv regulárním výrazem.

V diskuzních fórech se dnes a denně setkáváme s reakcemi typu

  s/nabýdku/nabídku/

To je právě ukázka nahrazení, jen bez využití regulárních výrazů. Autor takového příspěvku dává na vědomí, že pisatel udělal pravopisnou chybu a měla by být opravena. Tímto způsobem bychom opravu řešili v Perlu:

  $reakce = "Takovou nabýdku nebudu komentovat...";
  $reakce =~ s/nabýdku/nabídku/;
  print $reakce; #tiskne opravený text

I kdyby bylo v řetězci více stejných chyb, opraví se jen jedna z nich. Vždy se nahrazuje pouze 1. výskyt. Po jeho nalezení totiž vyhledávání vzoru skočí úspěchem. Náš problém, kdy jsme chtěli z řetězce odstranit všechna čísla, by se dal řešit uvedením přepínače g. Ten se, podobně jako u vyhledávání, aplikuje na všechny výskyty vyhovující vzoru v řetězci. Výskyty tedy budeme nahrazovat prázdným řetězcem.

  $retezec = "P21E251215563R413215305711L587";
  $retezec =~ s/\d+//g;
  print $retezec; #tiskne PERL

Jednoduché nahrazování - konverze znaků

Tato problematika do regulárních výrazů nepatří, ale má s nimi některé společné rysy. Z tohoto důvodu je zařazena právě zde.

Činnost této konstrukce není nic jiného, než nahrazování konkrétního znaku (nikoliv regulárního výrazu) za jiný konkrétní znak.

Syntaxe je podobná jako při nahrazování u regulárních výrazů:

  $text =~ tr/abc/ABC/;
  $text !~ tr/abc/ABC/;
  $text =~ y/abc/ABC/;

y a tr jsou 2 synonymní zápisy.

Po vykonání libovolného ze zmíněných tří příkazů se obsah proměnné $text změní. Všechna a se nahradí za A, všechna b za B a všechna c za C. Obecně se tedy ntý znak na levé straně nahrazuje ntým znakem z pravé strany.

Rozdíl mezi použitím !~ nebo =~ je v návratové hodnotě. Výraz s !~ vrací true, pokud nedošlo k žádnému nahrazení. Pokud ano, vrací false. !~ se u tr příliš nepoužívá. Naproti tomu =~ vrací vždy počet nahrazení. Pokud je tento počet nenulový, návratová hodnota je tedy true.

I u tr fungují rozsahy. Není tak problémem v proměnné $text nahradit všechna malá písmena za velká, bez toho abychom je všechny vypisovali (pomiňme použití funkce uc):

  $text =~ tr/a-z/A-Z/;

Teď si objasníme sporné případy použití tr:

Jako ukázku si nemohu odpustit Caesarovu šifru (v Unixu příkazy caesar, rot13). Jde o to nahradit každý znak (písmeno) znakem, který je v abecedě (ASCII tabulce, nebo zkrátka v nějaké soustavě znaků) o určitý počet znaků dále nebo blíže. Poprvé tuto šifru použili ke komunikaci Caesar a Cicero v době galské války. Písmena tehdy posouvali vždy o 3 znaky v abecedě dopředu.

Vytvoříme Caesarovu šifru s posunutím +4. Na vstupu (případně v souboru jako argumentu) bude program získávat otevřený text a následně vypisovat text šifrovaný.

K tomu potřebujeme cyklicky získávat řádky textu a zpracovávat je pomocí konstrukce tr. Posunutí +4 znamená, že a nahrazujeme za e, b za f, ..., v za z, w za a, x za b, y za c a z za d.

Takhle bude vypadat šifrovaná abeceda:

  Otevřená abeceda  abcdefghijklmnopqrstuvwxyz
  Šifrovaná abeceda (+4)  efghijklmnopqrstuvwxyzabcd

Otevřenou abecedu nahradíme šifrovanou:

  tr/abcdefghijklmnopqrstuvwxyz/efghijklmnopqrstuvwxyzabcd/;

Můžeme také využít rozsahy:

  tr/a-z/e-za-d/;

Vytvoříme cyklus, každou iteraci se načte řádek textu, provede se zašifrování a vytiskne se šifrovaný text:

  while ($radek = <>){
      $radek =~ tr/a-z/e-za-d/;
      print $radek;
  }

Také u tr lze pomocí výchozí proměnné vynechat operátor =~.

  while (<>){
      tr/a-z/e-za-d/;
      print;
  }

A abychom dovedli tuto ukázku téměř k dokonalosti, budeme nahrazovat i velká písmena:

  while (<>){
      tr/a-zA-Z/e-za-dE-ZA-D/;
      print;
  }

Přepínače

Pro tr existují dohromady 3 přepínače. V tabulce je jejich přehled.

PřepínačVýznam
sVíce stejných znaků za sebou, které mají být nahrazeny, jsou nahrazeny pouze 1 znakem.
cFunguje podobně jako negace hledaných znaků. Hledá a nahrazuje se jejich doplněk.
dNení-li dostatek znaků na pravé straně tr, nenahrazují se přebytečné znaky levé strany posledním znakem pravé strany, ale prázdným řetězcem.

Přepínač s

  $text = "xxxxyxx";
  $text =~ tr/x/X/s;
  print $text;

Podřetězce 'xxxx' a 'xx', jsou každý nahrazeny pouze jedním znakem X. Je tak vytisknuto 'XyX'.

Přepínač c

  $text = "xxxxyxXXxx";
  $text =~ tr/A-Z/ /c;
  print $text;

Bez uvedení přepínače by byla nahrazena všechna velká písmena mezerou. Protože zde ale je c, nahrazuje se vše mimo velkých písmen a tiskne se '      XX  '. V případné kombinaci s přepínačem s by výsledkem byl řetězec ' XX '

Přepínač d

  $text = "ABCDEFGH";
  $text =~ tr/ABC/x/d;
  print $text;

Vytisknuto je jen 'xDEFGH'. Hledáme znaky ABC, z nich pro B a C nemáme náhradu, a protože je uveden přepínač d, budeme je nahrazovat prázdným řetězcem. V případě, že by zde přepínač d nebyl, B a C by se nahrazovaly stejně jako A znakem x.

Počet výskytů znaku v řetězci

Protože výraz s operátorem =~ vrací počet nahrazení, vrací zároveň počet původních i nových znaků. Pokud tedy nahrazujeme znak stejným znakem, žádné změny v řetězci nenastanou. Pouze získáme počet výskytů. Řádek kódu přiřazuje do proměnné $vyskytu počet výskytů znaku x v obsahu proměnné $text:

  $vyskytu = $text =~ tr/x/x/;

Příště budeme pokračovat přepínači a speciálními konstrukcemi v perlových regulárních výrazech.

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