Страница 49 из 53
str = "а * ((b-c)/(d-e) - f) * g"
reg = /(? # Начало именованного выражения.
( # Открывающая круглая скобка.
(?: # Незапоминаемая группа.
(?> # Сопоставление с собственническим выражением:
\[()] # экранированная скобка
| # ЛИБО
[^()] # вообще не скобка. )
) # Конец собственнического выражения.
| # ЛИБО
g # Вложенная группа в скобках (рекурсивный вызов).
)* # Незапоминаемая группа повторяется нуль или
# более раз.
) # Закрывающая круглая скобка.
) # Конец именованного выражения.
/x
m = reg.match(str).to_a # ["((b-c)/(d-e) - f)", "((b-c)/(d-e) - f)"]
Отметим, что левосторонняя рекурсия запрещена. Следующий пример допустим:
str = "bbbaccc"
re1 = /(?<foo>a|bg<foo>c)/
re1.match(str).to_a # ["bbbaccc","bbbaccc"]
А такой — нет:
re2 = /(?<foo>a|g<foo>c)/ # Синтаксическая ошибка!
Ошибка объясняется наличием рекурсивного обращения в начале каждой альтернативы. Немного подумав, вы поймете, что это приведет к бесконечному возврату.
3.14. Примеры регулярных выражений
В этом разделе мы приведем краткий перечень регулярных выражений, которые могут оказаться полезны на практике или просто послужат учебными примерами. Для простоты примеров ни одно выражение не зависит от наличия Oniguruma.
3.14.1. Сопоставление с IP-адресом
Пусть мы хотим понять, содержит ли строка допустимый IPv4-адрес. Стандартно он записывается в точечно-десятичной нотации, то есть в виде четырех десятичных чисел, разделенных точками, причем каждое число должно находиться в диапазоне от 0 до 255.
Приведенный ниже образец решает эту задачу (за немногими исключениями типа «127.1»). Для удобства восприятия мы разобьем его на части. Отметим, что символ d дважды экранирован, чтобы косая черта не передавалась из строки в регулярное выражение (чуть ниже мы решим и эту проблему).
num = "(\d|[01]?\d\d|2[0-4]\d25[0-5])"
pat = ^(#{num}.){3}#{num}$"
ip_pat = Regexp.new(pat)
ip1 = "9.53.97.102"
if ip1 =~ ip_pat # Печатается: "да"
puts "да"
else
puts "нет"
end
Надо признать, что в определении переменной num слишком много символов обратной косой черты. Определим ее в виде регулярного выражения, а не строки:
num = /(d1[01]?dd|2[0-4]d|25[0-5])/
Когда одно регулярное выражение интерполируется в другое, вызывается метод to_s, который сохраняет всю информацию из исходного регулярного выражения.
num.to_s # "(?-mix:(\d|[01]?\d\d|2[0-4]\d|25[0-5]))"
Иногда для встраивания удобно использовать регулярное выражение, а не строку. Хорошее эвристическое правило: интерполируйте регулярные выражения, если веских причин интерполировать строки.
IPv6-адреса пока не очень широко распространены, но для полноты рассмотрим и их. Они записываются в виде восьми шестнадцатеричных чисел, разделенных двоеточиями, с подавлением начальных нулей.
num = /[0-9A-Fa-f]{0,4}/
pat = /^(#{num}:){7}#{num}$/
ipv6_pat = Regexp.new(pat)
v6ip = "abcd::1324:ea54::dead::beef"
if v6ip =~ ipv6_pat # Печатается: "да"
puts "да"
else
puts "нет"
end
3.14.2. Сопоставление с парой «ключ-значение»
Иногда приходится работать со строками вида «ключ=значение» (например, при разборе конфигурационного файла приложения).
Следующий код извлекает ключ и значение. Предполагается, что ключ состоит из одного слова, значение продолжается до конца строки, а знак равенства может быть окружен пробелами:
pat = /(w+)s*=s*(.*?)$/
str = "color = blue"
matches = pat.match(str)
puts matches[1] # "color"
puts matches[2] # "blue"
3.14.3. Сопоставление с числами, записанными римскими цифрами
Следующее довольно сложное регулярное выражение сопоставляется с любым правильно записанным римскими цифрами числом (до 3999 включительно). Как и раньше, для удобства восприятия образец разбит на части:
rom1 = /m{0,3}/i
rom2 = /(d?c{0,3}|с[dm])/i
rom3 = /(l?x{0,3}|x[lс])/i
rom4 = /(v?i{0,3}|i[vx])/i
roman = /^#{rom1}#{rom2}#{rom3}#{rom4}$/
year1985 = "MCMLXXXV"
if year1985 =~ roman # Печатается: "да"
puts "да"
else
puts "нет"
end
Возможно, у вас появилось искушение поставить в конец всего выражения модификатор i, чтобы сопоставлялись и строчные буквы: