Регулярные выражения — один из самых мощных и одновременно пугающих инструментов в арсенале разработчика. Строка вида (?<=\d{3})\s(?=\w+) способна вызвать оторопь даже у опытного программиста. Однако за этим кажущимся хаосом символов стоит стройная система, которую вполне реально освоить. В этом руководстве мы разберём регулярные выражения от простейших конструкций до продвинутых техник — с большим количеством практических примеров.
Что такое регулярные выражения
Регулярные выражения (regular expressions, regex, regexp) — это формальный язык описания текстовых шаблонов. Они позволяют находить, извлекать и заменять фрагменты текста по заданным правилам. Regex используются практически во всех языках программирования, текстовых редакторах, утилитах командной строки и серверных конфигурациях.
Исторически регулярные выражения появились в работах математика Стивена Клини в 1950-х годах. В программирование их привнёс Кен Томпсон, встроив поддержку regexp в текстовый редактор ed для Unix. С тех пор regex стали неотъемлемой частью обработки текста.
Типичные задачи, решаемые с помощью регулярных выражений:
- Валидация данных — проверка email-адресов, телефонов, паролей, URL
- Поиск и извлечение — нахождение определённых паттернов в тексте
- Замена текста — массовое переименование, форматирование, очистка данных
- Парсинг — разбор логов, конфигурационных файлов, CSV
- Маршрутизация — правила перенаправления в htaccess и Nginx
Базовый синтаксис
Литералы
Самый простой вид регулярного выражения — обычные символы, совпадающие сами с собой. Паттерн cat найдёт подстроку «cat» в словах «cat», «concatenate», «catalog». Регулярные выражения чувствительны к регистру по умолчанию.
Метасимволы
Метасимволы — символы со специальным значением, составляющие основу языка regex:
| Метасимвол | Значение |
|---|---|
. | Любой символ (кроме переноса строки) |
^ | Начало строки |
$ | Конец строки |
* | 0 или более повторений |
+ | 1 или более повторений |
? | 0 или 1 повторение |
| | Логическое ИЛИ (альтернация) |
() | Группировка |
[] | Символьный класс |
{} | Квантификатор с числом повторений |
\ | Экранирование |
Экранирование
Чтобы найти метасимвол как обычный символ, перед ним ставится обратный слеш \. Например, \. ищет точку-пунктуацию, а не «любой символ»:
index\.html — совпадёт с «index.html», но не с «indexXhtml»
Экранируются: \. \* \+ \? \\ \( \) \[ \{ \| \^ \$
Символьные классы
Пользовательские классы [abc]
Квадратные скобки задают множество символов для одной позиции:
[aeiou] — любая гласная (строчная)
[a-z] — любая строчная латинская буква
[A-Z] — любая заглавная латинская буква
[0-9] — любая цифра
[a-zA-Z0-9] — буква или цифра
[a-f0-9] — шестнадцатеричная цифра
Отрицание — символ ^ в начале:
[^0-9] — любой символ, кроме цифры
[^a-z] — любой символ, кроме строчной латинской буквы
Внутри класса большинство метасимволов теряют специальное значение. Исключения: ], \, ^ (если первый), - (диапазон). Чтобы включить дефис как литерал: [-abc] или [abc-].
Предопределённые классы
| Класс | Эквивалент | Описание |
|---|---|---|
\d | [0-9] | Цифра |
\D | [^0-9] | Не цифра |
\w | [a-zA-Z0-9_] | Буква, цифра или подчёркивание |
\W | [^a-zA-Z0-9_] | Не «словесный» символ |
\s | [\t\n\r\f\v ] | Пробельный символ |
\S | [^\t\n\r\f\v ] | Не пробельный символ |
Примеры:
\d{3}-\d{2}-\d{2} — телефон вида 123-45-67
\w+@\w+\.\w+ — упрощённый email
\s+ — один или более пробельных символов
Точка (.)
Совпадает с любым символом, кроме \n (поведение меняется флагом s):
a.b — совпадёт с aXb, a1b, a.b; не совпадёт с ab, a\nb
.+ — один или более любых символов
Квантификаторы
Квантификаторы определяют количество повторений предшествующего элемента.
Основные квантификаторы
| Квантификатор | Значение | Пример |
|---|---|---|
* | 0 или более | go*gle → ggle, gogle, google... |
+ | 1 или более | go+gle → gogle, google... |
? | 0 или 1 | colou?r → color, colour |
{n} | Ровно n | \d{4} → ровно 4 цифры |
{n,} | n или более | [a-z]{3,} → 3+ строчных букв |
{n,m} | От n до m | \d{2,4} → от 2 до 4 цифр |
Жадные vs ленивые
По умолчанию квантификаторы жадные — захватывают максимум. Добавление ? делает их ленивыми — захватывают минимум:
Текст: <b>жирный</b> и <b>ещё жирный</b>
Жадный: <b>.+</b> → <b>жирный</b> и <b>ещё жирный</b> (одно совпадение)
Ленивый: <b>.+?</b> → <b>жирный</b> и <b>ещё жирный</b> (два совпадения)
| Жадный | Ленивый | Значение |
|---|---|---|
* | *? | 0 или более (минимум) |
+ | +? | 1 или более (минимум) |
? | ?? | 0 или 1 (предпочитает 0) |
{n,m} | {n,m}? | От n до m (предпочитает n) |
В PCRE (PHP, Java) доступны также притяжательные квантификаторы *+, ++, ?+ — работают как жадные, но не откатываются. Полезны для оптимизации.
Якоря и границы
Якоря обозначают позицию в строке, не совпадая с символами.
Начало и конец строки: ^ и $
^Hello — строка начинается с «Hello»
world$ — строка заканчивается на «world»
^\d{4}-\d{2}-\d{2}$ — строка целиком является датой
С флагом m якоря ^ и $ работают для каждой строки в многострочном тексте.
Граница слова: \b
\b — позиция между «словесным» (\w) и «несловесным» (\W) символом:
\bcat\b — слово «cat» целиком
совпадёт: «the cat sat»
не совпадёт: «concatenate», «catalog»
\bgo\w* — слова на «go»: go, gone, google
\B — позиция внутри слова (противоположность \b).
В Python и PHP также доступны \A и \Z — начало и конец всего текста, игнорируя флаг m.
Группы и обратные ссылки
Захватывающие группы
Круглые скобки группируют элементы и сохраняют совпавший текст:
(ab)+ — «ab», «abab», «ababab»...
(cat|dog) — «cat» или «dog»
(\d{2})\.(\d{2})\.(\d{4}) — дата: группа 1 — день, 2 — месяц, 3 — год
const m = '07.03.2026'.match(/(\d{2})\.(\d{2})\.(\d{4})/);
// m[1] = '07', m[2] = '03', m[3] = '2026'
Именованные группы
const m = '07.03.2026'.match(/(?<day>\d{2})\.(?<month>\d{2})\.(?<year>\d{4})/);
// m.groups.day = '07', m.groups.month = '03', m.groups.year = '2026'
В Python синтаксис отличается — (?P<name>...):
m = re.match(r'(?P<day>\d{2})\.(?P<month>\d{2})\.(?P<year>\d{4})', '07.03.2026')
m.group('year') # '2026'
Незахватывающие группы (?:...)
Группировка без сохранения — не сбивает нумерацию и работает быстрее:
(?:ab)+ — повторение «ab», группа не сохраняется
(?:http|https):// — протокол без захвата
(?:\d{1,3}\.){3}\d{1,3} — упрощённый IPv4
Обратные ссылки
\1, \2 ссылаются на текст, захваченный соответствующей группой:
(\w+)\s+\1 — повторяющееся слово: «the the», «is is»
(['"]).*?\1 — строка в одинаковых кавычках
<(\w+)>.*?</\1> — парный HTML-тег
// Нахождение дублей
'the the quick fox fox'.match(/\b(\w+)\s+\1\b/g);
// Замена с группами
'Иванов Иван'.replace(/(\S+)\s(\S+)/, '$2 $1'); // 'Иван Иванов'
Lookahead и Lookbehind
Lookaround-проверки задают условие впереди или позади текущей позиции, не включая проверяемый текст в результат.
Positive Lookahead (?=...)
\d+(?=\s?руб) — число перед «руб»: «500 руб.» → 500
\w+(?=\() — слово перед скобкой: «func(x)» → func
Negative Lookahead (?!...)
\d+(?!\s?руб) — число НЕ перед «руб»: «500 долл.» → 500
^(?!.*admin).*$ — строки без «admin»
Positive Lookbehind (?<=...)
(?<=\$)\d+ — число после $: «$100» → 100
(?<=class=")\w+ — значение после class=": «class="header"» → header
Negative Lookbehind (?<!...)
(?<!\$)\d+ — число без $ перед ним: «€100» → 100
(?<!un)happy — «happy» без «un»: happy → да, unhappy → нет
Комбинирование
(?<=<b>).*?(?=</b>) — содержимое между <b> и </b>
# Валидация пароля через множественные lookahead
^(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*]).{8,}$
Lookbehind доступен в JavaScript с ES2018. В Python он требует паттерн фиксированной длины. PCRE (PHP) снимает это ограничение частично.
Флаги
Флаги меняют поведение выражения: /pattern/flags (JS) или параметром функции (Python, PHP).
| Флаг | Имя | Действие |
|---|---|---|
g | Global | Все совпадения, а не только первое |
i | Case-insensitive | Без учёта регистра |
m | Multiline | ^/$ для каждой строки |
s | DotAll | Точка совпадает с \n |
u | Unicode | Полная поддержка Unicode |
x | Extended | Пробелы и комментарии в паттерне (Python, PHP) |
'banana'.match(/an/g); // ['an', 'an'] — все вхождения
/hello/i.test('HELLO'); // true
/^.$/u.test('😀'); // true — эмодзи как один символ
Флаг x позволяет писать читаемые паттерны:
pattern = re.compile(r'''
^ # начало строки
(?P<protocol>https?) # протокол
:// # разделитель
(?P<domain>[\w.-]+) # домен
(?P<path>/\S*)? # путь (опционально)
$ # конец строки
''', re.VERBOSE)
Сводная таблица эквивалентов:
| Флаг | JavaScript | Python | PHP |
|---|---|---|---|
g | /pattern/g | findall() / finditer() | preg_match_all() |
i | /pattern/i | re.IGNORECASE | /pattern/i |
m | /pattern/m | re.MULTILINE | /pattern/m |
s | /pattern/s | re.DOTALL | /pattern/s |
x | — | re.VERBOSE | /pattern/x |
Практические примеры
Проверить любой из приведённых паттернов можно в Regex Tester на reChecker — инструмент показывает совпадения в реальном времени и разбирает структуру выражения.
^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$
| Фрагмент | Значение |
|---|---|
[a-zA-Z0-9._%+-]+ | Локальная часть (до @) |
[a-zA-Z0-9.-]+ | Домен |
\.[a-zA-Z]{2,} | Точка + TLD (минимум 2 буквы) |
Российский телефон
^(\+7|8)\s?\(?\d{3}\)?\s?\d{3}[-\s]?\d{2}[-\s]?\d{2}$
Совпадёт: +7 (999) 123-45-67, +79991234567, 8(999)1234567, 8 999 123 45 67.
URL
^https?:\/\/[\w.-]+\.[a-z]{2,}(\/\S*)?$
Расширенная версия с поддержкой query-параметров:
^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$
IPv4-адрес
Наивный \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} пропустит 999.999.999.999. Корректный паттерн:
^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$
Разбор октета: 25[0-5] → 250–255, 2[0-4]\d → 200–249, [01]?\d\d? → 0–199.
Дата (дд.мм.гггг)
^(0[1-9]|[12]\d|3[01])\.(0[1-9]|1[0-2])\.(19|20)\d{2}$
Паттерн не проверяет логику (31 февраля пройдёт) — для полной валидации нужна программная логика.
Пароль
Минимум 8 символов, заглавная, строчная, цифра и спецсимвол:
^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[!@#$%^&*()_+\-=\[\]{}|;:,.<>?]).{8,}$
Каждый (?=...) — это отдельная проверка через lookahead, а .{8,} — основное условие длины.
HTML-теги
<([a-zA-Z][a-zA-Z0-9]*)\b[^>]*> — все открывающие теги
<title>(.*?)<\/title> — содержимое <title>
<[^>]+> — любой тег (для удаления)
'<p>Текст <b>жирный</b></p>'.replace(/<[^>]+>/g, '');
// 'Текст жирный'
Домен из URL
'https://www.example.com/page'.match(/^https?:\/\/(?:www\.)?([^\/\s:]+)/);
// [1] = 'example.com'
HEX-цвет
^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
Дубликаты слов
\b(\w+)\s+\1\b
Находит «и и», «на на» — полезно при вычитке текстов.
Числа из текста
-?\d+(?:[.,]\d+)?
Совпадёт: 42, -3.14, 1,5.
Множественные пробелы в один
'слишком много пробелов'.replace(/\s{2,}/g, ' ');
// 'слишком много пробелов'
Regex в разных языках
JavaScript
const regex = /\d+/g;
const regex2 = new RegExp('\\d+', 'g');
regex.test('abc123'); // true
'abc123def456'.match(/\d+/g); // ['123', '456']
'abc123def456'.matchAll(/\d+/g); // итератор совпадений (ES2020)
'abc123'.search(/\d+/); // 3
'abc123'.replace(/\d+/, 'NUM'); // 'abcNUM'
'a1b2c3'.split(/\d/); // ['a', 'b', 'c', '']
Особенности: нет флага x, lookbehind и именованные группы — с ES2018, matchAll — ES2020, флаг v — ES2024.
Python
import re
re.search(r'\d+', 'abc123') # Match (первое совпадение)
re.match(r'\d+', '123abc') # Match (только с начала строки)
re.fullmatch(r'\d+', '123') # Match (вся строка)
re.findall(r'\d+', 'a1 b2 c3') # ['1', '2', '3']
re.sub(r'\d+', 'N', 'a1 b2') # 'aN bN'
re.split(r'\s+', 'a b c') # ['a', 'b', 'c']
pattern = re.compile(r'(?P<year>\d{4})-(?P<month>\d{2})')
m = pattern.search('2026-03')
m.group('year') # '2026'
Особенности: raw-строки r'...', именованные группы (?P<name>...), re.match ≠ re.search, lookbehind фиксированной длины. Сторонний модуль regex снимает эти ограничения.
PHP
preg_match('/\d+/', 'abc123', $m); // $m[0] = '123'
preg_match_all('/\d+/', 'a1 b2 c3', $m); // $m[0] = ['1', '2', '3']
preg_replace('/\d+/', 'N', 'a1 b2 c3'); // 'aN bN cN'
preg_replace_callback('/\d+/', function ($m) {
return $m[0] * 2;
}, 'a1 b2 c3'); // 'a2 b4 c6'
preg_split('/[\s,]+/', 'one, two, three'); // ['one', 'two', 'three']
Особенности: PCRE-движок, разделители /pattern/flags, рекурсия (?R), условные паттерны (?(cond)yes|no), притяжательные квантификаторы.
Различия между языками
| Возможность | JavaScript | Python | PHP (PCRE) |
|---|---|---|---|
| Именованные группы | (?<name>...) | (?P<name>...) | Оба варианта |
| Lookbehind перем. длины | Нет | Нет | Частично |
| Рекурсия | Нет | Нет | (?R), (?1) |
| Verbose-режим | Нет | re.VERBOSE | Флаг x |
| Unicode по умолчанию | Нет (флаг u) | Да | Нет (флаг u) |
| Атомарные группы | Нет | Нет* | (?>...) |
| Притяжательные кванторы | Нет | Нет* | *+, ++, ?+ |
* Доступны через сторонний модуль regex.
Оптимизация и производительность
Катастрофический откат
Паттерн (a+)+b на строке aaaaaaaaaaaa (без b) заставляет движок перебирать экспоненциально растущее число комбинаций. Для 20 символов — более миллиона попыток. Опасные конструкции:
(a+)+ — вложенные квантификаторы
(a|aa)+ — перекрывающиеся альтернативы
(.*a){10} — жадный .* с повторением
Правила оптимизации
1. Избегайте вложенных квантификаторов — (a+)+b → a+b
2. Сужайте символьные классы:
<.*> — плохо, .* захватит слишком много
<[^>]*> — хорошо, отрицание конкретного символа
3. Якорите паттерны — ^ и $ позволяют движку быстрее отбросить несовпадения.
4. Компилируйте и переиспользуйте:
pattern = re.compile(r'\d{3}-\d{2}-\d{2}')
for line in lines:
if pattern.search(line):
process(line)
5. Используйте незахватывающие группы — (?:...) быстрее (...), если захват не нужен.
6. Выносите общие префиксы:
(?:Monday|Tuesday|Wednesday) → (?:Mon|Tues|Wednes)day
7. В PCRE используйте притяжательные квантификаторы — a++b вместо a+b предотвращает откат.
Для тестирования и отладки паттернов используйте Regex Tester на reChecker, а для быстрой генерации типовых выражений — AI Regex Generator.
Справочная таблица
Символы и классы
| Символ | Описание |
|---|---|
. | Любой символ (кроме \n без флага s) |
\d / \D | Цифра / не цифра |
\w / \W | Словесный символ / не словесный |
\s / \S | Пробельный / не пробельный |
\t \n \r | Табуляция, перевод строки, возврат каретки |
Якоря
| Символ | Описание |
|---|---|
^ / $ | Начало / конец строки |
\b / \B | Граница слова / не граница |
\A / \Z | Начало / конец текста (Python, PHP) |
Квантификаторы
| Жадный | Ленивый | Описание |
|---|---|---|
* | *? | 0 или более |
+ | +? | 1 или более |
? | ?? | 0 или 1 |
{n} | {n}? | Ровно n |
{n,} | {n,}? | n или более |
{n,m} | {n,m}? | От n до m |
Группы
| Синтаксис | Описание |
|---|---|
(abc) | Захватывающая группа |
(?:abc) | Незахватывающая группа |
(?<name>abc) | Именованная группа (JS, PHP) |
(?P<name>abc) | Именованная группа (Python, PHP) |
\1, $1 | Обратные ссылки (в паттерне / при замене) |
Lookaround
| Синтаксис | Описание |
|---|---|
(?=abc) | Впереди есть abc |
(?!abc) | Впереди нет abc |
(?<=abc) | Позади есть abc |
(?<!abc) | Позади нет abc |
Заключение
Регулярные выражения — навык, который окупается многократно. Одна строка regex способна заменить десятки строк процедурного кода. При этом важен баланс: слишком сложное выражение становится нечитаемым и хрупким. Если паттерн занимает больше двух строк, стоит задуматься о декомпозиции или использовании парсера.
Ключевые принципы:
- Начинайте с простого — усложняйте паттерн постепенно, проверяя на каждом шаге
- Тестируйте граничные случаи — пустые строки, длинные строки, неожиданные символы
- Используйте именованные группы — они делают паттерн самодокументируемым
- Следите за производительностью — избегайте катастрофического отката
- Не парсите HTML регулярными выражениями — для этого есть DOM-парсеры
Regex используются не только в коде приложений. Они лежат в основе серверных конфигураций — правила rewrite в .htaccess и Nginx строятся на регулярных выражениях. Умение составлять корректные паттерны напрямую помогает при настройке маршрутизации, редиректов и контроля доступа.