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

Regex

Что это такое

Регулярное выражение — это небольшой язык шаблонов, которым описывают текст. Вы пишете, как текст должен выглядеть, а движок сопоставления отвечает, подходит ли строка, какая её часть совпала и из каких кусков она состоит; тот же синтаксис встречается в правиле проверки, поиске с заменой в редакторе, фильтре журнала и определении маршрута, поэтому его стоит выучить как следует, а не копировать из результатов поиска.

Почему это важно

Регулярное выражение — самый короткий способ выразить одни проверки и самый дорогой способ выразить другие. Шаблон, проверяющий адрес электронной почты, может молча отвергать настоящие адреса или принимать бессмыслицу; шаблон с вложенными квантификаторами может выполняться экспоненциально долго на короткой строке и подвесить обработчик запросов. Синтаксис плотен, поэтому неверный шаблон всё равно чему-то соответствует, и ошибкой становится ложное срабатывание, на которое не указывает ни одно сообщение. Практический навык состоит в том, чтобы знать те немногие конструкции, которые покрывают обычную работу. И ещё узнавать формы, которые грозят бесконтрольным сопоставлением.

Ключевые идеи

Шаблон описывает одну позицию за раз

Движок идёт по строке и спрашивает, может ли шаблон совпасть начиная с каждой позиции, если вы его не привязали. ^ означает начало строки, $ — конец. Без привязок проверочный шаблон подтверждает лишь, что совпадение где-то есть, однако о корректности всего значения это не говорит. Это и есть самый частый источник ложных срабатываний.

шаблон: [0-9]{3}
"abc123def" -> совпадает с "123"      (содержит его)
"^[0-9]{3}$" -> не совпадает          (всё значение — не три цифры)

Классы символов и экранирование задают алфавит

Литеральный символ совпадает сам с собой. Класс в квадратных скобках совпадает с любым из своих членов, а сокращения покрывают привычные наборы: цифру, символьный знак, пробельный символ. Отрицание с ^ внутри класса его инвертирует; метасимволы, которые должны быть литеральными, нужно экранировать, а правила различаются для самого шаблона и для окружающей строки — поэтому обратная косая черта так часто требует двух.

\d      одна цифра
\w      символьный знак (буква, цифра, подчёркивание)
\s      пробельный символ
[^,]    всё, кроме запятой
\.      литеральная точка, а не «любой символ»

Квантификаторы по умолчанию жадные

Квантификатор говорит, сколько раз может повториться предыдущий элемент. Вариантов четыре: ноль или больше, один или больше, необязательно или точное число. По умолчанию он берёт как можно больше, а затем отдаёт по одному, пока остаток шаблона не совпадёт. Завершающий ? заставляет его брать как можно меньше. И жадность — обычная причина того, что шаблон захватывает куда больше текста, чем задумано.

"<b>one</b> and <b>two</b>"
<.+>    -> совпадает со всей строкой  (жадно)
<.+?>   -> совпадает с "<b>one</b>"   (лениво)

Группы захватывают, а обратные ссылки оглядываются

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

\b(\w+)\s+\1\b     -> "the the"   (одно слово дважды)
(\d{4})-(\d{2})    -> захватывает год, затем месяц

Катастрофический откат — реальный риск

Когда несколько квантификаторов могут потребить один и тот же текст, движок может перебрать экспоненциальное число сочетаний, прежде чем заключить, что совпадения нет. Короткого ввода с множеством повторов достаточно, чтобы это заняло минуты. Формы, которых стоит избегать, — вложенные квантификаторы поверх перекрывающихся классов и альтернативы, способные совпасть с одним и тем же префиксом. Привязки, перевод одного квантификатора в притяжательный или ленивый вид и упрощение классов символов снижают риск.

(a+)+b     на "aaaaaaaaaaaaaaaaaaaaaaaaac"
движок перебирает 2^n разбиений, прежде чем сдаться

Практика

Проверяйте, а не ищите внутри

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

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

Извлеките и переиспользуйте захват

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

Готово, когда: первый шаблон возвращает три поля по отдельности, а второй сообщает только о настоящих удвоенных словах.

Частые ошибки

  • Отсутствие привязок в проверочном шаблоне — значение с мусором вокруг принимается; исправить: привязывайтесь к началу и концу, когда совпасть должно всё значение.
  • Вложенные квантификаторы поверх перекрывающихся классов — время сопоставления взрывается на обычном вводе; исправить: упростите классы или сделайте один квантификатор ленивым либо притяжательным.
  • Забытое экранирование литеральной точки — шаблон ловит любой символ вместо точки; исправить: экранируйте метасимволы, которые должны остаться литеральными.
  • Расчёт на жадное сопоставление не для той задачи — совпадение тянется от первого открывающего тега до последнего закрывающего; исправить: используйте ленивый квантификатор или класс, в который разделитель не входит.
  • Один нечитаемый шаблон без пояснений — следующий читатель не решается его менять; исправить: дайте ему имя, опишите в комментарии, что он принимает, и проверьте его на неудобных вводах.

Что читать дальше

  • Командная оболочка Unix (01-cs-fundamentals/13-unix-shell) — инструменты, где шаблоны используются чаще всего и где заметны различия диалектов.
  • Чистый код (01-cs-fundamentals/08-clean-code) — отношение к шаблону как к именованному документированному коду, а не к загадке внутри строки.
  • Отладка (01-cs-fundamentals/11-debugging) — как изолировать слишком много ловящий шаблон, проверяя его на нарочно неудобном образце.