Как перестать бояться и полюбить регулярные выражения

    В работе и повседневной жизни мы буквально окружены текстами: книги и статьи, письма и чаты, веб-страницы и программный код. Да что там — сама биологическая жизнь основана на ДНК. И для эффективной работы по поиску или модификации текста уже давно был придуман мощный инструмент — регулярные выражения, или regex. Однако по ряду причин далеко не все их используют, или имеют дело с ними только когда заставили при крайней необходимости. В этой статье я попытаюсь исправить эту печальную ситуацию.



    Чтобы функции отыскать, воедино созвать и единою чёрною волей сковать.

    На Хабре уже есть ряд хороших статей, которые помогут разобраться в основах:


    Мы не будем углубляться в работу регулярных выражений или пытаться в полном объёме покрыть все возможности, а рассмотрим на самых элементарных примерах, насколько просто начать пользоваться этим замечательным инструментом.


    Иногда случается так, что регулярные выражения приходят в нашу жизнь совершенно неожиданно. Например, вы разбираетесь с валидацией пароля и сталкиваетесь со следующим:


    ^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$&^()*])[a-zA-Zа-яА-Я0-9!@#$&^()*]{1,}$
    

    после чего настроение неизменно падает. Даже если и удалось продраться через нагромождение символов, то желание возвращаться к регулярным выражениям наверняка сильно уменьшится. Человек же со стороны вообще может принять это за инопланетный язык и начать сторониться аки чумы: «Зачем мне ещё один язык программирования, да к тому же такой сложный?».



    Гэндальф ещё Серый, а потому боится использовать regex.

    Поиск


    Для экспериментов нам понадобится старый добрый Властелин Колец и любой текстовый редактор с поддержкой регулярных выражений. В данной статье будет использоваться Notepad++ (простой, удобный и расширяемый – очень рекомендую).
    Копируем текст без оглавления в приложение, Ctrl+f, отмечаем «Regular expression» и вперёд!



    Пример окна поиска в Notepad++.

    1. Точное совпадение с фразой


    Попробуем найти Исилдура (того, что у Саурона кольцо подрезал) на страницах «Властелина колец»




    Поздравляю! Вы только что использовали регулярное выражение «Исилдур».
    Тут всё просто – совпадение один к одному. Есть еще магия регистров букв (отмечаем галочку «Matсh Case»), но по умолчанию она обычно не включена. «Исилдур», «исилдур», «ИсИлДУР» дадут один и тот же результат. Если же мы знаем регистры букв искомого слова, то можем указать их и отбросить лишние результаты. Однако это может сыграть злую шутку, когда при последующем поиске может потеряться часть результатов. Убирайте галочку, если регистры букв могут быть разными.


    Веб браузеры, текстовые редакторы, мессенджеры – все имеют такую функцию. Однако не все об этом знают. Жаль, что у бумажных книг нет такой возможности.


    Первый шаг сделан, и ничего не мешает двигаться дальше.



    2. Символы подстановки


    Еще одна распространённая возможность — это символ подстановки (wildcard) звёздочка «*». Она заменяет любое количество любых допустимых символов. Самый распространённый пример – поиск файлов.


    Скажем, мы хотим найти фотографии отдыха на море. Для этого в строке поиска введём «*.png» – и получим список всех фотографий в папке:



    Скан паспорта.png
    Море Испания.png
    Тосса-де-Мар, море.png
    Трудовые будни.png
    Закатное море, Сант-Себастиа.png
    

    Уже хорошо. А что будет, если мы добавим слово «море» до или после звездочки?
    море*.png

    Море Испания.png
    

    *море.png

    Тосса-де-Мар, море.png
    

    Следующий вариант вернёт то, что нужно:
    *море*.png

    Море Испания.png
    Тосса-де-Мар, море.png
    Закатное море, Сант-Себастиа.png
    

    «*море*.png» — наглядный пример шаблона поиска, или маски – мы что-то знаем о структуре искомой строчки (в данном случае расширение «.png» в конце и где-то слово «море»), в остальных местах предполагаем любые символы.


    Регулярные выражения, как развитие подстановочных символов, имеют в своём арсенале огромный набор разнообразных средств, с помощью которых мы можем более тонко составлять шаблон поиска. Но пока познакомимся с аналогом звёздочки — точка и звёздочка «.*».



    3. Точка со звёздочкой «.*»


    Забудем о значении звёздочки в подстановочных символах – в регулярных выражениях она обозначает «любое количество, от 0 и до бесконечности». А точка обозначает «любой строковый символ». Вместе получаем «любое количество любых строковых символов».


    Точка не включает в себя символ новой строки, поэтому поиск будет производится до конца строки, или, другими словами, абзаца. Посмотрим, как часто Фродо и Сэм оказывались вместе: для этого поищем по шаблонам «Сэм.*Фродо» и «Фродо.*Сэм»»:




    215 совпадений. Кто бы сомневался…




    Можно это сделать проще, одним запросом. Для этого используем специальный знак ИЛИ «|»: альтернативные варианты разделяются вертикальной чертой и окружаются скобками:
    (Фродо.*Сэм|Сэм.*Фродо)




    Можно заметить, что такой поиск выдал меньше совпадений — 213. Так получается, потому что, например, строчку «Рядом с Фродо шел всхлипывающий Сэм, да и сам Фродо безмолвно плакал.» раздельные запросы найдут два раза.


    Чтобы узнать, как часто упоминается Гэндальф в полной форме, используем шаблон:
    Гэндальф (Серый|Белый)




    Всё. Имея на руках рассмотренное выше, можно начинать исследовать текстовые моря или болота логов. Начните с малого, исследуйте на возможность поиска те приложения, которыми вы часто пользуетесь. Задавайте поиску простые вопросы, и со временем вы сами почувствуете желание сделать поиск более сложным.


    А если остались силы, то можно копнуть чуть глубже и взять на вооружение ещё два базовых понятия, изучив которые вы станете истинными начинающими regex-волшебниками.



    Саруман понимает, какая мощь в его руках, и не боится её. Поэтому он всегда в курсе всех дел.

    4. Количество повторений или квантификаторы


    С одним квантификатором мы уже познакомились – звёздочка. Есть ещё несколько способов задать количество:
    {4} точное количество
    {1,5} диапазон
    {2,} от числа до бесконечности
    {,7} от 0 до числа

    Некоторые часто используемые диапазоны имеют собственные символы:
    * или {0,} любое количество
    ? или {0,1} ноль или один
    + или {1,} есть как минимум один

    Спросим, как часто упоминается одно и то же слово в одном абзаце:




    Бильбо 2 раза — «(Бильбо.*){2}» — упоминается в 7 случаях, а большее количество раз в абзаце — «(Бильбо.*){3,}» — ни разу. Слово «Хоббит» встречается аж 3 раза в строке, как часть слов «Хоббиты» «хоббита» и «Хоббитании».



    5. Представление символов


    Точка (любой символ) — один из примеров представления символов. Другими примерами являются цифра «\d», цифра или буква «\w», набор символов «[abc], «[1-9], «[.,?:;]».
    Например, мы хотим узнать, как часто числа используются в книге:
    «\d+» или «\d{1,}» – как минимум одна цифра подряд:




    Находим номера глав и даты. Если не брать совсем уж легендарные времена (37г.), то рассказ ведётся о втором тысячелетии по летоисчислению хоббитов (Л. Х.) Оценим масштаб саги, для этого найдём 4 цифры подряд — \d{4}




    Первая дата говорит нам о начале табаководства у хоббитов в 1070 году, а Фродо уплывает от нас на Запад в 1421 — 351 год.


    С остальными представлениями символов предлагаю познакомиться самостоятельно.



    Замена




    Как только фамилию Бильбо не переводили: Сумникс, Беббинс, в нашем случае – Торбинс, но для меня есть только один вариант — Бэггинс. Однако это не проблема: почти всегда рядом с возможностью поиска есть возможность замены. Найденное слово можно заменить по всему тексту в одно действие. Для этого воспользуемся вкладкой «Replace»:




    Теперь можно читать книгу с привычным переводом. Но будьте осторожны: шаблон поиска может подойти не только желаемому слову, но и более длинному, содержащему в себе искомое. Тогда при замене «дача» на «деревня» мы неожиданно можем получить «удеревня» или «телепередеревня». Желательно предварительно провести поиск по такому же шаблону и проверить результаты. Усложняйте шаблон для более точного поиска. В случае «дача» мы можем добавить пробел до слова и пробел или знаки препинания в конце: « дача[ .,!?;:]».

    Практика использования


    Для создания, редактирования или исследования регулярных выражений советую использовать понравившийся вам онлайн-редактор. Для меня самым удобным оказался REGEX101:



    Кольцо находит все определения функций в ISO C99, и потому такое могущественное.

    В строке сверху мы вводим регулярное выражение, а снизу — текст, по которому производим поиск. Редактор автоматически подсвечивает найденные совпадения, при наведении на части выражения всплывают подсказки, объясняющие значение, и много других полезных функций. Для замены нужно выбрать «Substitution» в левой панели.

    Заключение


    • Чаще пользуйтесь поиском для удобства и ускорения работы.
    • Используйте звёздочку при поиске файлов, создавая простые шаблоны.
    • Начните использовать простые регулярные выражения в повседневной работе, накапливая опыт и постепенно изучая новые возможности.

    Конечно, в этой статье затронута лишь самая верхушка. Осталось без внимания множество как полезных возможностей, так и возможных проблем. Но если у вас появилось желание более основательно погрузиться в мир regex, помимо справочников и учебников в интернете, на Хабре можно почитать следующие статьи:


    Сегодня я уже не представляю свою работу без регулярных выражений, как и без слепого метода печати. А требовалось всего лишь начать использовать. Чего вам и желаю.


    Only registered users can participate in poll. Log in, please.

    На сколько хорошо вы знаете и как часто используете регулярные выражения?

    • 1.6%А что это такое?3
    • 5.7%Примерно знаю, но никогда не пользовался.11
    • 27.5%Приходится разбираться, когда сталкиваюсь по работе, а так не использую.53
    • 45.6%Иногда использую простые regexp'ы для поиска и замены в текстах.88
    • 19.7%Постоянно пользуюсь, используя большую часть возможностей.38
    Миландр
    Разрабатываем микросхемы, приборы, ПО

    Comments 26

      0
      Ну там же язык Мордора, а не эльфийский.
        +4
        Это язык Мордора, незачем ему звучать здесь!
        +2
        Но не стоит забывать и об обратной стороне медали ;)
        Some people, when confronted with a problem, think
        “I know, I'll use regular expressions.” Now they have two problems.
          0
          А почему ваше регулярное выражение не нашло вот эти повторения:
          спойлер
          image
            +3

            Это особенность Notepad++. Если вы присмотритесь, то заметите, что действие происходит на 139 строке в обоих случаях. Оно просто явно показывает, что это два разных вхождения регулярки в строку.

              +2
              Ок, спасибо
              0
              все нашло, просто на одну строку отображает одно совпадение.
              это все Line 139
              +1
              ^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$&^()*])[a-zA-Zа-яА-Я0-9!@#$&^()*]{1,}$

              Кстати, может кто сказать, зачем здесь столько positive lookahead и почему они не включают в себя русские буквы?


              (?=.*[a-z])
              (?=.*[A-Z])
              (?=.*[0-9])
              (?=.*[!@#$&^()*])

              UPD: Поэкспериментировал, понял. Они нужны, чтобы неявно указать, что в пароле должна быть минимум одна латинская буква в нижнем регистре, в верхнем, цифра и спецсимвол. При этом минимальная длина пароля – 1. Ну-ну.


              Неплохо бы и в статье объяснить, как эта регулярка работает. :)

                0
                Статья даже не затрагивает жадность, а вы предлагаете разобрать выражение, которое использует т.н. позитивный просмотр вперёд (о котором я узнал только что, в то время как про жадность знаю лет 15-20)
                UPD: Поэкспериментировал, понял. Они нужны, чтобы неявно указать, что в пароле должна быть минимум одна латинская буква в нижнем регистре, в верхнем, цифра и спецсимвол. При этом минимальная длина пароля – 1. Ну-ну.
                не минимальная длина пароля, а минимальное количество символов помимо перечисленных, то есть, в совокупности не менее 5 символов?
                  0
                  Ну тут смотря как это назвать русским языком. Проверка на длину — тут именно 1 символ или больше, но в строке в любом случае должны быть 4 разных типа символа, а это может быть только в строке из 4-х символов. Так что формально тут «минимальная длина = 1 символ», а фактически не менее 4-х и при этом разных
                    0
                    Точно. Чтобы лучше читалось и не вводило в заблуждение, последний квантификатор лучше заменить на {4,}
                +9

                Хорошо знаю, и стараюсь не использовать. Регэкспы — это язык программирования state-machine, для которой нет ни самодокументирующегося кода, ни тестов, ни адекватной обработки ошибок.


                От простого abc.+ легко свалиться до полного треша, потому что каждый шаг осмысленный и простой, а результат — нечитаемый. Ту же state-machine для простых случаев можно спокойно переписать используя возможности языка, а для трудных случаев нужно осознать, что state-machine сложная и требует соответствующих конструкций языка.


                То, что кто-то может "прийти и переписать на регэкспах" ничем не отличается от "прийти и переписать на perl'е". Спасибо большое, этот проект пишется не на перле.

                  0
                  Вообще есть более одного варианта писать регулярки в других форматах. Ну, как и конечный автомат, собственно, можно описать более чем одним способом. Parser Combinators, например. И там с некоторой вероятностью будут и тесты, и обработка ошибок (хотя этот пункт наверное самый нетривиальный). Язык обычно можно описать формально, для этого есть грамматики, и инструменты, а вот что делать если текст не соответствует грамматике — тут уже формализмов маловато, и каждый косячит как умеет.
                    0
                    Согласен, сначала регулярки меня восхищали, т.к. на них можно сдедать короткую магию и сразу решить задачу. Но когда я их стал писать много и очень лаконичные, то туда начала сочиться уже сложная логика, которую не все понимают. Пришёл к тому же, что и вы, использую в крайнем случае, когда без них никак. И обязательно с обширным набором тестов.
                    0
                    Человек же со стороны вообще может принять это за инопланетный язык и начать сторониться аки чумы: «Зачем мне ещё один язык программирования, да к тому же такой сложный?».

                    Это как раз простой язык — там меньше синтаксических конструкций по сравнению с языком программирования. А вот сам код на этом языке может получится очень громоздким.

                      +1
                      Люблю регулярки. Для фанатов — regexcrossword.com
                        –1
                        Никак, пока они выглядит как brainfuck
                          0
                          Пространства между предпоследним и последним пунктами в голосовалке больше, чем во всех остальных промежутках вместе взятых дважды.
                          Что делать тем, кто слишком далеко от обоих пунктов между ними?
                            +3
                            У меня была своя история с регулярками. Делал как-то парсер для одного финансового формата. Нагородил циклов, строковых функций, плотно обложил тестами и все полетело. Выглядело жутковато, но работало.

                            Потом мне стрельнуло и я его заменил на парсер на регулярках — стало вообще красиво. Время шло и потребовалось поддержать еще один формат, который был почти такой же, но слегка отличался. Я потратил два дня в попытках поправить регулярку, чтобы работали и старые тесты и новый формат, и все безрезультатно.

                            В итоге плюнул, достал старый парсер (до сих пор помню — класс назывался CrazyLogicParser), добавил один if и все завелось и поехало в прод. С тех пор использую регэкспы только для очень простых вещей.
                              +1
                              Спасибо за интересную статью, много разных читал, но всегда было отвращение и непонимание регулярок. После этой хотя-бы начну использовать при поиске.
                                +1
                                Благодарю. Для тех кто только начинает знакомиться — отличная статья.
                                Автор надеюсь продолжит цикл статей )
                                  0
                                  А можно ли делать замену по регуляркам? Условно говоря, если встретил четыре цифры, то первые три оставь, а четвертую удали.
                                    0
                                    Да, POSIZ в помощь
                                      0
                                      Поиск: \d(\d{3})
                                      Замена: $1
                                      Пояснение: в поиске мы ищем «цифра» + «3 цифры подряд». 3 цифры заключены в скобки (группа захвата), при замене мы заменяем найденное по шаблону слово (4 цифры подряд) на заключённое в группу ($1).
                                        0
                                        У меня недостаточно кармы, поэтому отвечу словом «СПАСИБО!»
                                      0
                                      Желательно также изучить, в каких местах регэкспы уместны и применимы, а в каких даже не стоит о них задумываться.

                                      Only users with full accounts can post comments. Log in, please.