Академия 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». Например, языковой префикс:

.htaccessкопировать
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:

.htaccessкопировать
# Поймать и .JPG, и .jpg, и .Jpg
RewriteRule \.(jpe?g|png|webp)$ - [NC,F]

Также нет модификаторов /m (многострочный), /s (точка матчит перевод строки), /x (игнорировать пробелы и комментарии), /u (UTF-8). Пути URL — однострочные ASCII-последовательности, поэтому это ограничение редко критично.

Готовые паттерны

.htaccessкопировать
# Главная страница (пустой путь)
^$

# Один сегмент пути (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.

Упражнения

  1. Напишите паттерн, который ловит /blog/2024/my-post и захватывает год и слаг в отдельные группы.
    Подсказка: ^blog/(\d{4})/([a-z0-9-]+)/?$.
    Проверьте в тестере regex.
  2. Напишите паттерн для всех файлов .css и .js.
    Подсказка: альтернатива в конце строки.
    Проверьте в тестере regex.
  3. Напишите паттерн «любой путь, кроме начинающихся с admin/».
    Подсказка: negative lookahead ^(?!admin/).*$.
    Проверьте в тестере regex.
  4. Почему паттерн ^index.php$ без экранирования точки — потенциальный баг?
    Подсказка: точка в regex — любой символ, в том числе X или -.

Что дальше

В уроке 5 — финальном — соберём всё вместе: правильный порядок правил в реальном .htaccess, защита от петель, фронт-контроллер, полный пример и пошаговая отладка.