Регулярные выражения (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) — как изолировать слишком много ловящий шаблон, проверяя его на нарочно неудобном образце.