PCRE: регулярные выражения для mod_rewrite

В .htaccess регулярными выражениями (PCRE — тот же движок, что в PHP preg_*, но без ограничителей /…/ и почти без модификаторов) сопоставляется первый аргумент RewriteRule, второй аргумент RewriteCond, а также RedirectMatch, <FilesMatch>, SetEnvIf, Header edit. Регистронезависимость включается не модификатором (?i), а флагом [NC]. Как работает сам mod_rewrite (правила, переменные, флаги) — туториал «Как работает mod_rewrite»; прогнать свой паттерн на конкретном URL — тестер /rewrite/.

1. Зачем regex в .htaccess

Понимая регулярки, перестаёшь писать правила «методом тыка». Где именно в .htaccess встречается regex:

  • RewriteRule — первый аргумент (паттерн пути): RewriteRule ^post/([0-9]+)$ /post.php?id=$1 [L].
  • RewriteCond — второй аргумент, если он не начинается с < / > / = / -: RewriteCond %{HTTP_HOST} ^www\.example\.com$ [NC].
  • RedirectMatch (mod_alias) — RedirectMatch 301 ^/old/(.*)$ /new/$1.
  • <FilesMatch> / <DirectoryMatch> / <LocationMatch><FilesMatch "\.(sql|bak|log)$">.
  • SetEnvIf / SetEnvIfNoCaseSetEnvIfNoCase User-Agent "(nikto|sqlmap)" bad_bot.

2. Якоря и границы

  • ^ — начало строки.
  • $ — конец строки.
  • \b — граница слова (буква↔не-буква); удобно в фильтрах по User-Agent / Referer.

Важно: в RewriteRule тестируемая строка — это путь запроса без ведущего / (когда правило в .htaccess), поэтому пишут ^about$, а не ^/about$. В RewriteCond со значением %{REQUEST_URI} слэш, наоборот, присутствует. Примеры: ^$ (главная страница), ^index\.php$ (точно index.php), \.php$ (заканчивается на .php).

3. Символьные классы

  • [abc] — один из перечисленных символов; [^abc] — любой, кроме перечисленных.
  • Диапазоны: [a-z], [0-9], [A-Za-z0-9_-].
  • [^/] — любой символ, кроме слэша (частый приём «ровно один сегмент пути»).
  • Сокращения: \d = [0-9], \w = [A-Za-z0-9_], \s = пробельные; \D / \W / \S — наоборот.
  • . — любой символ, кроме перевода строки (вне класса! внутри [ ] точка — это просто точка).
  • Грабли: дефис - в классе ставь первым или последним ([-a-z], [a-z-]) либо экранируй — иначе это диапазон.

Примеры: ^[a-z0-9-]+$ (slug), ^[^/]+/[^/]+$ (ровно два сегмента пути).

4. Кванторы

  • * — 0 или больше; + — 1 или больше; ? — 0 или 1 (необязательно).
  • {3} — ровно 3; {2,} — 2 и больше; {2,4} — от 2 до 4.
  • По умолчанию кванторы жадные — берут как можно больше; ленивые *? +? ?? — как можно меньше (нужны редко — «до первого /» обычно делают через [^/]+).

Примеры: .* (что угодно, в т.ч. пусто), .+ (что угодно непустое), [^/]+ (один непустой сегмент), [0-9]{1,6} (число до 6 цифр), /?$ (необязательный завершающий слэш).

5. Группы и захваты

( … ) — группа с захватом; куски доступны в подстановке RewriteRule как $1, $2, … (нумерация по открывающей скобке слева направо), а от последнего сработавшего RewriteCond — как %1, %2, …. (?: … ) — группировка без захвата (когда скобки нужны только для квантора или альтернативы и не хочется сбивать нумерацию $N). Альтернатива внутри группы: (jpe?g|png|gif|webp).

  • Вложенные группы нумеруются по открывающим скобкам: ^((a)(b))$ даёт $1=ab, $2=a, $3=b.
  • $0 в RewriteRule — вся совпавшая строка.
  • Не нужен захват — пиши (?:…), чтобы не сбивать номера $N.
.htaccessкопировать
# /post/123  ->  /post.php?id=123
RewriteRule ^post/([0-9]+)$ /post.php?id=$1 [L]

# /ru/about  ->  /about?lang=ru
RewriteRule ^([a-z]{2})/(.*)$ /$2?lang=$1 [L,QSA]
.htaccessкопировать
# ?id=42  ->  захват из query-строки через RewriteCond
RewriteCond %{QUERY_STRING} (^|&)id=([0-9]+)(&|$)
RewriteRule ^item$ /item.php?id=%2 [L]

6. Экранирование

Метасимволы . ^ $ * + ? ( ) [ ] { } | \ имеют спецзначение; чтобы матчить их буквально, ставь \ перед символом.

  • \. — точка (самое частое: \.html$, ^www\.example\.com$, \.(jpg|png)$).
  • \? — буквальный вопрос (но в RewriteRule query-строки нет — см. раздел 7).
  • \/ — слэш (в PCRE без ограничителей экранировать не обязательно, но многие пишут \/ по привычке — это не ошибка).
  • \\ — обратный слэш.
  • Грабли: причина №1 «правило ловит лишнее» — забытый \ перед .: ^index.php$ совпадёт и с indexXphp; пишите ^index\.php$. В подстановке RewriteRule (правая часть) regex-экранирование не нужно — там обычный текст с $N/%N.

7. Особенности в RewriteCond и переменных %{…}

  • Тестовая строка RewriteCond — это подставленное значение переменной: %{HTTP_HOST}, %{REQUEST_URI}, %{QUERY_STRING}, %{HTTP_USER_AGENT}, %{HTTP_REFERER}, %{HTTPS} и т.д. (полный список — туториал «Как работает mod_rewrite»); regex применяется уже к этому значению.
  • Query-string матчат только через RewriteCond %{QUERY_STRING} … — в паттерн RewriteRule query не попадает; для сохранения исходного query при подстановке — флаг [QSA].
  • RewriteCond поддерживает не только regex: -f / -d / -s / -l (файл / каталог / непустой файл / симлинк существует), < / > / = / <= / >= (лексикографическое сравнение строк), expr=… (выражения; онлайн-тестер их не эмулирует).
  • ! перед паттерном — отрицание (!^www\.); [NC] — без учёта регистра; [OR] — логическое ИЛИ со следующим RewriteCond (иначе подряд идущие условия — это И).
.htaccessкопировать
# редирект на без-www, работает регистронезависимо
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

8. Модификаторы PCRE: что есть и чего нет

  • Есть: регистронезависимость — но не модификатором (?i) и не суффиксом /i, а флагом [NC] у RewriteRule / RewriteCondSetEnvIfNoCase вместо SetEnvIf).
  • Нет (по умолчанию не нужны): /m (строка и так одна), /s (. и так не встречает \n), /x (расширенный режим), /u (Unicode / \p{…}) — кириллицу и не-ASCII в путях обычно матчат как %D0%…-последовательности или просто [^/]+.
  • Поддерживается, но в .htaccess редко: (?:…) (без захвата — используется), (?i) (инлайн-флаг — работает, но привычнее [NC]), \b (граница слова), lookahead (?=…) / (?!…), lookbehind (?<=…) / (?<!…), обратные ссылки \1 внутри паттерна — PCRE-движок Apache всё это умеет.
.htaccessкопировать
# всё, что НЕ начинается с api/ — переписываем во фронт-контроллер
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(?!api/).*$ index.php [L]

9. Готовые шаблоны

  • ^$ — главная страница (пустой путь).
  • ^([^/]+)/?$ — один сегмент пути, опц. завершающий слэш — страница верхнего уровня.
  • ^([a-z0-9-]+)/([a-z0-9-]+)/?$категория/товар из slug-ов → $1, $2.
  • ^([0-9]+)$ / ([0-9]+)$ — числовой ID (в начале / в конце).
  • \.(jpe?g|png|gif|webp|svg)$ [NC] — файлы-картинки (анти-хотлинк, кэш).
  • ^(en|ru|de)/(.*)$ — языковой префикс + остаток пути → $2?lang=$1.
  • ^(.*[^/])$ → подстановка $1/ — добавить завершающий слэш, если его нет.
  • ^assets/.*\.(css|js)$ — статика в /assets/ (исключить из rewrite, поставить кэш).
  • ^(?!api/).*$ — всё, кроме api/… (lookahead).
  • ^old-section/(.*)$/new-section/$1 — перенос целого раздела (301).

Любой из этих паттернов прогоните на конкретном URL в тестере RewriteRule или в тестере регулярных выражений — увидите, что попало в $1/$2, и разбор паттерна. Готовые целые блоки — в рецептах и генераторе.

10. Проверить и почитать дальше