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
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:
$text = "ABCDEF";
$text =~ tr/A-F/xyz/;
print $text;
$text = "ABCDEF";
$text =~ tr/AB/xyz/;
print $text;
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;
}
Pro tr existují dohromady 3 přepínače. V tabulce je jejich přehled.
Přepínač | Význam |
s | Více stejných znaků za sebou, které mají být nahrazeny, jsou nahrazeny pouze 1 znakem. |
c | Funguje podobně jako negace hledaných znaků. Hledá a nahrazuje se jejich doplněk. |
d | Není-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. |
$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'.
$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 '
$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.
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.