Регулярные выражения

Если вы когда-либо работали с командной строкой, вы, вероятнее всего, использовали маски имён файлов. Допустим, если хотели полностью удалить файлы в текущей директории, начинающиеся на букву «d», писали команду rm d*.

В принципе, регулярные выражения — это схожий инструмент, однако более мощный. Он позволяет искать строки, проверять их на соответствие определённому шаблону и т. п. Англоязычное название — Regular Expressions либо просто RegExp. Можно сказать, что регулярные выражения — это язык, описывающий шаблоны строк.

Реализация данного инструмента на разных языках программирования различается, правда, не очень сильно. В этой статье мы поговорим о регулярных выражениях в контексте их реализации на Perl.

Основы синтаксиса

Для начала отметим, что любая строка уже сама по себе — это регулярное выражение. Например, выражению Хаха будет соответствовать строка «Хаха» и лишь она. При этом стоит учитывать, что регулярные выражения регистрозависимы, а значит, «хаха» уже будет совсем другим выражением.

Регулярные выражения имеют спецсимволы, их нужно экранировать. Список выглядит следующим образом:

. ^ $ * + ? { } [ ] \ | ( )

Для экранирования используют \, добавляя его перед спецсимволом.

Набор символов

Допустим, надо найти в тексте все междометия, которые обозначают смех. Мы не можем написать просто Хаха, так как не будут учтены такие междометия, как «Хихи», «Хохо», «Хехе» и т. д. И вот здесь нам и пригодятся наборы, которые записываются в квадратных скобках. Вместо того, чтобы указывать конкретный символ, мы можем записать список, то есть строка будет считаться подходящей, если в ней будет находиться любой символ из перечисленных в списке.

К примеру, паттерну [abcd] соответствует любой из символов d, c, b или a.

Большинство спецсимволов внутри набора не нуждается в экранировании, но применение перед ними «\» не будет ошибкой. Нужно экранировать символы «^» и «\», рекомендуется экранировать «]» и «-». Последний применяется для задания диапазонов, но об этом ниже.

Если мы после «[» запишем символ «^», набор получит обратный смысл, а подходящим станет считаться любой символ, кроме тех, что указаны. К примеру, паттерну [^xyz] будет соответствовать любой символ, кроме «x», «y» и «z».

Если вернуться к нашему случаю, то после написания [Хх][аоие]х[аоие] любая из строк «Хаха», «хихи», «хехе» и даже «Хохо» будет отвечать шаблону.

Предопределённые классы символов

Для ряда часто используемых наборов есть специальные шаблоны. Так, для пробела, табуляции и переноса строки применяют \s, для цифр — \d, для подчёркивания, символов латиницы — \w. Когда нужно описать любой символ вообще, применяют точку — ..

Если вы указанные выше классы напишете с прописной буквы (\S, \D, \W), они поменяют смысл на противоположный.

Кроме всего прочего, с помощью регулярных выражений можно проверить, где находится строка по сравнению с остальным текстом. Допустим, выражение \b служит для обозначения границы слова, \B — не границы слова, ^— началa текста,$ — конца текста.

Таким образом, паттерн \bJava\b в строке «Java and JavaScript» найдёт первые 4 символа, а паттерн \bJava\B обнаружит символы c 10-го по 13-й.

Диапазоны

Иногда возникает необходимость обозначить определённый набор, включающий в себя буквы, к примеру, от «б» до «ф». Чтобы не писать [бвгдежзиклмнопрстуф], вы можете задействовать механизм диапазонов, то есть написать лишь [б-ф].

Механизм особенно полезен для русского языка в силу отсутствия конструкции, аналогичной \w. Если хотите обозначить все буквы из русского алфавита, подойдёт паттерн [а-яА-ЯёЁ]. Учтите, что «ё» не включена в общий диапазон букв, поэтому её следует указывать отдельно.

Квантификаторы в регулярных выражениях (указатели количества повторений)

Что делать, когда в нашем «смеющемся» междометии окажется между буквами «х» более, чем одна гласная («Хаахааа»)? Подготовленное нами ранее регулярное выражение уже помочь не сможет. А значит, не обойтись без использования квантификаторов.

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

Итак, благодаря квантификаторам, можно улучшить шаблон для тех же междометий, написав [Хх][аоеи]+х[аоеи]*. Теперь он распознает строки типа «Хааха», «Хихии», «хееех».

«Ленивая» квантификация и регулярные выражения

Представим, что у нас есть задача по поиску всех HTML-тегов в строке:

<p><b>Otus</b> — моя <i>любимая</i> школа онлайн-образования!</p>

Очевидно, что простое решение <.*> тут работать не будет, так как найдётся вся строка полностью, ведь она начинается и заканчивается тегом абзаца. Можно сказать, что содержимым тега считается следующая строка:

p><b>Otus</b> — моя <i>любимая</i> школа онлайн-образования!</p

Так происходит потому, что по умолчанию квантификатор в регулярных выражениях функционирует по, если можно так выразиться, «жадному алгоритму», то есть пытается вернуть самую длинную строку, которая отвечает условию. Проблема решается двумя способами: 1. Используется выражение <[^>]*>. Оно запрещает правую угловую скобку считать содержимым тега. 2. Квантификатор объявляется «ленивым», а не «жадным». Для этого справа к квантификатору добавляется символ ?. Таким образом, при поиске всех тегов выражение обратится в <.*?>.

«Ревнивая» квантификация и регулярные выражения

Порой, для повышения скорости поиска (например, если строка не соответствует регулярному выражению) алгоритму запрещают возвращаться к предыдущим шагам поиска в целях нахождения возможных соответствий для оставшейся части регулярного выражения. Это «ревнивая» квантификация. Квантификатор становится таковым при добавлении к нему справа символа +. Другое применение — исключение нежелательных совпадений. Таким образом, паттерну ab*+a в строке «ababa» соответствуют лишь первые 3 символа, но не соответствуют символы с 3-го по 5-й, ведь символ «a», находящийся на 3-й позиции, уже использовался для получения первого результата.

Скобочные группы

Для шаблона «смеющегося» междометия осталось всего ничего — учесть то обстоятельство, что буква «х» способна встречаться больше, чем один раз, допустим, «Хахахахааахахооо», да и вообще, слово может не заканчиваться на «х». Можно задействовать квантификатор для группы [аиое]+х, однако если мы просто укажем [аиое]х+, квантификатор + будет относиться лишь к символу «х», но не к выражению целиком. Дабы это исправить, регулярное выражение следует взять в круглые скобки: ([аиое]х)+.

Итак, регулярное выражение преобразуется в [Хх]([аиое]х?)+, причём в начале следует строчная либо заглавная «х», потом — ненулевое произвольное количество гласных, которые перемежаются одиночными строчными «х» (возможно, однако необязательно). Но это регулярное выражение решает проблему лишь частично, ведь под него попадут и такие строки, как, допустим, «хихахех». Но можно использовать набор изо всех гласных только единожды, а потом опираться на результаты первого поиска. Но как это сделать?

Обратная связь — запоминание результата поиска по группе

Результат поиска по скобочной группе сохраняется в отдельной ячейке памяти, а доступ к ней разрешён для применения в последующих частях регулярного выражения. Если вернуться к задаче поиска HTML-тегов на странице, давайте представим, что нужно не только найти теги, но и определить их названия. В данном случае поможет регулярное выражение <(.*?)>.

<p><b>Otus</b> — моя <i>любимая</i> школа онлайн-образования!</p>

Вот результат поиска по всему регулярному выражению:

«<p>», «<b>», «</b>», «<i>», «</i>», «</p>».

А вот итог поиска по 1-й группе:

«p», «b», «/b», «i», «/i», «/i», «/p».

Вы можете ссылаться на результат поиска по группе с помощью регулярного выражения \n, в котором n — это цифра в пределах 1-9. К примеру, регулярному выражению (\w)(\w)\1\2 отвечают строки «aaaa», «abab», однако они не соответствует «aabb».

Когда выражение берётся в скобки лишь для использования в её отношении квантификатора, то сразу после первой скобки следует добавить ?:, допустим, (?:[abcd]+\w).

С применением данного механизма мы легко перепишем всё регулярное выражение, приведя его к следующему виду:

 [Хх]([аоие])х?(?:\1х?)*

Перечисление

Желаете проверить, соответствует ли строка хотя бы одному из шаблонов? Воспользуйтесь аналогом булевого оператора OR, записываемого посредством символа |. Здесь, под шаблон Лена|Одиночество попадают строки и «Лена», и «Одиночество».

Удобно применять перечисления внутри скобочных групп. Так, регулярное выражение (?:a|b|c|d) абсолютно эквивалентно [abcd]. В этом случае 2-й вариант даже лучше из-за читаемости и производительности.

Используя данный оператор, мы легко добавим к регулярному выражению для поиска междометий способность распознавать смех типа «Ахахаах» — это единственная усмешка, начинающаяся с гласной:

[Хх]([аоие])х?(?:\1х?)*|[Аа]х?(?:ах?)+

Полезные сервисы по регулярным выражениям

Проверить своё регулярное выражение и потренироваться можно на специальных сервисах: RegExr, Regex101, Regexpal.

Если хотите понять, как работает регулярное выражение, попавшее к вам в руки, используйте Regexper — это сервис, который способен строить понятные диаграммы на основе регулярных выражений.

Также существует визуальный конструктор функций JS, предназначенный для работы с регулярными выражениями — это RegExp Builder.

Помните: не всегда следует решать задачу с помощью регулярных выражений (вспоминаем известный анекдот про программиста и проблему, которая превратилась в две проблемы). В некоторых случаях, к примеру, лучше написать развёрнутый автомат конечных состояний.