Ruby/Wyrażenia regularne

Wyrażenia regularneEdytuj

Stwórzmy bardziej interesujący program. Tym razem sprawdzimy czy łańcuch pasuje do opisu zakodowanego jako ścisły wzorzec.

Oto pewne znaki i kombinacje znaków które mają specjalne znaczenie w tych wzorcach:

Kombinacja Opis
[] specyfikacja zakresu (np., [a-z] oznacza litery od a do z)
\w litera lub cyfra; to samo co [0-9A-Za-z]
\W ani litera ani cyfra
\s biały znak; to samo co [ \t\n\r\f]
\S nie biały znak
\d znak cyfra; to samo co [0-9]
\D znak nie będący cyfrą
\b backspace (0x08) (tylko jeśli występuje w specyfikacji zakresu)
\b granica słowa (jeśli nie występuje w specyfikacji zakresu)
\B granica nie słowa
* treść stojąca przed tym symbolem może powtórzyć się zero lub więcej razy
+ treść stojąca przed tym symbolem musi powtórzyć się jeden lub więcej razy
{m,n} treść stojąca przed tym symbolem musi powtórzyć się od m do n razy
? treść stojąca przed tym symbolem może wystąpić najwyżej jeden raz; to samo co {0,1}
| albo treść stojąca przed tym symbolem albo następne wyrażenie muszą pasować
() grupowanie
^ początek wiersza
$ koniec wiersza

Wspólny termin określający wzory, które używają tych dziwnych symboli, to wyrażenia regularne. W Rubim tak samo jak w Perlu bierze się je raczej w ukośniki (/) niż w cudzysłowy. Jeżeli nigdy wcześniej nie pracowałeś z wyrażeniami regularnymi, prawdopodobnie nie wyglądają one zbyt regularnie, ale naprawdę warto poświęcić trochę czasu by się z nimi zaznajomić. Wyrażenia regularne są skuteczne i ekspresywne. Oszczędzi ci to bólów głowy (i wielu linii kodu) niezależnie od tego, czy potrzebujesz dopasowywania wzorców, wyszukiwania czy też innego manipulowania łańcuchami.

Dla przykładu, przypuśćmy, że chcemy sprawdzić czy łańcuch pasuje do tego opisu: "Zaczyna się małą literą f, po której zaraz następuje jedna duża litera i opcjonalnie jakieś inne znaki, aż do wystąpienia innych innych małych liter." Jeżeli jesteś doświadczonym programistą C, prawdopodobnie już napisałeś około tuzina linii kodu w głowie, prawda? Przyznaj, prawie nie możesz sobie poradzić. Ale w Rubim potrzebujesz jedynie by łańcuch został sprawdzony pod kątem występowania wyrażenia regularnego /^f[A-Z][^a-z]*$/.

A co z "zawiera liczbę heksadecymalną w ostrych nawiasach"? Żaden problem.

def ma_hex?(s)                      # "zawiera hex w ostrych nawiasach"
  (s =~ /<0(x|X)(\d|[a-f]|[A-F])+>/) != nil
end

puts ma_hex?("Ten nie ma.") #=> false

puts ma_hex?("Może ten? {0x35}") #=> false 
# (zły rodzaj nawiasów)

puts ma_hex?("Albo ten? <0x38z7e>") #=> false
# fałszywa liczba hex

puts ma_hex?("Dobra, ten: <0xfc0004>.") #=> true

Chociaż wyrażenia regularne mogą się wydawać na początku nieco zagadkowe, z pewnością szybko osiągniesz satysfakcję z tak ekonomicznego sposobu wyrażania skomplikowanych pomysłów.

Oto mały program który pomoże ci eksperymentować z wyrażeniami regularnymi. Zapisz go jako regx.rb i uruchom przez wpisanie ruby regx.rb w linii poleceń.

# Wymaga terminala ANSI!

st = "\033[7m"
en = "\033[m"

puts "Aby zakonczyc wprowadz pusty tekst."

while true
  print "tekst> "; STDOUT.flush; tekst = gets.chop
  break if tekst.empty?
  print "wzor> "; STDOUT.flush; wzor = gets.chop
  break if wzor.empty?
  wyr = Regexp.new(wzor)
  puts tekst.gsub(wyr,"#{st}\\&#{en}")
end

Program wymaga dwukrotnego wprowadzenia danych. Raz oczekuje na łańcuch tekstowy, a raz na wyrażenie regularne. Łańcuch sprawdzany jest pod kątem występowania wyrażenia regularnego, następnie wypisywany z podświetleniem wszystkich pasujących fragmentów. Nie analizuj teraz szczegółów, analiza tego kodu wkrótce się pojawi.

tekst> foobar
wzor> ^fo+
foobar
~~~

Znaki tyldy oznaczają podświetlony tekst na wyjściu programu.

Wypróbujmy kilka innych tekstów.

tekst> abc012dbcd555
wzor> \d
abc012dbcd555
   ~~~    ~~~

Jeśli cię to zaskoczyło, sprawdź w tabeli na górze tej strony: \d nie ma żadnego związku ze znakiem d, oznacza natomiast pojedynczą cyfrę.

A co, jeśli jest więcej niż jeden sposób poprawnego dopasowania wzoru?

tekst> foozboozer
wzor> f.*z
foozboozer
~~~~~~~~

foozbooz jest spasowany zamiast samego fooz, gdyż wyrażenie regularne wybiera najdłuższy, możliwy podłańcuch.

Oto wzór do wyizolowania pola zawierającego godzinę z separatorem w postaci dwukropka.

tekst> Wed Feb  7 08:58:04 JST 1996
wzor> [0-9]+:[0-9]+(:[0-9]+)?
Wed Feb  7 08:58:04 JST 1996
           ~~~~~~~~

"=~" jest operatorem dopasowania w odniesieniu do wyrażeń regularnych; zwraca pozycję w łańcuchu, gdzie może być znaleziony pasujący podłańcuch lub nil jeżeli takowy nie występuje.

puts "abcdef" =~ /d/ #=> 3
puts "aaaaaa" =~ /d/ #=> nil