Регулярные выражения — спасение от всех бед для одних и ночной кошмар
для других разработчиков, а если говорить объективно, то это мощнейший
инструмент, требующий, однако, большой осторожности при применении.
Регулярные выражения (регексы, регекспы, регулярки) в языке Ruby
основаны на синтаксисе Perl 5 и потому в основных чертах знакомы всем,
кто использовал Perl, Python или PHP. Но Ruby тем и хорош, что каждый
компонент языка реализован со своим собственным подходом, упрощающим
использование данного инструмента и увеличивающим его мощность. В
предлагаемой мной небольшой статье рассматриваются особенности регулярок
в Ruby и их применение в различных операторах.
В Ruby все — объект
Прежде всего стоит отметить, что регулярное выражение является объектом
соответствующего класса. Соответственно, его можно создавать через вызов
new и объединять(union).
r1 = Regexp.new "a”
r2 = Regexp.new "b”
ru = Regexp.union r1, r2
Выражение, получившееся в результате объединения будет соответствовать
строкам, соответствующим хотя бы одному из объединяемых шаблонов.
Оператор сопоставления регулярки со строкой возвращает индекс первого
совпадения или nil, но нам во многих случаях нужна и другая информация о
найденном совпадении. Можно, как и Perl, воспользоваться специальными
переменными, $~, $’, $& и так далее. Если переменные $1, $2, …,
соответствующие группам, запомнить довольно просто, то как люди вообще
пользуются остальными для меня всегда оставалось загадкой. Поэтому в
Ruby конечно же есть другой подход — можно использовать метод
Regexp.last_match
"abcde” =~ /(b)(c)(d)/
Regexp.last_match[0]
Regexp.last_match[1]
Regexp.last_match[2]
Regexp.last_match[3]
Regexp.last_match.pre_match
Regexp.last_match.post_match
Поименованные группы
Ruby, начиная с версии 1.9 поддерживает синтаксис поименованных групп:
"a reverse b".gsub /(?<first>\w+) reverse (?<second>\w+)/, '\k<second> \k<first>'
Этот же пример демонстрирует и использование обратных ссылок, но эта
возможность и так есть уже во всех современных реализациях PCRE.
\k<group_name> — эта специальная последовательность по сути является аналогом обратных ссылок для именованных групп.
\g<group_name> — последовательность, соответствующая повторению
ранее заданной именованной группы. Различие между ними просто показать
на примере:
"1 1" =~ /(?<first>\d+) \k<first>/
"1 2" =~ /(?<first>\d+) \k<first>/
"1 a" =~ /(?<first>\d+) \k<first>/
"1 1" =~ /(?<first>\d+) \g<first>/
"1 2" =~ /(?<first>\d+) \g<first>/
"1 a" =~ /(?<first>\d+) \g<first>/
Получить совпадения связанные с этими группами также можно через объект MatchData:
Regexp.last_match[:first]
Другие способы проверить соответствие
Кроме традиционного =~ в Ruby есть и другие способы проверить строку на
совпадение с регулярным выражением. В частности, для этого предназначен
метод match, который особенно хорош тем, что может вызываться
применительно как к объекту класса String, так и к экземпляру Regexp. Но
и это не все. Получить совпадение строки регуляркой можно обычным
методом индексирования:
"abcde"[/bc?f?/]
, а также методом slice:
"abcde".slice(/bc?f?/)
Кроме того, есть и еще один, на вид не самый логичный способ:
/bc?f?/ === "abcde"
Вряд ли кто-то будет использовать подобный синтаксис, но и этому
замечательному свойству языка Ruby есть применение, о чем будет написано
далее.
Применение регулярок в различных функциях
Одним из самых полезных применений регулярных выражений в Ruby, которое
встречается однако не так часто, является их использование в операторе
case. Пример:
str = 'september'
case str
when /june|july|august/:
puts "it's summer"
when /september|october|november/:
puts "it's autumn"
end
Все дело в том, что сравнение в case как раз выполняется вышеупомянутым оператором ===(подробнее здесь), что и позволяет очень лаконично и элегантно использовать регекспы в таких случаях.
Также регулярки можно использовать в функции split. Пример с ruby-doc:
"1, 2.34,56, 7".split(%r{,\s*})
Один из способов получения списка слов из строки с помощью этой функции:
"one two three”.split(/\W+/)
Для работы с кириллическими строками:
"строка, из которой нужно получить список слов".split(/[^[:word:]]+/)
(ruby 1.9 only)
Для разделения строки на части иногда гораздо удобнее использовать метод scan. Предудыщий пример с использованием этого метода:
"строка, из которой нужно получить список слов".scan(/[[:word:]]+/)
(ruby 1.9 only)
Функция sub, выполняющая замену первого вхождения подстроки, также может принимать на вход объект Regexp:
"today is september 25".sub(/\w+mber/, 'july')
Аналогично можно использовать регулярные выражения в методах sub!, gsub и gsub!..
Метод partition, разделяющий строку на 3 части, также может использовать регулярное выражение в качестве разделителя:
"12:35".partition(/[:\.,]/)
Аналогично можно использовать регулярные выражения в методе rpartition.
Методы index и rindex также могут работать с регулярками, возвращают
они, понятное дело, индексы первого и последнего их вхождения в строку.
|