Одна лишняя пара скобок способна полностью изменить регулярное выражение. Например, шаблон http|https:// выглядит так, будто ищет два протокола, но на самом деле первая ветка заканчивается после http. Поэтому строка http://example.com даст совпадение только с http, а не со всем началом адреса.
После базового синтаксиса я считаю группы самым полезным следующим шагом в изучении regex. Круглые скобки позволяют обращаться с несколькими символами как с единым блоком, выбирать варианты через |, делать целые фрагменты необязательными, повторять их и сохранять найденный текст. Главное не путать две роли скобок. Они одновременно группируют шаблон и, если используются как (...), создают захватывающую группу.
Зачем regex нужны круглые скобки
Начну с адресов. Допустим, в тексте встречаются оба протокола.
http://example.com https://example.com
Можно написать
(http|https)://
Оператор | означает выбор между альтернативами. Regex сначала пробует левую ветку, затем следующую, если первая не подходит. Сам оператор имеет очень низкий приоритет, поэтому без скобок выражение
http|https://
фактически состоит из двух самостоятельных вариантов.
http или https://
Такое поведение легко пропустить, потому что глазами человек обычно читает шаблон иначе.
Для двух протоколов есть более аккуратная запись.
https?://
Квантификатор ? относится только к букве s и означает «ноль или один раз». Поэтому подходят http:// и https://.
Скобки становятся действительно нужны, когда необязательным должен стать целый фрагмент. Например, адрес может содержать www..
https?://(?:www.)?example.com
Группа (?:www.)? означает, что весь фрагмент www. может встретиться один раз или отсутствовать.
Сравним с ошибочным вариантом.
https?://www.?example.com
Здесь ? управляет только точкой. Буквы www остаются обязательными.
Квантификатор можно применять и к повторяющемуся фрагменту.
(?:ab){3}
Regex ищет
ababab
а не три буквы b. Скобки превратили последовательность ab в единый элемент, к которому применяется {3}.
Тот же приём пригодится для идентификаторов.
[A-Z]{2}(?:-d{3}){2}
Под шаблон подходит строка
AB-123-456
Группа -d{3} повторяется целиком два раза.
Есть ещё одна особенность оператора |, которая регулярно удивляет новичков. В распространённых движках с поиском слева направо порядок альтернатив имеет значение.
cat|catalog
На строке catalog первая ветка уже успешно совпадает с cat. Regex не обязан выбирать самую длинную альтернативу. Если нужен полный вариант, порядок лучше изменить.
catalog|cat
Подробное поведение групп, альтернатив и обратных ссылок хорошо описывает справочник MDN. Базовые конструкции похожи во многих современных движках, но конкретный синтаксис ссылок и замены может различаться.
Захватывающие группы и ссылки на найденный текст
Обычные круглые скобки (...) не только объединяют выражение. Regex запоминает подстроку, которая попала внутрь. Такой блок называют захватывающей группой.
Возьму дату.
11.08.2026
Шаблон можно разбить на три группы.
(d{2}).(d{2}).(d{4})
| Группа | Результат |
|---|---|
1 |
11 |
2 |
08 |
3 |
2026 |
В JavaScript, Python и многих других движках обычные захватывающие группы нумеруются по расположению открывающих скобок слева направо. Нулевая группа в программных API обычно обозначает всё совпадение целиком, поэтому первая пара скобок получает номер 1.
Теперь найденные части можно переставлять. В JavaScript, .NET и многих текстовых редакторах поле замены поддерживает форму вроде
$3-$2-$1
и дата
11.08.2026
превращается в
2026-08-11
Здесь есть принципиальная деталь. $1 не является универсальным способом обращения к группе во всех контекстах.
Внутри самого регулярного выражения числовая обратная ссылка обычно выглядит так.
1
В строке замены JavaScript и ряда редакторов часто используется
$1
Python применяет другой синтаксис. Например, в re.sub() можно писать 1, но для сложных замен безопаснее и понятнее форма g<1>. Различия подробно описывает документация Python.
Поэтому я никогда не переношу строку замены между редактором, JavaScript, Python и другим инструментом вслепую. Сам regex может работать одинаково, а ссылки в поле замены окажутся другими.
Если скобки нужны только для структуры, лишний захват лучше не создавать. Для такого случая существует незахватывающая группа.
(?:...)
Сравним.
(http|https)://
Протокол сохраняется как отдельная группа.
(?:http|https)://
Альтернатива работает точно так же, но отдельного результата для http или https уже нет.
Незахватывающие группы уменьшают путаницу с номерами. Представим выражение
(d{2})-(d{2})-(d{4})
День находится в группе 1, месяц в группе 2, год в группе 3. Если позднее добавить в начало обычную группу
(date|created): (d{2})-(d{2})-(d{4})
все прежние номера съедут. День станет группой 2, месяц группой 3, год группой 4.
Если значение date|created не нужно сохранять, безопаснее написать
(?:date|created): (d{2})-(d{2})-(d{4})
Нумерация полезных групп останется прежней.
В больших выражениях ещё удобнее именованные группы. Здесь универсального синтаксиса уже нет. JavaScript использует конструкцию вроде (?<day>d{2}), Python традиционно использует (?P<day>d{2}). Имена снижают риск ошибок, когда шаблон меняют и номера групп сдвигаются.
Практика с URL и датами
Теперь соберу несколько конструкций в реальные шаблоны.
Для учебной задачи с адресами достаточно такого выражения.
b(https?)://([^/s]+)(?:/[^s]*)?
Для строки
Откройте https://example.com/articles/regex
полное совпадение содержит весь адрес, первая группа содержит протокол
https
а вторая группа содержит хост
example.com
Путь сгруппирован через (?:...), потому что отдельно сохранять его мне в данном примере не требуется.
Не стоит превращать такой учебный шаблон в «валидатор URL». Реальные URL допускают гораздо больше вариантов, включая порты, IPv6, кодирование символов, параметры, фрагменты, международные доменные имена и другие конструкции. Для разбора URL в программе лучше использовать штатный URL-парсер языка или платформы, а regex оставить для поиска адресоподобных фрагментов в контролируемом тексте.
С датами интереснее. Пусть документ содержит три формы.
11.08.2026 12/09/2026 13-10-2026
Самый очевидный шаблон выглядит так.
(d{2})[./-](d{2})[./-](d{4})
Символьный класс [./-] разрешает точку, слеш или дефис. Точку внутри такого класса экранировать не требуется. Дефис я поставил в конец, чтобы regex воспринимал его как обычный символ, а не как обозначение диапазона.
Проблема проявляется на странной строке.
11.08/2026
Шаблон тоже её примет, потому что оба разделителя проверяются независимо.
Если разделители должны совпадать, первый можно захватить и потребовать повторить его.
b(d{2})([./-])(d{2})2(d{4})b
Здесь группы распределены так.
(d{2})день, группа 1([./-])первый разделитель, группа 2(d{2})месяц, группа 32тот же текст, который совпал с группой 2(d{4})год, группа 4
Поэтому regex принимает
11.08.2026 11/08/2026 11-08-2026
но отвергает
11.08/2026
Именно здесь хорошо видна разница между группой и обратной ссылкой. Конструкция ([./-]) запоминает символ. Конструкция 2 не означает «ещё один символ из списка». Она требует повторить конкретный символ, который уже нашла группа 2.
Для унификации даты в среде с заменой через $1 можно использовать
$4-$3-$1
и получить
2026-08-11 2026-09-12 2026-10-13
Regex при этом проверяет только форму. Значение
99.99.2026
тоже соответствует конструкции d{2}. Можно построить гораздо более сложное выражение с диапазонами дней и месяцев, но я редко считаю такой подход оправданным. Если нужна настоящая проверка календарной даты, лучше сначала извлечь части регулярным выражением, а затем передать их библиотеке работы с датами.
Два неочевидных поведения групп
Первое связано с повторением захватывающей группы. Допустим, группа совпадает несколько раз.
(d{2}-)+
На строке
12-34-56-
нельзя рассчитывать, что обычный результат группы 1 превратится в список 12-, 34- и 56-. В JavaScript и Python доступное значение такой группы соответствует последнему успешному повторению, то есть 56-. Некоторые движки дают дополнительные API для истории захватов, но переносимого правила «повторил группу и получил массив» нет.
Поэтому для извлечения каждого элемента я либо запускаю отдельный глобальный поиск, либо перестраиваю шаблон и разбираю совпадение после поиска.
Вторая ловушка связана с альтернативами. Regex не ищет «самый умный» вариант среди веток.
a|ab
На строке abc распространённый движок с последовательным перебором альтернатив сначала успешно находит a. До ветки ab дело уже не доходит, если остальная часть выражения не заставляет движок вернуться назад.
Поэтому в альтернативе я обычно ставлю более специфичные варианты раньше.
https|http
Хотя для конкретного примера с протоколами запись https? всё равно проще.
Что означают круглые скобки в regex?
Круглые скобки объединяют несколько элементов регулярного выражения в единый фрагмент. Обычная конструкция
(...)также создаёт захватывающую группу и сохраняет совпавший текст.
Как работает оператор или в регулярных выражениях?
Оператор
|разделяет альтернативы. Выражениеcat|dogищетcatилиdog. Если альтернатива входит в более крупный фрагмент, её обычно заключают в скобки.
Чем захватывающая группа отличается от незахватывающей?
Конструкция
(...)сохраняет совпавшую подстроку. Конструкция(?:...)только группирует шаблон и не создаёт отдельный захваченный результат.
Что означает 1 в regex?
1обычно означает обратную ссылку на текст первой захватывающей группы внутри самого регулярного выражения.2ссылается на вторую группу и так далее.
Что означает $1 при замене текста?
Во многих редакторах, JavaScript и .NET запись
$1в строке замены вставляет содержимое первой захватывающей группы. Python использует другой синтаксис, поэтому формат замены нужно проверять для конкретного инструмента.
Как сделать целую часть regex необязательной?
Фрагмент нужно объединить в группу и поставить после неё
?. Например,(?:www.)?означает, что вся последовательностьwww.может присутствовать или отсутствовать.
Как найти даты с разными разделителями?
Шаблон
b(d{2})([./-])(d{2})2(d{4})bпринимает даты с точкой, слешем или дефисом и за счёт2требует одинаковый разделитель между всеми частями.
После групп regex перестаёт быть набором загадочных символов и становится небольшим конструктором. Скобки собирают элементы в блоки, | задаёт альтернативы, квантификаторы управляют целыми блоками, захватывающие группы сохраняют нужные части, а обратные ссылки позволяют использовать уже найденный текст ещё раз.
Для повседневной работы я бы сначала закрепил пять конструкций (...), (?:...), |, ? после группы и 1. С таким набором уже можно уверенно разбирать даты, URL, идентификаторы и другие структурированные строки. Следующий шаг в серии логично посвятить поиску и замене регулярными выражениями, потому что именно там захваченные группы превращаются из синтаксической особенности в инструмент массового редактирования.
