Академия mod_rewrite. Урок 4: регулярные выражения на практике
Регулярные выражения — сердце mod_rewrite. Без их понимания правила пишутся «методом тыка». В этом уроке разберём PCRE на практике: от якорей до negative lookahead, с готовыми паттернами и тестером regex для проверки.
Урок 4 из 5 · ← предыдущий | следующий → · оглавление курса
Где встречается regex в .htaccess
RewriteRule— первый аргумент: паттерн пути запроса.RewriteCond— второй аргумент: паттерн для проверки значения переменной.RedirectMatch— аналог Redirect с regex-паттерном.<FilesMatch>,<DirectoryMatch>,<LocationMatch>— контейнеры с паттерном.SetEnvIf/SetEnvIfNoCase— условия на заголовки.
Движок PCRE — тот же, что в PHP preg_*, но без ограничителей (нет /…/) и почти без модификаторов — регистронезависимость включается флагом [NC], а не (?i).
Якоря, точка и символьные классы
Якоря:
^— начало строки. В паттернеRewriteRuleпуть уже без ведущего/, поэтому^blog/— «строка начинается с blog/».$— конец строки. Без него паттерн^blogсработает и наblog, и наblog/news, и наblog/anything/deep.
Точка . — любой символ (кроме перевода строки). Самый частый источник ошибок: паттерн ^index.php$ без экранирования сработает и на indexXphp.
Символьные классы [...]:
[a-z]— буква от a до z.[a-z0-9-]— буква, цифра или дефис — типичный класс для slug.[^/]— любой символ, кроме слэша. Очень частый приём для «один сегмент пути без вложенности».\d— цифра (эквивалент[0-9]).\w— буква, цифра или подчёркивание.\s— пробельный символ.
Кванторы: *, +, ?, {n,m}
*— ноль или более повторений.+— одно или более (обязательно хотя бы одно).?— ноль или одно (делает предыдущее необязательным). Часто используется для опционального слэша:/?$— «с или без слэша в конце».{n,m}— от n до m повторений.{4}— ровно 4,{2,5}— от 2 до 5.
Жадность: по умолчанию кванторы жадные — захватывают максимально возможное. (.*) захватит всё до конца строки. Для «ленивого» (минимального) захвата — .*?.
Важное различие: (.*) — «всё, включая слэши», а ([^/]+) — «всё кроме слэша» (один сегмент пути). Второй вариант надёжнее для URL-сегментов.
Группы захвата и бэкрефы
( ) — группа захвата. Сопоставленное содержимое доступно как $1, $2, … в подстановке RewriteRule. Нумерация — слева направо, по открывающей скобке.
(?:...) — группа без захвата. Для группировки альтернатив без увеличения счётчика захватов.
Альтернатива: (en|ru|de) — «en, ru или de». Например, языковой префикс:
RewriteEngine On RewriteRule ^(en|ru|de)/(.*)$ /index.php?lang=$1&path=$2 [L,QSA]
Здесь $1 — язык, $2 — остаток пути. $0 — весь сматченный путь.
Вложенные группы: ^(a(b)c)$ — $1 = abc, $2 = b.
Экранирование метасимволов
Метасимволы PCRE: . ^ $ * + ? ( ) [ ] { } | \. Если они нужны как буквальные символы — экранируйте обратным слэшем.
Самая частая ошибка — неэкранированная точка в паттерне:
^index.php$— сработает наindex.php,indexXphp,index-php(точка = любой символ).^index\.php$— только наindex.php(точка буквальная).
В подстановке RewriteRule экранирование не нужно — там нет regex. Экранируют только в паттерне и в паттерне RewriteCond.
[NC] вместо модификатора /i
В mod_rewrite нет модификатора /i как в PHP. Регистронезависимость включается флагом [NC] в RewriteRule или в RewriteCond:
# Поймать и .JPG, и .jpg, и .Jpg RewriteRule \.(jpe?g|png|webp)$ - [NC,F]
Также нет модификаторов /m (многострочный), /s (точка матчит перевод строки), /x (игнорировать пробелы и комментарии), /u (UTF-8). Пути URL — однострочные ASCII-последовательности, поэтому это ограничение редко критично.
Готовые паттерны
# Главная страница (пустой путь) ^$ # Один сегмент пути (slug) ^([a-z0-9-]+)/?$ # Категория / товар ^([a-z0-9-]+)/([a-z0-9-]+)/?$ # Числовой ID ^post/(\d+)/?$ # Год и slug в блоге: /blog/2024/my-post ^blog/(\d{4})/([a-z0-9-]+)/?$ # Файлы изображений \.(jpe?g|png|gif|webp|svg)$ [NC] # CSS и JS \.(css|js)$ # Языковой префикс + остаток пути ^(en|ru|de)/(.*)$ # Всё, кроме путей, начинающихся с api/ (negative lookahead) ^(?!api/).*$
Паттерн ^(?!api/).*$ — negative lookahead: «строка не начинается с api/». Используется, когда нужно применить правило ко всему кроме определённого префикса.
Попробуй сам
Откройте тестер регулярных выражений — вставьте паттерн и несколько тестовых строк (каждая на новой строке). Вы увидите, какие строки сматчились и какие группы захвата ($1, $2) были извлечены. Там же есть «Разбор паттерна» — построчное объяснение каждого элемента regex.
Упражнения
-
Напишите паттерн, который ловит
/blog/2024/my-postи захватывает год и слаг в отдельные группы.
Подсказка:^blog/(\d{4})/([a-z0-9-]+)/?$.
Проверьте в тестере regex. -
Напишите паттерн для всех файлов
.cssи.js.
Подсказка: альтернатива в конце строки.
Проверьте в тестере regex. -
Напишите паттерн «любой путь, кроме начинающихся с
admin/».
Подсказка: negative lookahead^(?!admin/).*$.
Проверьте в тестере regex. -
Почему паттерн
^index.php$без экранирования точки — потенциальный баг?
Подсказка: точка в regex — любой символ, в том числеXили-.
Что дальше
В уроке 5 — финальном — соберём всё вместе: правильный порядок правил в реальном .htaccess, защита от петель, фронт-контроллер, полный пример и пошаговая отладка.