Ruby po kapkách (9.) – úvod do regulárních výrazů

11. května 2009

I kdybyste nechtěli vědět vůbec nic o Ruby, znalost regulárních výrazů vám sama o sobě významně zvýší kvalifikaci, zejména pokud jste vývojář nebo systémový administrátor. Tento extrémně silný nástroj pro práci s textovými daty se v posledních deseti letech dostal z okruhu esoterických nauk mezi běžně využívané techniky. Významně k tomu přispěla podpora v populárních skriptovacích jazycích nevyjímaje Ruby.

Co je to regulární výraz? Je to zápis určitého vzoru, který lze porovnávat s textem. Je to vlastně velmi speciální programovací jazyk, na základě kterého se vytvoří podprogram pro prohledávání textu. Regulární výraz je v Ruby reprezentován (jak jinak) objektem třídy Regexp. Nejjednodušším způsobem, jak v vytvořit instanci tohoto objektu, je použití literálu. Regulární výraz se uzavře do dvou obyčejných lomítek. Pro porovnání textu s výrazem pak slouží speciální porovnávací operátor.

irb(main):001:0> /Ruby/.class
=> Regexp
irb(main):002:0> ‚Ruby‘ =~ /Ruby/
=> 0
irb(main):003:0> /Ruby/ =~ ‚Ruby‘
=> 0
irb(main):004:0> ‚Ruby‘ =~ /Duby/
=> nil
irb(main):005:0> ‚Ruby‘ !~ /Duby/
=> true
irb(main):006:0> ‚Duby Ruby‘ =~ /Ruby/
=> 5

V těchto triviálních příkladech vystupuje regulární výraz, který odpovídá řetězci ‚Ruby‘. Vidíme, že lomítkový literál skutečně vytváří Regexp. Porovnávací operátor je definován jak na třídě Regexp tak na třídě String, takže porovnání je možné provádět oběma směry. K dispozici je také negovaný operátor. Výsledkem porovnání může být buď nil v případě, že definovaný vzor nebyl v řetězci nalezen, nebo index znaku, kde začíná první nalezený výskyt textu odpovídajícího vzoru. Na první pohled jinak se chová negované porovnání, ale je třeba si uvědomit, že interpret Ruby v tomto případě provede normální porovnání (v našem případě s výsledkem nil) a výsledek pak zneguje. Tím dostaneme onu hodnotu true.

Základy jazyka regulárních výrazů jsou jednoduché. Většina znaků ve vzoru reprezentuje sama sebe a při porovnání se v textu prostě hledá odpovídající znak. Některé znaky jsou speciální a umožňují definici dodatečných podmínek. Nejčastěji se používá speciálních znaků, které určují povinnost či možné opakování předchozího znaku, seskupení znaků a možnost definovat výskyt třídy znaků namísto jednoho znaku.

irb(main):001:0> ‚Rub‘ =~ /Ruby/
=> nil
irb(main):002:0> ‚Rub‘ =~ /Ruby?/
=> 0
irb(main):003:0> ‚Rubbbby‘ =~ /Rub+y/
=> 0
irb(main):004:0> ‚Rubbbby‘ =~ /Rub*y/
=> 0
irb(main):005:0> ‚Ruy‘ =~ /Rub*y/
=> 0
irb(main):006:0> ‚Rubbbby‘ =~ /Rub{4}y/
=> 0
irb(main):008:0> ‚Rubbbby‘ =~ /Rub{1,5}y/
=> 0
irb(main):009:0> ‚Rubbbby‘ =~ /Rub{3,}y/
=> 0

Modifikátory povinnosti a opakování jsou definovány takto:

  • ? – předcházející znak nebo skupina znaků se v textu nachází volitelně (0..1)
  • + – předcházející znak nebo skupina znaků se v textu nachází jednou nebo vícekrát (1..n)
  • * – předcházející znak nebo skupina znaků se v textu nachází volitelně jednou nebo vícekrát (0..n)
  • {n} – předcházející znak nebo skupina znaků se v textu nachází právě n-krát (n..n)
  • {m, n} – předcházející znak nebo skupina znaků se v textu nachází minimálně m-krát a maximálně n-krát (m..n); n je nepovinné

irb(main):001:0> ‚abcd‘ =~ /[a-z]+/
=> 0
irb(main):002:0> ‚abcd‘ =~ /[a-c]+/
=> 0
irb(main):003:0> ‚abcd‘ =~ /[a-c]{4}/
=> nil
irb(main):004:0> ‚abcd‘ =~ /[0-9]{4}/
=> nil
irb(main):005:0> ‚abcd‘ =~ /[0-9abcd]{4}/
=> 0
irb(main):006:0> ‚abcd‘ =~ /[ˆ0-9]{4}/
=> 0

Hranaté závorky definují třídu všech znaků, které se mohou objevit na nějaké pozici. Znaky lze definovat buď výčtem nebo rozsahem, kde mezi první a poslední znak se píše pomlčka. Výčet a rozsahy lze libovolně kombinovat a skládat za sebe. Pokud bychom chtěli pomlčku použít jako součást výčtu, musí být na prvním místě. Pokud dáme na první místo znak ^, bude třída definována opačně – jako všechny znaky, které nejsou součástí definice. V rámci regulárního výrazu má speciální použití znak tečka. Slouží jako zástupka pro jakýkoliv znak. Regulární výraz, který pasuje na jakýkoliv řetězec je pak kombinací tečky s nejsilnějším modifikátorem .*

Kulaté závorky seskupují vzory do skupin. Svislítko funguje jako logická spojka nebo. Pro větší kontrolu nad porovnáváním se také hodí znaky ^ a $, které odpovídají začátku a konci řetězce. Jinak k pozitivnímu výsledku porovnání stačí, aby vzor odpovídal části řetězce, jak jsme viděli v předchozím příkladu na řádku 2, kde definované množině znaků ve skutečnosti odpovídaly jen první tři znaky. Pokud bychom chtěli kterýkoliv speciální znak použít v regulárním výrazu doslova, museli bychom mu předřadit zpětné lomítko (což tím pádem platí i pro zpětné lomítko samotné).

irb(main):001:0> ‚Ruby‘ =~ /Perl|Python|Ruby/
=> 0
irb(main):002:0> ‚Duby Ruby‘ =~ /Perl|Python|Ruby/
=> 5
irb(main):003:0> ‚Duby Ruby‘ =~ /ˆ(Perl|Python|Ruby)/
=> nil
irb(main):004:0> ‚Duby Ruby‘ =~ /(Perl|Python|Ruby)$/
=> 5

Syntaxe regulárních výrazů umožňuje mnoho dalších funkcí, ale výše uvedené základy postačují pro mnoho praktických aplikací. Kde se tyto aplikace v Ruby skrývají? V první řadě po každém porovnání máme v několika předdefinovaných proměnných tyto řetězce:

  • $& – část řetězce odpovídající vzoru
  • $` – část řetězce před částí odpovídající vzoru
  • $‘ – část řetězce za částí odpovídající vzoru

Tyto kryptické názvy jsou dědictvím jazyka Perl. Můžeme se jim vyhnout, pokud pracujeme s regulárnímy výrazy jako s objekty a voláme jejich metody.

irb(main):001:0> ‚Perl Python Ruby‘ =~ /Py[a-z]+/
=> 5
irb(main):002:0> $&
=> „Python“
irb(main):003:0> $`
=> „Perl „
irb(main):004:0> $‘
=> “ Ruby“
irb(main):005:0> r = Regexp.new(‚Py[a-z]+‘)
=> /Py[a-z]+/
irb(main):006:0> r.source
=> „Py[a-z]+“
irb(main):007:0> m = r.match(‚Perl Python Ruby‘)
=> #<MatchData:0x302afc8>
irb(main):008:0> m[0] => „Python“
irb(main):009:0> m.pre_match
=> „Perl „
irb(main):010:0> m.post_match
=> “ Ruby“

Jak je vidět, instanci Regexp vytvoříme kromě již ukázaného způsobu s literálem také standardně voláním new a jako parametr předáme zdrojový řetězec regulárního výrazu. Metoda match třídy Regexp provede porovnání a vrací výsledek ve formě objektu třídy MatchData. Ten má definovaný indexovací operátor (hranaté závorky). Pod indexem 0 se skrývá řetězec odpovídající vzoru. Pokud bychom použili ve vzoru skupiny, byly by jednotlivé odpovídající části výsledku pod dalšími indexy. Index skupiny odpovídá vždy pořadí levé závorky, která uvozuje skupinu v regulárním výrazu. Při použití porovnání s literálem jsou tyto výsledku dostupné pod proměnnými $1, $2 atd. (Pozor $0 je název aktuálně bežícího procesu.)

Podívejme se nakonec na nějaké zajímavé metody, které používají regulární výraz jako parametr.

irb(main):001:0> ‚Perl Python Ruby‘.sub(/P[a-z]+/, ‚Ruby‘)
=> „Ruby Python Ruby“
irb(main):002:0> ‚Perl Python Ruby‘.gsub(/P[a-z]+/, ‚Ruby‘)
=> „Ruby Ruby Ruby“
irb(main):003:0> ‚123 aaa 678 zzz‘.scan(/[0-9]+/)
=> [„123“, „678“] irb(main):004:0> [‚123‘, ‚aaa‘, ‚678‘, ‚zzz‘].grep(/[0-9]+/)
=> [„123“, „678“]

Pomocí metody sub a gsub můžeme nahrazovat části řetězců. První metoda nahrazuje první nalezený výskyt vzoru, druhá mětoda nahrazuje všechny výskyty. Metoda scan prochází řetězec a generuje pole všech výskytů vzoru. Pokud není vzor nalezen, je pole prázdné. Máme-li kolekci, která je Enumerable, máme k dispozici metodu grep, která filtruje prvky podle toho, zda odpovídají zadanému regulárnímu výrazu.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *