Шпаргалка Regex: метасимволы, группы и lookaround (полный справочник)
Регулярное выражение — это маленький язык шаблонов для поиска по тексту: \d+ значит «одна или более цифр», ^Error — «строка, начинающаяся с Error». Этим всё и ограничивается. Шпаргалка по регулярным выражениям (regex) собирает синтаксис на одной прокручиваемой странице: метасимволы, квантификаторы, якоря, группы, lookaround, флаги — и 15+ готовых паттернов под JavaScript и Python.
Текст написан для разработчиков, которые знают, что такое строка, и хотят справочник, а не экскурсию. Если нужны только символы — листайте сразу к таблице быстрого справочника. Если regex у вас хоть раз подвешивал сервер — читайте разделы про lookaround и подводные камни.
1. Что такое regex и почему он по-прежнему нужен в 2026 году
Regex — это паттерн, скомпилированный в конечный автомат: автомат сканирует строку и либо находит совпадение, либо отказывает. Грамматика небольшая, а применений у неё много.
ИИ набросает паттерн за секунду, но три задачи по-прежнему делаются вручную:
- Разбор логов. Десять миллионов строк access-логов
nginx, нужны все запросы с кодом 5xx от конкретного user agent. Regex из 40 символов черезgrep -Eотработает за секунды; вызов LLM на каждую строку — нет. - Валидация форм и полей. Номера телефонов, почтовые индексы, ISO timestamp, лицензионные ключи. Паттерн живёт рядом с полем ввода и срабатывает на каждое нажатие клавиши в браузере.
- Массовый поиск и замена. Рефакторинг тысячи файлов, где нужно захватить имя и переиспользовать его.
sed,ripgrep, команда «Заменить в файлах» в редакторе — все они изначально говорят на regex.
Для JSON-половины того же набора инструментов есть отдельная шпаргалка по jq для командной строки.
1.1 Как читать regex-паттерн (краткое введение в регулярные выражения за 5 секунд)
Большинство паттернов проще читать слева направо, по одному токену. Возьмём для примера ^[A-Z]\w+\d{2,4}$:
^привязывает совпадение к началу строки.[A-Z]совпадает ровно с одной заглавной буквой.\w+совпадает с одним или более символами слова.\d{2,4}совпадает с числом цифр от двух до четырёх.$привязывает к концу строки.
Навык в том, чтобы читать сначала якоря, потом классы символов, и только потом квантификаторы.
2. Таблица быстрого справочника
Ради этого раздела сюда обычно и приходят. Копируйте нужное.
Метасимволы
| Паттерн | Что соответствует |
|---|---|
. | Любой символ, кроме перевода строки (или любой символ с флагом s/dotall) |
\d | Цифра ([0-9] или все Unicode-цифры с флагом u) |
\D | Не-цифра |
\w | Символ слова ([A-Za-z0-9_]) |
\W | Несимвол слова |
\s | Любой пробельный символ (пробел, табуляция, перевод строки, …) |
\S | Любой непробельный символ |
Квантификаторы
| Паттерн | Что соответствует |
|---|---|
* | 0 или более (жадный) |
+ | 1 или более (жадный) |
? | 0 или 1 (жадный) |
{n} | Ровно n раз |
{n,m} | От n до m раз |
{n,} | n или более раз |
*?, +?, ??, {n,m}? | Ленивые версии каждого квантификатора |
Якоря
| Паттерн | Что соответствует |
|---|---|
^ | Начало строки (или начало строки текста с флагом m) |
$ | Конец строки (или конец строки текста с флагом m) |
\b | Граница слова |
\B | Не-граница слова |
\A | Абсолютное начало строки (Python) |
\Z | Абсолютный конец строки (Python) |
Классы символов
| Паттерн | Что соответствует |
|---|---|
[abc] | Любой из a, b, c |
[^abc] | Всё, кроме a, b, c |
[a-z] | Любая строчная буква |
[0-9] | Любая цифра |
\p{L} | Любая Unicode-буква (флаг u в JS, по умолчанию в Python re) |
Группы
| Паттерн | Что соответствует |
|---|---|
(...) | Захватывающая группа |
(?:...) | Незахватывающая группа |
(?<name>...) | Именованный захват (JS ES2018+); Python использует (?P<name>...) |
\1, \2 | Обратная ссылка на группу 1, 2 |
Lookaround
| Паттерн | Что соответствует |
|---|---|
(?=...) | Положительный lookahead |
(?!...) | Отрицательный lookahead |
(?<=...) | Положительный lookbehind |
(?<!...) | Отрицательный lookbehind |
Флаги
| Флаг | Эффект |
|---|---|
i | Без учёта регистра |
m | Multiline: ^ и $ совпадают для каждой строки |
s | Dotall: . совпадает с переводами строк |
g | Global (JS) — найти все совпадения |
u | Режим Unicode |
y | Sticky (JS) — привязать к lastIndex |
3. Метасимволы и классы символов
3.1 Литералы и специальные символы
Большинство символов — это литералы. 12 метасимволов, которые нужно экранировать, чтобы они означали сами себя:
. ^ $ * + ? ( ) [ ] { } | \
Забытое экранирование . — самая частая ошибка в regex. \. совпадает с литеральной точкой. Внутри класса символов [.] тоже совпадает с литеральной точкой: большинство метасимволов теряют свою силу внутри [...], кроме ], \, ^ (когда стоит первым) и - (в середине).
3.2 Сокращения для символов
Сокращённые классы выглядят просто ровно до встречи с Unicode:
// JavaScript — без флага u, \d работает только с ASCII
/\d/.test('5'); // true
/\d/.test('٥'); // false (арабо-индийская цифра)
/\d/u.test('٥'); // false — даже с u, \d остаётся ASCII в JS
/\p{N}/u.test('٥'); // true — \p{N} это Unicode-осведомлённый класс цифр
# Python — модуль re по умолчанию трактует \d как Unicode
import re
re.match(r'\d', '٥') # <Match span=(0, 1)>
re.match(r'(?a)\d', '٥') # None — (?a) форсирует ASCII
Пока на входе только английский ASCII, \d и [0-9] взаимозаменяемы. Стоит пользователю вставить имя с диакритикой — и вместо \w уже нужен \p{L}.
3.3 Пользовательские классы символов
// JavaScript
/[A-Za-z][A-Za-z0-9_-]{2,29}/.test('valid_handle-1'); // true
// Отрицание и диапазоны вместе
/[^aeiou\s]/g // любой не-гласный, не-пробельный символ
Для Unicode-категорий \p{L} означает «любая буква», \p{N} — «любое число», \p{Script=Han} — «любой иероглиф Han». JavaScript требует флаг u; Python поддерживает \p{...} только через PyPI-пакет regex, в стандартном re этого нет.
Если вы работаете в командной строке, вам также могут встретиться POSIX-классы символов:
| POSIX-класс | Что совпадает | ASCII-эквивалент |
|---|---|---|
[[:alpha:]] | буквы | [A-Za-z] |
[[:digit:]] | цифры | [0-9] (\d) |
[[:alnum:]] | буквы и цифры | [A-Za-z0-9] |
[[:space:]] | пробельные символы | \s |
[[:upper:]] | заглавные буквы | [A-Z] |
[[:lower:]] | строчные буквы | [a-z] |
POSIX-классы работают в grep -E, sed -E. В JavaScript и в Python re они не работают — используйте \d, \s, \w.
4. Квантификаторы: жадные и ленивые
4.1 Базовые квантификаторы
/a*/.exec('aaab') // ['aaa'] — 0 или более
/a+/.exec('aaab') // ['aaa'] — 1 или более
/a?/.exec('aaab') // ['a'] — 0 или 1
/a{2,3}/.exec('aaaab') // ['aaa'] — от 2 до 3
4.2 Жадные и ленивые
По умолчанию квантификаторы жадные: они захватывают сколько могут, а потом отступают назад, пока весь паттерн не сойдётся. Добавьте ?, и квантификатор станет ленивым.
const html = '<p>one</p><p>two</p>';
html.match(/<p>.*<\/p>/)[0]; // '<p>one</p><p>two</p>' (жадный съедает оба)
html.match(/<p>.*?<\/p>/)[0]; // '<p>one</p>' (ленивый останавливается на первом)
Когда извлекаете теги или строки в кавычках, почти всегда нужна именно ленивая версия. Ещё лучше вообще обойтись без . и взять класс с отрицанием: <p>[^<]*</p> работает быстрее, чем <p>.*?</p>, потому что движку некуда возвращаться.
4.3 Катастрофический возврат
Так regex подвешивает сервер. Вложите один квантификатор в другой с пересекающимися совпадениями, и движок прошарит экспоненциальное число путей, прежде чем сдаться.
// Не делайте так
/(a+)+b/.test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!'); // выполняется секундами
Для 41 a, за которыми идёт !, движок перебирает порядка 2^41 точек разбиения, прежде чем решит, что b отсутствует. Три способа исправления:
- Уплощите паттерн:
/a+b/делает ту же работу без вложенности. - Используйте атомарную группу (Python
regex, PCRE, Java, Ruby):(?>a+)+b— после совпадения сa+движок не возвращается в неё назад. - Смените движок:
regexpв Go, RE2 и crateregexдля Rust используют NFA с линейным временем и по своей конструкции не способны катастрофически откатываться.
JavaScript и Python re оба откатываются и в стандартной библиотеке атомарных групп не имеют (PyPI-пакет regex для Python их добавляет). Если длина ввода под вашим контролем — это нормально; если ввод приходит от пользователя, сначала проверяйте длину или заранее компилируйте паттерн против RE2.
5. Якоря и границы слов
5.1 ^ и $
По умолчанию ^ соответствует началу всего ввода, а $ — его концу. С флагом m (multiline) они превращаются в начало и конец каждой строки текста:
const log = 'INFO start\nERROR boom\nINFO done';
log.match(/^ERROR.*/); // null — однострочный режим, ^ совпадает только с индексом 0
log.match(/^ERROR.*/m); // ['ERROR boom']
5.2 \b и \B
\b — утверждение нулевой ширины: оно срабатывает на позиции между символом слова (\w) и не-символом слова. Удобно для поиска по целым словам:
/\bcat\b/.test('the cat sat'); // true
/\bcat\b/.test('concatenate'); // false
Границы слов определяются через \w, а он по умолчанию работает только с ASCII. В китайском, японском и корейском тексте между словами нет пробелов, и \b границ слов там не находит. Понадобится токенизатор (jieba, MeCab) — до regex, а не вместо него.
5.3 Многострочный режим
import re
text = "INFO ok\nERROR fail\nINFO done\n"
re.findall(r'^ERROR.*$', text) # []
re.findall(r'^ERROR.*$', text, re.MULTILINE) # ['ERROR fail']
В JavaScript то же самое пишется как text.match(/^ERROR.*$/gm). Скомбинируйте m и g, чтобы получить каждую совпадающую строку.
6. Группы, захват и обратные ссылки
6.1 Захватывающие группы
Скобки выполняют сразу две роли: группируют подпаттерны для квантификаторов и сохраняют совпадение для дальнейшего использования.
'2026-05-13'.match(/(\d{4})-(\d{2})-(\d{2})/);
// ['2026-05-13', '2026', '05', '13', index: 0, ...]
Группы нумеруются слева направо по их открывающей скобке, начиная с 1.
6.2 Незахватывающие группы
Когда нужна только группировка без захвата — берите (?:...). Так быстрее, и нумерация остальных групп не съезжает:
/(?:https?):\/\/(\S+)/.exec('see https://go-tools.org');
// ['https://go-tools.org', 'go-tools.org']
// — протокол сгруппирован, но не захвачен; группа 1 это хост
6.3 Именованные группы
Имена у групп делают паттерн читаемее и не ломают код при рефакторинге.
// JavaScript (ES2018+)
const m = '2026-05-13'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
m.groups.year; // '2026'
# Python — обратите внимание на синтаксис (?P<...>)
import re
m = re.match(r'(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})', '2026-05-13')
m.group('year') # '2026'
6.4 Обратные ссылки
С обратной ссылкой более поздняя часть паттерна повторяет то, что уже совпало с предыдущим захватом.
// Найти любой символ, который повторяется подряд
'bookkeeper'.match(/(\w)\1/g); // ['oo', 'kk', 'ee']
// Сопоставить парные HTML-теги по имени
const tag = /<(\w+)>(.*?)<\/\1>/;
'<b>bold</b>'.match(tag);
// ['<b>bold</b>', 'b', 'bold']
В Python \1 работает и в паттерне, и в замене; именованные ссылки в паттерне пишутся как (?P=name), а в заменах re.sub — как \g<name>.
7. Lookaround: lookahead и lookbehind
Lookaround — утверждения нулевой ширины. Они проверяют условие, не потребляя символов, поэтому их можно связывать в цепочки.
7.1 Lookahead
// Пароль: не менее 8 символов, одна цифра, одна заглавная, одна строчная
const strong = /^(?=.*\d)(?=.*[A-Z])(?=.*[a-z]).{8,}$/;
strong.test('Hunter2!'); // true
strong.test('hunter2!'); // false — нет заглавных
// Отрицательный lookahead — имена файлов, которые не .tmp
/^[\w-]+(?!\.tmp$)\.[a-z]+$/.test('report.csv'); // true
7.2 Lookbehind
Lookbehind работает в зеркальную сторону: проверяет то, что стоит перед текущей позицией.
// Извлечь цену после символа валюты — оставить число, отбросить $
'price: $42.50'.match(/(?<=\$)\d+(\.\d+)?/); // ['42.50', '.50']
// Отрицательный lookbehind — совпасть с Bond, но не с James Bond
'Mr. Bond'.match(/(?<!James )Bond/); // ['Bond']
'James Bond'.match(/(?<!James )Bond/); // null
7.3 Lookbehind в JavaScript и в Python
Одно из немногих мест, где два движка расходятся достаточно, чтобы при переносе паттерн просто ломался.
| Движок | Длина lookbehind |
|---|---|
| JavaScript (V8, SpiderMonkey, JSC 16.4+) | Переменной ширины с ES2018. (?<=\d+) допустимо. |
Python stdlib re | Только фиксированной ширины. (?<=\d+) вызывает error: look-behind requires fixed-width pattern. |
PyPI-пакет Python regex | Поддерживается переменная ширина. import regex; regex.search(r'(?<=\d+)abc', '12abc'). |
Обходной путь в Python: переписать lookbehind с известным повтором ((?<=\d{3})) либо захватить префикс и отрезать его уже после совпадения.
8. Флаги и модификаторы
8.1 i — без учёта регистра
/error/i.test('FATAL ERROR'); // true
re.search(r'error', 'FATAL ERROR', re.IGNORECASE) # <Match span=(6, 11)>
8.2 m и s
Флаг m превращает ^ и $ в построчные якоря. Флаг s (dotall) разрешает . совпадать с переводом строки. Эти два флага независимы, и при необходимости их спокойно ставят вместе.
/<script>(.*?)<\/script>/s.exec('<script>\nalert(1)\n</script>')[1];
// '\nalert(1)\n' — без s точка отказалась бы от переводов строк
8.3 g — глобальный
В JavaScript флаг g меняет API, а не само совпадение. Без g метод String.match возвращает группы захвата; с g — каждое совпадение целиком, без групп. Чтобы сохранить группы по всем совпадениям, используйте matchAll.
const text = 'a=1 b=2 c=3';
text.match(/(\w)=(\d)/); // первое совпадение с группами
text.match(/(\w)=(\d)/g); // ['a=1', 'b=2', 'c=3'] — без групп
[...text.matchAll(/(\w)=(\d)/g)]; // каждое совпадение, с группами
В Python флага g нет: глобальные варианты — это re.findall, re.finditer и re.sub.
8.4 u — Unicode и \p{...}
// Совпасть с любым иероглифом Han (китайский, японский kanji)
/\p{Script=Han}+/gu.test('Hello 世界'); // true
// Совпасть с эмодзи (extended pictographic)
/\p{Extended_Pictographic}/u.test('👋'); // true
В Python Unicode включён по умолчанию; эквивалент для диапазона Han — re.findall(r'[一-鿿]+', text). Полные Unicode-экранирования свойств есть в PyPI-пакете regex: regex.findall(r'\p{Script=Han}+', text).
9. Готовые паттерны на каждый день
9.1 Валидация email
Сначала честно решите, какая версия вам нужна.
// Паттерн на 95% — то, что использует большинство валидаторов форм
const email = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
email.test('a@b.co'); // true
// Паттерн «я действительно хочу быть в духе RFC 5322»
const rfc = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
По правде говоря, полная валидация email по RFC 5322 на чистом regex занимает около 6000 символов и всё равно спотыкается на краевых случаях. Возьмите паттерн на 95% и отправьте письмо для верификации: это единственный тест, который реально работает.
9.2 Извлечение URL
const urlPattern = /https?:\/\/[^\s<>"]+/g;
const found = 'See https://example.com/a?b=1 and http://x.io'.match(urlPattern);
// ['https://example.com/a?b=1', 'http://x.io']
После того как URL извлечён, обычно хочется заглянуть в query string. Вставьте адрес в наш URL декодер и кодировщик, и percent-encoded параметры станут читаемыми с одного взгляда. Когда кодировать, а когда декодировать, разобрано в руководстве по percent-encoding.
9.3 Номера телефонов
// E.164 — международный, опциональный + и код страны из 1-3 цифр
const e164 = /^\+?[1-9]\d{1,14}$/;
e164.test('+14155551234'); // true
// Североамериканский план нумерации с разделителями
const nanp = /^(\+?1[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}$/;
nanp.test('(415) 555-1234'); // true
Если нужно что-то серьёзнее проверки «похоже ли это вообще на телефон», берите libphonenumber. Существование кода города regex не проверит.
9.4 IPv4 и IPv6
// IPv4 — строго 0-255 на октет
const ipv4 = /^((25[0-5]|2[0-4]\d|1?\d?\d)\.){3}(25[0-5]|2[0-4]\d|1?\d?\d)$/;
ipv4.test('192.168.1.1'); // true
ipv4.test('999.0.0.1'); // false
// IPv6 — упрощённая форма. Полный паттерн RFC 4291 это ~600 символов.
const ipv6simple = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
ipv6simple.test('2001:0db8:85a3:0000:0000:8a2e:0370:7334'); // true
Для настоящего IPv6 с сокращением ::, встроенным IPv4 и идентификаторами зон возьмите isIP() из node:net или ipaddress.ip_address() в Python. Сделать это на чистом regex — сначала обряд посвящения, потом вечная боль поддержки.
9.5 Даты и timestamp ISO 8601
// Только дата — YYYY-MM-DD
const isoDate = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/;
isoDate.test('2026-05-13'); // true
// Дата + время + опциональные дробные секунды + Z или смещение
const iso = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
iso.test('2026-05-13T09:30:00.123Z'); // true
ISO 8601 кажется простым, но в нём полно ловушек: секунды координации, недельные даты (2026-W19), порядковые даты (2026-133). Про секунды эпохи и миллисекунды, а также про сдвиги часовых поясов написано в руководстве по Unix timestamp.
10. Рабочие процессы поиска и замены с regex
10.1 JavaScript — String.replace с $1
// Переформатировать даты США: MM/DD/YYYY -> YYYY-MM-DD
'05/13/2026'.replace(/(\d{2})\/(\d{2})\/(\d{4})/, '$3-$1-$2');
// '2026-05-13'
// Использовать callback, когда замена условная
'price 42 dollars'.replace(/(\d+) dollars/, (_, n) => `$${n}`);
// 'price $42'
$1, $2, … обращаются к нумерованным группам, $<name> — к именованным. $& — это полное совпадение, а $$ — литеральный $.
10.2 Python — re.sub с \1 и callback
import re
# То же переформатирование даты, что и выше
re.sub(r'(\d{2})/(\d{2})/(\d{4})', r'\3-\1-\2', '05/13/2026')
# '2026-05-13'
# Callback — привести в верхний регистр каждый email-адрес в строке
def upper_email(m):
return m.group(0).upper()
re.sub(r'[\w.-]+@[\w.-]+', upper_email, 'mail me at hi@go-tools.org')
# 'mail me at HI@GO-TOOLS.ORG'
В заменах Python пишут \1 или \g<name>. Префикс raw-строки r'...' принципиально важен: без него \1 превратится в литеральный символ.
10.3 CLI: sed, grep, ripgrep, jq
В пакетных рефакторингах в командной строке regex переезжает из скрипта прямо в shell:
# ripgrep — найти каждый TODO с прикреплённым именем
rg -n '\bTODO\(([^)]+)\)' --replace 'TODO(\1)'
# grep -E с якорями — строки неудачных логинов из auth.log
grep -E '^[A-Z][a-z]{2} +[0-9]+ .*Failed password' /var/log/auth.log
# sed — удалить хвостовые пробелы, in-place, по всему дереву
find . -name '*.md' -print0 | xargs -0 sed -i -E 's/[[:space:]]+$//'
ripgrep использует crate regex для Rust (стиль RE2, линейное время, без lookbehind). grep -E и sed -E работают на POSIX extended regex, в котором нет \d: вместо него пишите [0-9] или [[:digit:]]. Когда данные — это JSON, regex стоит сменить на jq: рядом лежит шпаргалка по jq.
11. Распространённые подводные камни
11.1 Забытое экранирование .
Реальная история из продакшена: log redactor должен был маскировать IP-адреса.
// Неправильно — совпадает и с '192a168b1c1'
/(\d+).(\d+).(\d+).(\d+)/.test('192a168b1c1'); // true
// Правильно
/(\d+)\.(\d+)\.(\d+)\.(\d+)/.test('192a168b1c1'); // false
Внутри класса символов . и так литерал, поэтому работают и [.], и \.. Везде в других местах её нужно экранировать.
11.2 Жадный .* съедает слишком много
'<a href="x"><b>bold</b></a>'.match(/<(.*)>/)[1];
// 'a href="x"><b>bold</b></a' — всё целиком!
Жадный .* доходит до конца строки и затем откатывается назад, пока не упрётся в > — то есть в последний > во вводе. Либо берите ленивую версию (.*?), либо, что быстрее и понятнее, — класс с отрицанием ([^>]*).
11.3 Многострочные якоря
Частое заблуждение: ^ и $ по умолчанию якобы цепляются за переводы строк. Нет, они совпадают с позициями в начале и в конце всего ввода. Флаг m делает их построчными якорями. Флаг s разрешает . пересекать переводы строк. Флаги ортогональны, и для разбора логов обычно нужны оба.
11.4 ReDoS и как его обезвредить
ReDoS — regex denial of service — продакшен-версия катастрофического возврата. Что с этим делать:
- Статический анализ. Инструменты вроде
safe-regex,recheckи правило ESLintno-misleading-character-classловят опасные паттерны ещё до релиза. - Атомарные группы (Python
regex, PCRE, Ruby, Java):(?>...)не даёт движку повторно зайти в группу при откате. - Притяжательные квантификаторы (
*+,++,?+в PCRE/Java): та же идея, синтаксис короче. - Переход на движок без возврата.
regexpв Go, RE2, crateregexдля Rust и Python-биндингre2работают за линейное время. ripgrep — самое массовое применение RE2 в дикой природе. - Сначала валидируйте длину ввода. Regex-бомба на 10 КБ — это баг; ограничение ввода в 10 байт — одна строка кода.
Более широкий взгляд на повседневные инструменты, которые работают в паре с regex (форматтеры, декодеры, конвертеры), есть в руководстве по инструментам разработчика.
Прежде чем отправлять сложный паттерн в продакшен, протестируйте его интерактивно. regex101.com переключается между диалектами PCRE, JavaScript, Python и Go, объясняет каждый токен и показывает возврат, так что катастрофические паттерны видны сразу.
12. FAQ
В чём разница между regex * и +?
* соответствует нулю или более вхождений (то есть совпадает и с пустой строкой); + требует минимум одного вхождения. a* совпадает с '', 'a', 'aaaa'. a+ совпадает с 'a' и 'aaaa', но не с ''.
Как сопоставить regex по нескольким строкам?
Включите multiline-флаг (/.../m в JavaScript, re.MULTILINE в Python), чтобы ^ и $ привязывались к каждой строке. Чтобы . пересекала переводы строк, добавьте dotall-флаг (s в JavaScript, re.DOTALL в Python).
Одинаков ли regex в JavaScript и Python?
Базовый синтаксис (квантификаторы, якоря, классы символов, простые группы) совпадает примерно на 90%. Реальных расхождений два. JavaScript (ES2018+) поддерживает lookbehind переменной длины и пишет именованные группы как (?<name>...). Стандартный re в Python требует lookbehind фиксированной ширины и использует (?P<name>...). Чтобы получить lookbehind переменной длины в Python, поставьте пакет regex из PyPI.
Почему у моего regex катастрофический возврат?
В паттерне сидят вложенные квантификаторы с пересекающимися совпадениями: (a+)+, (a|a)* и тому подобное. На вводе, который почти совпадает, но обламывается ближе к концу, движок перебирает каждое возможное разбиение внутреннего квантификатора — экспоненциально много путей. Лечится атомарной группой (?>a+)+, притяжательным квантификатором a++ или переходом на движок без возврата (RE2, regexp в Go).
Можно ли использовать lookbehind в JavaScript?
Да. Положительный (?<=...) и отрицательный (?<!...) lookbehind есть в V8 (Chrome, Node.js), SpiderMonkey (Firefox) и JavaScriptCore (Safari 16.4+) начиная с ES2018. Lookbehind переменной длины поддерживается. Для более старого Safari транспилируйте через Babel или определяйте поддержку через try/catch вокруг new RegExp.
Как сопоставить литеральную точку . в regex?
Экранируйте её обратным слешем: \. соответствует литеральной точке. Внутри класса символов точка и так литерал, работают и [.], и [\.]. Вне класса неэкранированная . — метасимвол со значением «любой символ, кроме перевода строки» (а с dotall-флагом — вообще любой символ).
Что означает \s в regex?
\s совпадает с любым пробельным символом — пробелом, табуляцией, переводом строки, возвратом каретки. В режиме Unicode он также совпадает с NBSP. \S — обратный класс.
Чувствительны ли регулярные выражения к регистру?
По умолчанию — да. Используйте флаг i в JavaScript (/cat/i) или re.IGNORECASE / (?i) в Python.