Wyrażenia regularne

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

Tutaj są pewne znaki i kombinacje znaków które mają specjalne znaczenie w tych wzorach, dołączając:

[] zakres specyfikacji (np., [a-z] oznacza litery z zakresu od a do z)
\w znaki wyrazu; to samo co [0-9A-Za-z_]
\W znaki nie-słowne
\s białe znaki; to samo co [ \t\n\r\f]
\S nie-białe znaki
\d cyfry; to samo co [0-9]
\D nie-cyfry
\b backspace (0x08) (tylko jeżeli jest w zakresie specyfikacji)
\b słowo granicy (jeżeli nie jest w zakresie specyfikacji)
\B nie-wyraz granicy
* zero lub więcej powtórzeń poprzedniego
+ jedno lub więcej powtórzeń poprzedniego
{m,n} przynajmniej m i co najwyżej n powtórzeń poprzedniego
? co najwyżej jedno powtórzenie poprzedniego; to samo co {0,1}
| zarówno poprzednie lub następne wyrażenie może pasować
() grupowanie

Wspólny termin dla wzorów, które używają tego dziwnego nazewnictwa to wyrażenia regularne. W Ruby, tak jak w Perlu, ogólnie są one otoczone raczej przez ukośniki niż cudzysłowy. Jeśli nigdy wcześniej nie pracowałeś z wyrażeniami regularnymi, prawdopodobnie nie wyglądają dla Ciebie specjalnie użytecznie, ale mądrze byłoby poświęcić im trochę czasu i się z nimi zapoznać. Mają wydajną siłę wyrazu, która pozwoli Ci zaoszczędzić bólu głowy (i wielu linii kodu), kiedy tylko potrzebujesz zrobić dopasowanie do wzorca, wyszukiwać lub wykonać inne manipulacje na łańcuchach tekstu.

Dla przykładu, załóżmy że chcemy testować czy ciąg pasuje do opisu: "Zaczyna się małą literą f, która jest odrazu po jednej dużej literze i opcjonalnie jakieś po tym, jak długo nie ma więcej małych liter.". Jeżeli jesteś doświadczonym programistą C, prawdopodobnie już napisałeś około tuzina linii kodu w swojej głowie, prawda? Przyznaj się, możesz ledwie sobie pomóc. Ale w Ruby potrzebujesz jedynie by łańcuch został sprawdzony pod kątem występowania wyrażenia regularnego /^f[A-Z][^a-z]*$/.

Co powiesz na "Zawarcie liczb szesnastkowych załączonych w nawiasach kątowych"? Nie ma problemu.

ruby> def chab(s)   # "contains hex in angle brackets"
    |    (s =~ /<0(x|X)(\d|[a-f]|[A-F])+>/) != nil
    | end
  nil
ruby> chab "Not this one."
  false
ruby> chab "Maybe this? {0x35}"    # wrong kind of brackets
  false
ruby> chab "Or this? <0x38z7e>"    # bogus hex digit
  false
ruby> chab "Okay, this: <0xfc0004>."
  true

Chociaż wyrażenia regularne mogą wprawiać w zakłopotanie na pierwszy rzut oka, możesz szybciej uzystać sadysfakcję będąc zdolnym do wyrażania siebie bardziej zwięźle.

Tutaj jest mały program do pomocy w eksperymentowaniu z wyrażeniami regularnymi. Zapisz go jako regx.rb i uruchom wpisując "ruby regx.rb" w wierszu poleceń.

# Wymagany jest terminal ANSI!

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

puts "Wprowadź pusty ciąg, aby zamknąć."

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

Program wymaga podwójnego wejścia, jeden dla łańcucha i jeden dla wyrażenia regularnego. Łańcuch jest sprawdzany ponownie przez wyrażenie regularne, wówczas wszystkie pasujące części wyświetlają się jako podświetlone przez inwersję. Nie myśl teraz o szczegółach; analiza tego kodu będzie wkrótce.

str> foobar
pat> ^fo+
foobar

Jak widzisz podświetla się dokładnie to co pasuje do wzorca z wyrażenia regularnego.

Spróbujmy jeszcze kilka wejść.

str> abc012dbcd555
pat> \d
abc012dbcd555

Jeśli to Cię zaskoczyło, odnieś się do tabelki na górze tej strony: \d nie jest powiązany ze znakiem d, ale raczej pasuje do pojedyńczej cyfry.

Co jeśli istnieje więcej niż jednen sposób poprawnego dopasowania wzoru?

str> foozboozer
pat> f.*z
foozboozer

foozbooz pasuje zamiast samego fooz, gdyż wyrażenie regularne dopasowuje możliwie najdłuższy podciąg.

Oto wzór do wyizolowania pola czasu rozdzielanego dwukropkiem.

str> Wed Feb  7 08:58:04 JST 1996
pat> [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 to wyrażenie pasuje lub jeśli żaden wzór nie pasuje.

ruby> "abcdef" =~ /d/
   3
ruby> "aaaaaa" =~ /d/
   nil