Перейти к основному содержимому

Регулярные выражения — флаги и жадность

Разработчику

Предыдущий: проверки вокруг совпадения.
Следующий: рецепты и командная строка.
Жадный и ленивый .* — пресет в лаборатории и разбор в готовых паттернах.


Флаги — как меняется поведение

Флаг (модификатор) подключается к шаблону и меняет правила сопоставления — учитывать ли регистр, искать ли все вхождения, как трактовать ^ и $ в многострочном тексте.

В JavaScript флаги пишут после закрывающего /:

/pattern/gim

В C# — через RegexOptions:

Regex.IsMatch(text, pattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);

В Python — второй аргумент re.match / re.search:

re.findall(r'error', text, re.IGNORECASE | re.MULTILINE)
ФлагJSPython (re)Смысл для новичка
без учёта регистраiI / IGNORECASEThe = the = THE
все совпаденияgfindall / циклне останавливаться на первом
многострочностьmM / MULTILINE^ и $ на каждой строке
точка включает \nsS / DOTALL. совпадает и с переводом строки
Unicodeuв Py3 по умолчаниюкорректные классы вроде \p{L}
только с позицииy"липкое" сопоставление (редко)

В C# отдельного флага g нет: Match — первое совпадение, Matches — все.


Флаг i — регистр

Текст:

Error в логе и ERROR на сервере
ШаблонНайдёт
Errorтолько Error
Error + флаг iError и ERROR

Для кириллицы поведение зависит от движка и локали. Надёжнее явный класс [Ее]rror или нормализация текста (приведение к одному регистру) до поиска.


Флаг g — все вхождения

Текст:

cat sat on the mat
ШаблонПоведение
/at/первое at в cat
/at/gat в cat и at в mat

В редакторе режим "заменить все" обычно включает аналог глобального поиска.

В коде на JavaScript без g метод replace меняет только первое вхождение; с g — все.


Флаг m — несколько строк в одном тексте

Текст:

строка one
строка two

Шаблон ^строка без m срабатывает, только если весь текст начинается со слова строка.

С флагом m ^ и $ привязаны к началу и концу каждой строки (между символами перевода строки).

Пример — строки, заканчивающиеся на one или two:

^(строка one|строка two)$

С флагом m обе строки файла проверяются отдельно.


Флаг s (dotAll) — точка и перевод строки

По умолчанию . не совпадает с \n. В многострочном JSON, логе или HTML-блоке шаблон .*? "ломается" на границе строки.

С флагом s точка включает перевод строки. Удобно для коротких фрагментов, но опасно в сочетании с жадным .* на больших текстах.


Жадность — главная ловушка новичка

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


Пример с HTML-подобными тегами

Текст:

<div>A</div> и <div>B</div>

Шаблон:

<div>.*</div>
ЭтапЧто происходит
<div>совпало с первым тегом
.*жадно тянет всё до последнего возможного
</div>подстраивается под последний </div>

Одно огромное совпадение: от первого <div> до последнего </div> — внутри и A, и B.


Ленивый квантификатор ? после *, +, {}

Добавьте ? после квантификатора — повторение станет ленивым (minimal, non-greedy): движок берёт минимум, достаточный для остатка шаблона.

ЖадныйЛенивый
.*.*?
.+.+?
\d{2,5}\d{2,5}?

Тот же текст, шаблон:

<div>.*?</div>

Результат: два совпадения — <div>A</div> и <div>B</div>.

Потренируйтесь в лаборатории — пресет Жадный и ленивый.


Жадные квантификаторы *+, ++, ?+

В движках PCRE и .NET есть притяжательные (possessive) квантификаторы: они жадные, но не отдают уже съеденные символы при backtracking.

Пример: (a+)+ на длинной строке из a без завершения может вызвать катастрофический backtracking (ReDoS). Вариант (a++)+ ведёт себя иначе — полезно в узких оптимизациях, для новичка достаточно знать, что "двойной плюс" существует в продвинутых движках.


Три способа сузить "любые символы"

ПодходШаблонКогда уместен
Ленивый повтор<div>.*?</div>несколько однотипных блоков подряд
Отрицание класса<div>[^<]*</div>внутри тега нет вложенного <
Конкретная структурапарсер DOM / JSONвложенность и теги с атрибутами

Для разметки с вложенностью regex — вспомогательный инструмент, не замена парсера.


Backtracking простыми словами

Движок пробует совместить шаблон слева направо. Если ветка не сошлась, он откатывается и пробует отдать символы жадному квантификатору.

Жадный .* на длинном тексте с "почти подходящим" хвостом создаёт много откатов. Отсюда тормоза и ReDoS на шаблонах вроде (a+)+$ в пользовательском вводе.

Практика:

  • упростить шаблон;
  • заменить .* на [^>\n]* или ленивый .*?;
  • ограничить длину входной строки;
  • для проверки формата в API — таймаут или отдельная библиотека с лимитом шагов (.NET MatchTimeout).

Когда что использовать

ЗадачаПодсказка
Нашёл только первоефлаг g или цикл Matches / findall
The не находит THEфлаг i или явный класс
^ не работает во 2-й строке файлафлаг m
. не переходит на следующую строкуфлаг s
.* съело полстраницы.*? или уточнить класс символов
JSON/HTML целикомпарсер, не regex

Сводка

ПроблемаРешение
Нашёл только первоеg / Matches / findall
Регистр мешаетi / IGNORECASE
Якоря только для первой строкиm / MULTILINE
Точка "обрывается" на \ns / DOTALL
Один гигантский matchленивый *? или [^...]*
Зависание на длинном вводеупростить шаблон, таймаут, см. автоматы

Дальше: Рецепты и командная строка →