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

Регулярные выражения (Regex): Полное руководство

Регулярные выражения (Regex) — это мощный синтаксис, который позволяет сопоставлять строки с определенными шаблонами. Представьте их как продвинутый поиск текста, использующий квантификаторы, коллекции символов, специальные токены и группы захвата для создания чрезвычайно сложных поисковых шаблонов.

Regex необходим для таких задач, как:

  • Анализ вывода командной строки
  • Парсинг пользовательского ввода
  • Изучение логов серверов или программ
  • Работа с текстовыми файлами с согласованным синтаксисом (CSV и др.)
  • Чтение конфигурационных файлов
  • Поиск и рефакторинг кода

1. Шпаргалка по Regex (Cheat Sheet)​

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

ТокенОписание
a|bСоответствует либо "a", либо "b"
?Ноль или один раз
+Один или более раз
*Ноль или более раз
*?Ноль или более, но остановиться после первого совпадения
{N}Ровно N раз
{N, M}От N до M раз

Общие токены​

ТокенОписание
.Любой символ
\nПеренос строки
\tТабуляция
\sЛюбой пробельный символ (включая \t, \n и др.)
\SЛюбой непробельный символ
\wЛюбой "словный" символ (буквы A-Z, a-z, цифры 0-9 и _)
\WЛюбой символ, не являющийся "словным"
\bГраница слова (совпадает между символами)
\BНе граница слова
^Начало строки
$Конец строки
\\Обратный слеш (буквально "")

Коллекции шаблонов (Pattern Collections)​

ТокенОписание
[A-Z]Соответствует любой заглавной букве от "A" до "Z"
[a-z]Соответствует любой строчной букве от "a" до "z"
[0-9]Соответствует любой цифре
[asdf]Соответствует любому из символов "a", "s", "d" или "f"
[^asdf]Соответствует любому символу, кроме "a", "s", "d", "f"

Флаги​

ТокенОписание
gГлобальный поиск, найти все вхождения
mМногострочный поиск, $ и ^ работают для каждой строки в отдельности
iИгнорировать регистр

Группы​

ТокенОписание
(...)Захватывающая группа
(?:...)Незахватывающая группа
(?<name>...)Именованная группа захвата с именем "name"

Именованные обратные ссылки​

ТокенОписание
\k<name>Ссылка на именованную группу захвата "name" в запросе

Проверки (Lookahead и Lookbehind)​

ТокенОписание
(?!)Отрицательная проверка вперед (Negative lookahead)
(?=)Положительная проверка вперед (Positive lookahead)
(?<!)Отрицательная проверка назад (Negative lookbehind)
(?<=)Положительная проверка назад (Positive lookbehind)

2. Основы: Как выглядит регулярное выражение?​

В простейшем виде регулярное выражение выглядит как слово, которое вы ищете: /Test/ соответствует слову "Test".

Однако регулярные выражения могут быть гораздо сложнее. Например, шаблон номера телефона: ^(?:\d{3}-){2}\d{4}$

Шаблон: ^(?:\d{3}-){2}\d{4}$
Совпадение: "555-555-5555"
Нет совпадения: "555-abc-5555"

Более читаемая (но длинная) версия того же регулярного выражения: ^[0-9]{3}-[0-9]{3}-[0-9]{4}$


3. Как читать и писать регулярные выражения​

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

Квантификаторы определяют, сколько раз символ или группа должны быть найдены:

  • a|b: Соответствует либо "a", либо "b"
  • ?: Ноль или один раз
  • +: Один или более раз
  • *: Ноль или более раз
  • {N}: Ровно N раз
  • {N,}: N или более раз
  • {N,M}: От N до M раз
  • *?: Ноль или более, но остановиться после первого совпадения (ленивый поиск)

Пример: Hello{1,3}

Шаблон: Hello{1,3}
Совпадения: "Hello", "Helloo", "Hellooo"

Комбинированный пример: He?llo{2}

Шаблон: He?llo{2}
Совпадения: "Helloo", "Hlloo"

Жадный и ленивый поиск​

По умолчанию квантификаторы являются жадными — они захватывают как можно больше.

  • Hi+ соответствует "Hi" и "Hiiiiii".
  • Hi+? (ленивый) соответствует только "Hi", даже если дальше следуют другие "i".

Использование . (любой символ) with жадным/ленивым поиском:

  • H.*llo (жадный)
Строка: "Hellollollo"
Совпадение: "Hellollollo"
  • H.*?llo (ленивый)
Строка: "Hellollollo"
Совпадение: "Hello"

Коллекции шаблонов (Pattern Collections)​

Коллекции позволяют искать набор символов:

  • [A-Z]: Любая заглавная буква
  • [a-z]: Любая строчная буква
  • [0-9]: Любая цифра
  • [asdf]: Любой из символов "a", "s", "d", "f"
  • [^asdf]: Любой символ, кроме "a", "s", "d", "f"
  • [0-9A-Z]: Любая цифра или заглавная буква

Комбинирование с токенами: Вы можете включать токены в коллекции. Например, [A-Z\s] соответствует любой заглавной букве или пробельному символу.

Общие токены​

  • . : Любой символ
  • \n : Перенос строки
  • \t : Табуляция
  • \s : Любой пробельный символ (пробел, табуляция, перенос строки)
  • \S : Любой непробельный символ
  • \w : Любой "словный" символ (буквы A-Z, a-z, цифры 0-9 и _)
  • \W : Любой символ, не являющийся "словным"
  • \b : Граница слова (край между \w and \W). Совпадает между символами.
Шаблон: \b\w+\b
Строка: "This is a string"
Совпадения: "This", "is", "a", "string"
  • \B : Не граница слова
  • ^ : Начало строки
Шаблон: ^\w+
Строка: "This is a string"
Совпадение: "This"
  • $ : Конец строки
Шаблон: \w+$
Строка: "This is a string"
Совпадение: "string"
  • \\ : Обратный слеш (literal backslash)

Пример границ строк: $\s+ можно использовать для поиска всех пробельных символов между концом одной строки и началом следующей (полезно для минификации).

Экранирование символов​

Чтобы найти символы, которые имеют специальное значение в регулярных выражениях (например, ., *, \), используйте обратный слеш для их экранирования:

  • \\n соответствует строке "\n" буквально, а не переносу строки.

4. Использование в коде (примеры на PHP)​

Хотя большинство языков поддерживают регулярные выражения, синтаксис может немного отличаться. Вот основные реализации в PHP с использованием расширения PCRE:

Создание и поиск​

// preg_match возвращает 1, если совпадение найдено, и 0, если нет
preg_match('/[a-z]/', 'apple', $matches); // Возвращает 1, $matches[0] — это "a"
preg_match('/[a-z]/', '123', $matches); // Возвращает 0, $matches пуст

// preg_match_all находит все вхождения
preg_match_all('/[a-z]/', 'abc', $matches); // Возвращает 3, $matches[0] — это ["a", "b", "c"]

Замена строк​

function youSayHelloISayGoodbye(string $str): string {
// Находит Hello, hello, Hi, hi, Hey или hey
return preg_replace('/[Hh]ello|[Hh]i|[Hh]ey/', 'Goodbye', $str);
}
// Строка: "Hello, hi there!" -> Результат: "Goodbye, Goodbye there!"

Глобальный поиск в отличие от JavaScript​

В PHP preg_match находит первое вхождение, а preg_match_all — все. В отличие от флага /g в JavaScript, который привязан к объекту регулярного выражения, в PHP сами функции определяют, является ли поиск глобальным.

$str = "test1 test2 test3";
preg_match_all('/test\d/', $str, $matches);
// $matches[0] содержит ["test1", "test2", "test3"]

5. Флаги​

Флаги — это модификаторы, которые добавляются после последнего слеша:

  • g (Global): Найти все вхождения.
  • i (Case-insensitive): Игнорировать регистр.
  • m (Multiline): ^ и $ соответствуют началу/концу каждой строки.

6. Группы​

Группы определяются круглыми скобками и позволяют обрабатывать несколько символов как единое целое.

Захватывающие и незахватывающие группы​

  • (...): Захватывающая группа. Запоминает содержимое для дальнейшего использования (например, $1 при замене).
  • (?:...): Незахватывающая группа. Совпадает с содержимым, но не "запоминает" его.

Именованные группы захвата​

  • (?<name>...): Группа с именем "name".
  • Пример: /Testing (?<num>\d{3})/ -> Заменить на Hello $<num>.
Строка: "Testing 123"
Результат: "Hello 123"

Обратные ссылки (Backreferences)​

  • \k<name>: Соответствует тому же, что было захвачено в группе с именем "name".
  • Пример: /.*(?<name>James). \k<name>,.*/
Совпадение: "Hello there James. James, how are you doing?"
Нет совпадения: "Hello there James. Frank, how are you doing?"

Группы Lookahead и Lookbehind (Проверки)​

  • (?=...): Положительная проверка вперед (должно следовать).
  • (?!...): Отрицательная проверка вперед (НЕ должно следовать).
  • (?<=...): Положительная проверка назад (должно предшествовать).
  • (?<!): Отрицательная проверка назад (НЕ должно предшествовать).

Пример (Lookahead): /B(?!A)/

Строка: "BC" -> Совпадение: "B"
Строка: "BA" -> Нет совпадения

7. Продвинутые возможности PCRE (PHP)​

В PHP движок PCRE предоставляет еще более мощные возможности.

Атомарные группы (?>...)​

Запрещают возврат (backtracking) после того, как совпадение заблокировано. Предотвращают «катастрофический возврат».

Управление возвратом: (*SKIP)(*F)​

  • (*F): Принудительный провал совпадения.
  • (*SKIP): Пропускает до текущей позиции, если совпадение провалится позже.

Рекурсия (?R) или (?1)​

Позволяет сопоставлять вложенные структуры, например, сбалансированные скобки: /\((?:[^()]+|(?R))*\)/.


8. «Лучший трюк Regex в истории» (Исключение контекста)​

Шаблон: ИгнорируйЭто|ИгнорируйТо|НайдиЭто|(ЗахватиТоЧтоМнеНужно)

Пример: Найти Tarzan, но НЕ в двойных кавычках​

$regex = '/"[^"]*"|(\bTarzan\b)/';
$text = 'Tarzan likes Jane. "Tarzan is in quotes". Tarzan is free.';
preg_match_all($regex, $text, $matches);
$realMatches = array_filter($matches[1]);
// Результаты: ["Tarzan", "Tarzan"] (исключает тот, что в кавычках)

9. Пошаговое руководство: Создание сложного Regex​

Цель: Извлечь валидные E-mail адреса из HTML, но игнорировать те, что внутри тегов <code> или атрибутов href="...".

  1. Определите ИГНОР: <code>.*?<\/code> и href="[^"]*"
  2. Определите ПОИСК: [a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
  3. Объедините: <code>.*?<\/code>|href="[^"]*"|([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})

10. Элементы хорошего стиля Regex (AGRA)​

  1. A - Anchor (Якорь): Используйте ^, $, \A, \z.
  2. G - Greed (Жадность): Внимательно выбирайте * vs *?.
  3. R - Repeat (Повтор): Избегайте .*. Будьте конкретны (например, [^"]*).
  4. A - Atomic (Атомарность): Используйте (?>...) там, где возврат не нужен.

11. Функции Regex в PHP подробно​

PHP предоставляет несколько функций для работы с регулярными выражениями через расширение PCRE (Perl Compatible Regular Expressions).

preg_match против preg_match_all​

  • preg_match($pattern, $subject, &$matches): Ищет совпадение и возвращает 1, если оно найдено, 0, если нет, или false при ошибке. Останавливает поиск после первого совпадения.
  • preg_match_all($pattern, $subject, &$matches): Ищет все совпадения и возвращает количество найденных полных совпадений.

preg_replace и preg_filter​

  • preg_replace($pattern, $replacement, $subject): Заменяет все совпадения строкой замены.
  • preg_filter($pattern, $replacement, $subject): Аналогичен preg_replace, но возвращает результаты только там, где было найдено совпадение. Если входным данными является массив, он фактически фильтрует этот массив.

Разделители (Delimiters)​

В PHP шаблоны регулярных выражений должны быть заключены в разделители. Хотя / является наиболее распространенным, вы можете использовать любой не-алфавитно-цифровой символ (например, #, ~, !).

preg_match('#[a-z]#', 'apple'); // Валидно

12. Хорош ли ChatGPT в регулярных выражениях?​

Хотя ChatGPT и другие языковые модели отлично подходят для создания черновиков регулярных выражений, у них есть существенные ограничения:

  1. Dot-Star Soup: Они часто прибегают к неэффективным шаблонам типа .* вместо более специфичных классов символов, таких как [^"]*.
  2. Катастрофический возврат (Catastrophic Backtracking): Они могут создавать шаблоны, которые вызывают экспоненциальное время поиска при обработке несовпадающих строк.
  3. Синтаксические ошибки: Иногда они путают диалекты (например, используют специфичные для Python функции в контексте PHP).

Вердикт: Используйте ИИ для вдохновения, но всегда проверяйте шаблоны с помощью инструментов вроде regex101.com и проводите проверку кода для выражений, критичных к производительности.


Источники:​