Использование регулярных выражений в 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
    Поделиться публикацией
    Похожие публикации
    Реклама помогает поддерживать и развивать наши сервисы

    Подробнее
    Реклама
    Комментарии 17
    • +1
      «Some people, when confronted with a problem, think “I know, I’ll use regular expressions.” Now they have two problems.»
      — Jamie Zawinski
      • 0
        респект! и спасибо!
        признаться, про Regexp.last_match.pre_match / post_match не знал.
        не дочитал в детстве наверное :)
        • +6
          Забыли главную особенность регулярок в Руби: homakov.blogspot.com/2012/05/saferweb-injects-in-various-ruby.html
          • +1
            я подумал, что все про это и так знают, столько шума было какое-то время назад.
            • 0
              нуу я оче ожидал увидеть это в статье «Использование регулярных выражений в Ruby». Шума было немного )
            • 0
              А я не знал, спасибо. Такое поведение пофиксили в новых версиях? Не знаете, случайно?
              • 0
                Нет, так же работает.
                • +1
                  Это не баг, фиксить тут нечего.
                  • 0
                    Вот именно, это не баг, мало того такое поведение не только в ruby, но и в других языках.
                  • +2
                    Нас много таких, которые не читают спецификаций, полагаясь на то, что разработчики предпочтут понятную и на уровне рефлекса накатанную схему, а не будут мнить себя «не такими как другие».
                  • +2
                    Просто нужно использовать \A и \z вместо ^ и $ соответственно.
                    • 0
                      пиаррр :)
                    • +3
                      И хороший сервис для тестирования регулярок rubular.com/
                      • 0
                        Как по мне вот этот пофункциональнее будет. Пользуюсь больше года.
                      • 0
                        Очень удобно в руби — оформление в переменные именованных групп в регулярках:
                        /(?hello)/ =~ 'hello'; puts hi
                        Но это работает только с регулярками, объявленными непосредственно перед использованием.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.