Skip to main content

Квантификаторы и метасимволы. Жадность, классы символов

  1. Жадность, ленивость и «жадность без возврата»
  • По умолчанию квантификаторы жадные: *, +, ?, {m,n}
  • Ленивые (минимальные): *?, +?, ??, {m,n}?
  • «Без возврата» (possessive): *+, ++, ?+, {m,n}+ — не делают бэктрекинг после совпадения.
    • Поддержка: PCRE/Java/.NET — да; Python re/JS — нет (аналог в PCRE/.NET: атомарная группа (?>…); в Python — в модуле regex, не re).
  • Пример разницы (захват группы):
    • Шаблон: ^(.+)\d$ на “ab1cd2” → группа 1 = “ab1cd”
    • Ленивый: ^(.+?)\d$ → группа 1 = “ab1c”
  • Избегаем катастрофического бэктрекинга:
    • Плохо: ^(a+)+$ на “aaaaaaaaaaab” — взрыв по времени
    • Лучше: ^(?:a++)+$ (PCRE/.NET), либо просто ^a+$ если логика позволяет
    • Альтернатива: атомарная группа ^(?>a+)+$ (PCRE/.NET)
  1. Классы символов: [A-Za-z0-9] vs \d, \w, \s
  • Явные диапазоны безопасны для ASCII: [0-9], [A-Za-z], [A-Za-z0-9_]
  • Предопределённые классы зависят от движка/флагов:
    • JavaScript: \d = [0-9], \w = [A-Za-z0-9_], даже с флагом u; доступны Unicode-свойства c \p{…} только с флагом u (например, \p{L}, \p{Nd})
    • Python 3 re (Unicode по умолчанию): \d — любые Unicode-цифры; \w — «слово» из многих алфавитов + _; для ASCII-режима используйте явные диапазоны или флаг re.ASCII/inline (?a)
    • .NET: Unicode-aware; \d — все Nd, \w — буквы, цифры, соединительная пунктуация и _
    • PCRE2: по умолчанию \d ~ [0-9], \w ~ [A-Za-z0-9_]; с модификаторами /u и UCP — по Unicode-свойствам
  • Вывод: для строгих ASCII-политик паролей не используйте \d/\w — задавайте диапазоны явно.
  1. Unicode-нюансы: «двойники» и нормализация
  • Визуальные двойники:
    • Latin o (U+006F) vs Cyrillic о (U+043E): “pаssword” может содержать кириллическую а (U+0430)
    • Греческая Ι (U+0399) vs латинская I (U+0049); кириллическая с (U+0441) vs латинская c (U+0063)
  • Комбинируемые знаки: é = U+00E9 vs e + ◌́ (U+0065 U+0301). Без нормализации строки «на глаз» одинаковы, но байтово разные.
  • Практика ИБ:
    • Для паролей чаще фиксируют точный алфавит (часто ASCII) и не нормализуют.
    • Для логинов/имён — применяют нормализацию NFC/NFKC и фильтры на confusables.
  • Детект ASCII-только:
    • PCRE/JS: ^[\x00-\x7F]+$ или ^[\p{ASCII}]+$/u (JS, PCRE)
    • Python/.NET: ^[\x00-\x7F]+$
  1. Практика: кейсы различий движков
  • Кейc 1. ^\w+$ и строка “Имя_пользователя”

    • JS: ✗ (ASCII-ограниченный \w)
    • Python/.NET (Unicode): ✓
    • PCRE2: ✗ без UCP; ✓ c /u и UCP
    • Рекомендация: для универсальности — явный алфавит или Unicode property escapes: ^[\p{L}\p{Nd}_]+$/u
  • Кейс 2. ^\d{3}$ и арабско-индийские цифры “١٢٣” (U+0661..U+0663)

    • JS: ✗; Python/.NET/PCRE(UCP): ✓
    • Для ASCII-цифр — только [0-9]
  • Кейс 3. Ловля «двойников» в паролях (ASCII-жёстко)

    • Разрешены только латиница/цифры: ^[A-Za-z0-9]{8,}$ — кириллическая «о» отсекается
    • Разрешить все буквы/цифры (Unicode): ^[\p{L}\p{Nd}]{8,}$/u — но учтите confusables
  • Кейс 4. Жадность и границы

    • Шаблон: ^(?=.*\d).{8,}$ — «хотя бы одна цифра, длина ≥8». Точка жадная, но lookahead фиксирует требование — это корректно.
    • Антипаттерн: ^.\d.$ без ^$ вокруг всей политики → проверяет подстроку, не весь пароль.
  • Кейс 5. Катастрофический бэктрекинг

    • Плохо: ^(.*a){10}$ на длинных «почти» подходящих строках
    • Лучше: избегать вложенных повторов на «.», использовать атомарные группы/possessive: ^(?:[^a]*+a){10}$ (PCRE/.NET)
  1. Рецепты-шаблоны
  • Строгий ASCII-пароль (буквы/цифры), 8–20: ^[A-Za-z0-9]{8,20}$
  • Unicode-буквы/цифры (ECMAScript 2018+): ^[\p{L}\p{Nd}]{8,20}$/u
  • «Хотя бы одна заглавная и цифра» (ASCII): ^(?=.[A-Z])(?=.[0-9])[A-Za-z0-9]{8,}$
  • Запрет «двойников» O, 0, l, I: ^(?!.*[O0lI])[A-Za-z0-9]{8,}$
  1. Мини-чек-лист
  • Нужен полный матч? Используйте ^…$ или fullmatch.
  • Какой алфавит? Если ASCII — никакого \w/\d.
  • Unicode нужен? Тогда свойста: \p{L}, \p{Nd} с флагом u (JS/PCRE), или эквиваленты в .NET/Python.
  • Есть вложенные квантификаторы? Подумайте о possessive/атомарных группах или переписывании шаблона.
  • Тестируйте в разных движках: regex101 (PCRE2, включайте /u и UCP), Python re, .NET, JS с /u.

Фрагмент корпуса для тестов

Примеры строк:
- The quick brown fox jumps over 13 lazy dogs.
- Start_1 middle_text end9
- password PASSword Pa55w0rd GoodPass9
- a____9 A12345 9start1
- name_surname name--surname username_ _username
- abcde9 abcdef ABCDEF1 ABcdefghij1
- HELLOlll IllIlIll safeSAFE2
- abc def abc_def abc-def
- 123456 0000000000 1234567890