Лаборатория кодера

Справочник · Код и сборка

Регулярные выражения: синтаксис и готовые шаблоны

Классы символов, квантификаторы, группы, просмотр вперёд и назад, флаги и различия между JavaScript, Python и grep.

Обновлено · 6 мин чтения

Регулярное выражение описывает шаблон строки. Движок идёт по тексту и пытается сопоставить шаблон с каждой позиции по очереди. Большая часть синтаксиса одинакова в JavaScript, Python, PCRE, Go и Java, различия в деталях собраны в конце. Проверять шаблоны удобно в тестере регулярных выражений.

Символы и классы

ЗаписьЧто совпадает
.любой символ, кроме перевода строки (с флагом s — и его тоже)
\dцифра; в JS — только 0-9, в Python 3 — любая цифра Юникода
\wбуква, цифра или _; в JS только латиница, в Python — любые буквы
\sпробельный символ: пробел, табуляция, переводы строк
\D, \W, \Sвсё, кроме соответствующего класса
[abc]один из перечисленных символов
[^abc]любой символ, кроме перечисленных
[a-z0-9]диапазоны
[.]точка как обычный символ: внутри скобок она не особенная
\. \* \(экранирование особых символов
\t \n \rтабуляция, перевод строки, возврат каретки
Асимвол по коду (JS, Python)

Квантификаторы

ЗаписьСколько раз
*0 и больше
+1 и больше
?0 или 1
{3}ровно 3
{2,5}от 2 до 5
{2,}2 и больше
*? +? ?? {2,5}?то же, но «ленивые»: как можно меньше

По умолчанию квантификаторы жадные: захватывают максимум и отступают, только если дальше шаблон не сходится. Разница на примере строки <b>один</b> и <b>два</b>:

text
<b>.*</b>    совпадает со всей строкой целиком
<b>.*?</b>   совпадает с <b>один</b>, затем с <b>два</b>
<b>[^<]*</b> то же, что ленивый вариант, но быстрее

Якоря и границы

ЗаписьГде
^начало строки (с флагом m — начало каждой строки)
$конец строки (с флагом m — конец каждой строки)
\bграница слова: между \w и не \w
\Bне граница слова
\A, \Zначало и конец всего текста (Python, PCRE; в JS нет)

Якоря не потребляют символы, они проверяют позицию. Без ^ и $ шаблон \d{4} найдёт четыре цифры и внутри 123456.

Группы и обратные ссылки

ЗаписьСмысл
(abc)группа с захватом, доступна как $1 или \1
(?:abc)группа без захвата, только для объединения
(?<year>\d{4})именованная группа (в Python также (?P<year>...))
\1то, что захватила первая группа, внутри шаблона
\k<year>ссылка на именованную группу
a|bили a, или b

Пример: найти повторяющееся слово «на на».

text
\b(\w+)\s+\1\b

Замена с группами переставляет части даты из 2026-11-03 в 03.11.2026:

js
"2026-11-03".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3.$2.$1");
"2026-11-03".replace(/(?<y>\d{4})-(?<m>\d{2})-(?<d>\d{2})/, "$<d>.$<m>.$<y>");

В Python в строке замены пишут \3.\2.\1 или \g<d>.

Просмотр вперёд и назад

Проверяют, что рядом есть (или нет) какой-то текст, но не включают его в совпадение.

ЗаписьУсловие
X(?=Y)за X идёт Y
X(?!Y)за X не идёт Y
(?<=Y)Xперед X стоит Y
(?<!Y)Xперед X не стоит Y
text
\d+(?=\s?₽)          число перед знаком рубля
(?<=\$)\d+(\.\d\d)?  сумма после знака доллара
^(?=.*\d)(?=.*[a-z]).{8,}$   не меньше 8 символов, есть цифра и строчная буква
\b(?!test)\w+\.py\b  имена .py-файлов, не начинающиеся с test

Флаги

ФлагJSPythonЧто меняет
без учёта регистраire.Ia совпадает с A
многострочныйmre.M^ и $ работают на каждой строке
точка и перевод строкиsre.S. совпадает и с \n
глобальныйg—найти все совпадения (в Python — findall, finditer)
Юникодuпо умолчаниюкорректные суррогатные пары, \p{...}
подробный—re.Xпробелы и комментарии внутри шаблона
липкийy—искать только с позиции lastIndex

Готовые шаблоны

Это рабочие, но упрощённые шаблоны. Для строгой проверки формата лучше использовать парсер.

ЧтоШаблон
целое число со знаком^[+-]?\d+$
десятичное число^[+-]?(\d+([.,]\d*)?|[.,]\d+)$
дата ГГГГ-ММ-ДД^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$
время ЧЧ:ММ^([01]\d|2[0-3]):[0-5]\d$
IPv4^((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$
шестнадцатеричный цвет^#([0-9a-fA-F]{3}){1,2}$
почта (грубо)^[^\s@]+@[^\s@]+\.[^\s@]+$
UUID^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
слаг для адреса^[a-z0-9]+(-[a-z0-9]+)*$
семантическая версия (кратко)^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$
пробелы в конце строк[ \t]+$ с флагом m
пустые строки^\s*$ с флагом m

Различия между инструментами

В командной строке встречаются три диалекта:

ГдеДиалектОсобенности
grep, sedBRE, базовый+ ? | ( ) { } надо экранировать: \( \+
grep -E, sed -E, awkERE, расширенныйскобки и + работают как обычно; нет \d, вместо него [0-9] или [[:digit:]]
grep -P, Perl, PCREPerl-совместимыйесть \d, ленивые квантификаторы, просмотр вперёд и назад
bash
grep -E '^[0-9]{3}-[0-9]{4}$' phones.txt
grep -oP '(?<=id=)\d+' access.log
sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/g' dates.txt

Классы POSIX: [[:alpha:]], [[:digit:]], [[:space:]], [[:upper:]]. Они учитывают текущую локаль, поэтому в UTF-8 [[:alpha:]] совпадает и с кириллицей.

Производительность и ловушки

  • Вложенные квантификаторы вроде (a+)+$ или (\w+\s?)*$ на неудачной строке дают экспоненциальный перебор вариантов. Это называется катастрофическим откатом и может повесить сервер. Пишите классы так, чтобы части шаблона не могли совпасть с одним и тем же текстом.
  • Отрицательный класс [^"]* почти всегда лучше, чем .*?: движку не нужно откатываться.
  • Не забывайте экранировать точку в доменах и версиях: 1.2.3 как шаблон совпадёт и с 1x2y3.
  • В JS объект RegExp с флагом g помнит lastIndex. Повторный вызов test() на той же строке может вернуть false.
  • В строковых литералах обратную косую черту приходится удваивать: new RegExp("\\d+"). В Python пишите сырые строки r"\d+".