Использование регулярных выражений в Ruby

в 14:39, , рубрики: ruby, Регулярные выражения, метки: ,

Регулярные выражения — спасение от всех бед для одних и ночной кошмар для других разработчиков, а если говорить объективно, то это мощнейший инструмент, требующий, однако, большой осторожности при применении. Регулярные выражения (регексы, регекспы, регулярки) в языке 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]            # "asd"
Regexp.last_match[1]            # "b"
Regexp.last_match[2]            # "c"
Regexp.last_match[3]            # "d"
Regexp.last_match.pre_match     # "a"
Regexp.last_match.post_match    # "e"

Поименованные группы

Ruby, начиная с версии 1.9 поддерживает синтаксис поименованных групп:

"a reverse b".gsub /(?<first>w+) reverse (?<second>w+)/, 'k<second> k<first>'     # “b a”

Этот же пример демонстрирует и использование обратных ссылок, но эта возможность и так есть уже во всех современных реализациях PCRE.

k<group_name> — эта специальная последовательность по сути является аналогом обратных ссылок для именованных групп.
g<group_name> — последовательность, соответствующая повторению ранее заданной именованной группы. Различие между ними просто показать на примере:

"1 1" =~ /(?<first>d+) k<first>/    # 0
"1 2" =~ /(?<first>d+) k<first>/    #nil
"1 a" =~ /(?<first>d+) k<first>/     #nil

"1 1" =~ /(?<first>d+) g<first>/    # 0
"1 2" =~ /(?<first>d+) g<first>/    # 0
"1 a" =~ /(?<first>d+) g<first>/     #nil

Получить совпадения связанные с этими группами также можно через объект MatchData:

Regexp.last_match[:first]

Другие способы проверить соответствие

Кроме традиционного =~ в Ruby есть и другие способы проверить строку на совпадение с регулярным выражением. В частности, для этого предназначен метод match, который особенно хорош тем, что может вызываться применительно как к объекту класса String, так и к экземпляру Regexp. Но и это не все. Получить совпадение строки регуляркой можно обычным методом индексирования:

"abcde"[/bc?f?/]         # "bc"

, а также методом slice:

"abcde".slice(/bc?f?/)        # "bc"

Кроме того, есть и еще один, на вид не самый логичный способ:

/bc?f?/ === "abcde"        # true

Вряд ли кто-то будет использовать подобный синтаксис, но и этому замечательному свойству языка 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*})         #  ["1", "2.34", "56", "7"]

Один из способов получения списка слов из строки с помощью этой функции:

“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')    # "today is july 25"

Аналогично можно использовать регулярные выражения в методах sub!, gsub и gsub!..

Метод partition, разделяющий строку на 3 части, также может использовать регулярное выражение в качестве разделителя:

"12:35".partition(/[:.,]/)        #  ["12", ":", "35"]

Аналогично можно использовать регулярные выражения в методе rpartition.

Методы index и rindex также могут работать с регулярками, возвращают они, понятное дело, индексы первого и последнего их вхождения в строку.

Дополнительная литература

1. Фридл — Регулярные выражения
2. Флэнаган, Мацумото — Язык программирования Ruby
3. Ruby-doc class Regexp

Автор: rsludge

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js