Регулярные выражения

Регуля́рные выраже́ния (англ. regular expressions, жарг. регэ́кспы или ре́гексы или регулярки) — система обработки текста, основанная на специальной системе записи шаблонов для поиска.

(?<=\.) {2,}(?=[A-Z])

Сейчас регулярные выражения используются многими текстовыми редакторами и утилитами для поиска и изменения текста на основе выбранных правил. Многие языки программирования уже поддерживают регулярные выражения для работы со строками. Например, Perl и Tcl имеют встроенный в их синтаксис механизм обработки регулярных выражений. Набор утилит (включая редактор sed и фильтр grep), поставляемых в дистрибутивах Unix, одним из первых способствовал популяризации понятия регулярных выражений.

Краткая история

править

Первой программой с поддержкой регулярных выражений был Unix редактор qed, от которого произошел редактор ed. Многим пользователям настолько понравилась команда редактора, которая позволяла искать по регулярным выражениям строки из файла, что она была вынесена и оформлена в отдельную программу — grep. Программа grep была и остается популярной; она портирована на множество платформ, но тем не менее она не установила каких-либо стандартов на язык регулярных выражений.

Многие разработчики, вдохновляясь grep, дополняли первоначальный синтаксис своими наработками, но все это делалось беcсистемно. Со временем какие-то программы становились популярными и вместе с ними становились популярны и диалекты регулярных выражений, которые в них были реализованы.

В настоящее время особую популярность приобрели следующие диалекты:

  • POSIX диалекты. Два диалекта стандартизовано в POSIX, которые носят названия BRE (от Basic Regular Expressions) и ERE (от Extended Regular Expressions). Например, BRE диалект используется программой grep, а ERE — egrep.
  • Perl диалект, появившийся в языке программирования Perl, который в целом следует ERE, дополняя его своими возможностями. Множество современных диалектов регулярных выражений копируют именно стиль Perl.
  • PCRE диалект (от Perl Compatible Regular Expressions) — диалект, используемый в библиотеке Филипа Хейзела. В целом похож на Perl диалект, но имеет некоторые отличия. PCRE диалект например используется в языке PHP и многих современных графических текстовых редакторах.
  • ECMAScript диалект. Диалект, используемый в языках ECMA-спецификации, например JavaScript. Данный диалект формировался под влиянием Perl 5. Этот диалект включается по умолчанию в стандартной библиотеке STL, начиная с версии стандарта С++11.
  • Python диалект, используемый в языке Python.

Глядя на такое разнообразие, не нужно думать, что при переходе с одного диалекта на другой нужно их заново изучать. Отчасти конечно нужно, но большинство базовых вещей остается во всех диалектах неизменными. Необходимо всего лишь знать, какие возможности поддерживаются, а какие являются непереносимыми между диалектами. Основная разница проявляется в интерпретации управляющих последовательностей, добавлением новых операторов, реализацией процессора регулярных выражений. В последнем случае это означает, что некоторые регулярные выражения могут работать медленнее или быстрее на разных платформах только потому, что они по разному интерпретируются процессором.

Базовые понятия

править

Регулярные выражения используются для сжатого описания некоторого множества строк с помощью шаблона (англ. pattern), без необходимости перечисления всех элементов этого множества.

Для лучшего понимания всего процесса мы введем понятие абстрактного процессора регулярных выражений. Под процессором мы будем понимать условную функцию, на вход которой подается некоторый текстовый фрагмент и шаблон. Результатом работы этой функции является возвращаемое множество результатов в виде подстрок, входящих в переданный фрагмент, которые удовлетворяют переданному шаблону.

На практике процессор регулярных выражений выполняет поиск по шаблону не для одного фрагмента, а для множества фрагментов, которые составляют входящий текст. Разбиение на фрагменты большого текста, позволяет процессору регулярных выражений разумно расходовать ресурсы компьютера, так как текст может быть очень большим для обработки его целиком. Не вдаваясь в детали реализации, процессор мог бы разбивать большой текст на фрагменты ориентируясь на символ переноса строки. Таким образом, процессор строка за строкой пытается найти искомые результаты во фрагментах, причем каждый отдельный фрагмент это отдельная по времени вызываемая процедура, результат которой никак не оказывает влияния на предыдущие или последующие результаты. Понимание этого важно, когда вы столкнетесь с конкретными реализациями процессоров регулярных выражений.

Хотя понимание фрагмента как совокупности символов до первого переноса строки позволяет вам выстроить некоторое базовое понимание, на практике сам шаблон может иногда учитывать символы переноса строки (потому что он просто так был составлен), и по этой причине результатом может быть две и более «склеенных» строки, что немного размывает понятие фрагмента.

Шаблон — это строка текста, состоящая из литералов и метасимволов (англ. wildcards). Для примера возьмем такое регулярное выражение

\d{4}-\d{2}-\d{2}

Данный шаблон представляет любую дату в тексте, записанную в формате YYYY-MM-DD, например 2022-07-11. В этом шаблоне литералами являются символы тире (-). Литералы буквально должны соответствовать символам, найденным в результатах. Все остальные символы в этом шаблоне являются метасимволами. Метасимволы в отличие от литералов не называют конкретный символ, а диктуют требования для символа(ов), который должен находится в указанной позиции фрагмента. Например, \d{4} диктует требования, что в указанной позиции может стоять 4 десятичных цифры, для того чтобы некоторая подстрока во фрагменте удовлетворяла данному шаблону. Когда процессор находит подстроку, соответствующую шаблону, мы говорим, что метасимволы раскрываются (соответствуют) в найденные литералы (англ. is matched).

Интересно, что само слово wildcard буквально означает «шальная карта», что вероятнее всего пришло из карточных игр. Под шальной картой понимается лежащая перевернутая игральная карта и наверняка не известно какого она достоинства. В иностранной литературе по регулярным выражениям под wildcard понимается не сам метасимвол, а метод его интерпретации процессором.

Базовый синтаксис

править

При составлении шаблонов используется специальный синтаксис, поддерживающий обычно следующие операции:

Перечисление (альтернатива) (англ. Alternation)
Предоставляет процессору возможные варианты раскрытия шаблона в указанной позиции. Соответствует слову ИЛИ. Обычно для обозначения перечисления используется вертикальная черта |. Например, gray|grey соответствует gray или grey. Вариантов перечисления в общем случае может быть больше двух.
Группировка (группа) (англ. Grouping)
Группы используются для компактного описания подмножеств в рамках одного общего шаблона. Группы выделяются круглыми скобками (). Например, gray|grey и gr(a|e)y являются разными шаблонами, но они оба описывают одно и тоже множество искомых вариантов gray и grey.
Квантификация (англ. Quantification)
Позволяет описать серию искомых символов. Выражает предложение «сколько раз может встречаться».
{n,m}
общее выражение, повторений может быть от n до m включительно.
{n,}
общее выражение, n и более повторений.
{,m}
общее выражение, не более m повторений.
{n}
общее выражение, ровно n повторений
?
Знак вопроса означает 0 или 1 раз, то же самое, что и {0,1}. Например, colou?r соответствует и color, и colour.
*
Звёздочка означает 0, 1 или любое число раз ({0,}). Например, go*gle соответствует gogle, google, gooogle, ggle, и др.
+
Плюс означает хотя бы 1 раз ({1,}). Например, go+gle соответствует gogle, google и т. д., но не ggle.

Конкретный синтаксис регулярных выражений зависит от реализации процессора регулярных выражений.

В теории формальных языков

править

Регулярные выражения состоят из констант и операторов, которые определяют множества строк и множества операций на них соответственно. На данном конечном алфавите Σ определены следующие константы:

  • (пустое множество) ∅ обозначает ∅
  • (пустая строка) ε обозначает множество {ε}
  • (строка) a в Σ обозначает множество {a}

и следующие операции:

  • (связь, конкатенация) RS обозначает множество { αβ | α из R и β из S }. Пример: {"ab", "c"}{"d", "ef"} = {"abd", "abef", "cd", "cef"}.
  • (перечисление) R|S обозначает объединение R и S.
  • (замыкание Клини, звезда Клини) R* обозначает минимальное надмножество из R, которое содержит ε и закрыто связью строк. Это есть множество всех строк, которые могут быть получены связью нуля или более строк из R. Например, {"ab", "c"}* = {ε, "ab", "c", "abab", "abc", "cab", "cc", "ababab", … }.

Многие книги используют символы ∪, + или ∨ для перечисления вместо вертикальной черты.

Синтаксис

править

Все диалекты в той или иной степени похожи друг на друга. Исходя из этого мы можем сформулировать общие правила построения шаблонов.

  • Во всех диалектах метасимволы могут быть разбиты на следующие категории:
    • Простые метасимволы. Обычно состоят из одного символа (\ ^ . $ |) и раскрывают или помечают характерную часть фрагмента. К простым метасимволам мы также можем отнести пары из скобок, например класс литералов ([]) и группа (()).
    • Квантификаторы. Позволяют описать серию из символов. Квантификаторы не могут быть записаны сами по себе и всегда привязаны к другому метасимволу или к литералу шаблона.
    • Класс литералов. Описывают конкретное множество допустимых символов, например \t — класс состоящий из одного символа табуляции; \d (PCRE) или [:digit:] (BRE) — класс представляющий десятичные цифры.
    • Мнимые метасимволы. Метасимволы, которые не раскрываются в какой-то видимый символ, а которые призваны обозначить место во фрагменте, например \b (PCRE) — граница слова.
    • Продвинутые группы. Это группы, позволяющие вводить условия поиска в шаблон, например группа позитивного прямого просмотра (Positive Lookahead) — (?=).
  • Любой символ в шаблоне воспринимается как простой литерал, если он не является метасимволом. Чтобы метасимвол воспринимался как простой литерал, его нужно экранировать метасимволом \. Метасимвол \ экранируется собой же, т.е. \\ это литерал \.
  • Любой простой литерал, на котором применен метасимвол экранирования, продолжает оставаться простым литералом (например \k и k эквивалентны). Однако, следует быть осторожным, так как не все реальные процессоры регулярных выражений следуют этому утверждению.
  • Все символы последовательности простых литералов воспринимаются как простые литералы, если они не являются метасимволами.
  • Чтобы создать произвольный класс литералов используется метасимвол [], внутри которого перечисляются все желаемые литералы. Внутри этого метасимвола разделителей нет. Если вы заранее знаете о классе литерала на некоторой позиции фрагмента, то лучше использовать метасимволы типа класс литералов. Произвольный класс литералов всегда раскрывается в один из допустимых литералов, если не используется какой-либо квантификатор. Внутри класса могут создаваться диапазоны с точки зрения таблицы кодировки. Диапазон создается с помощью символа тире. Например, чтобы создать класс из цифр с 1 по 7, нужно написать [1-7]. В этом смысле тире трактуется не как символ класса, а как диапазон. Чтобы включить тире в класс нужно написать его первым или последним, т.е. [-1-7] или [1-7-]. Внутри класса также можно экранировать литералы, которые могут трактоваться двояко: например, чтобы создать класс из символов -, ^ и \, можно написать [\-\^\\].
  • Альтернатива задается через метасимвол |, т.е. a|b означает «либо a, либо b». Альтернатив может быть больше двух (a|b|c|d), но раскрывается при этом для каждого сопоставления шаблона всегда только один вариант. На выбор из альтернатив может влиять жадность квантификатора, если он используется.
  • Для указания на группу используется ссылка на группу в виде \n, где n — цифра. Не следует путать это с литералом в виде цифры. Обычно процессор регламентирует, сколько групп может быть в одном шаблоне. Группы нумеруются слева направо, начиная с 1 (например, \1, \2, \3 и т.д.).
  • Группа () позволяет использовать повторяющиеся части искомой последовательности литералов многократно в одном шаблоне в пределах обрабатываемого фрагмента текста. Группа представляет отдельный шаблон, результат раскрытия по которому сохраняется в отдельной области памяти, на которую можно сослаться.
  • Группы могут вкладываться друг в друга.
  • Группа всегда раскрывается в той же позиции, в которой она находится во внешнем по отношению к ней шаблоне. Группа всегда раскрывается ровно один раз.
  • На группу может быть применен квантификатор, однако он не действует на сохраняемый в память результат: в памяти будет сохранено только самое первое вхождение после квантификации.
  • Ссылки на группу оформляются как \n, где n — цифра, начиная с единицы. От процессора регулярных выражений требуется поддержка не менее 9 групп. Для вложенных групп отдельного стека не создается и нумерация продолжается по сквозному принципу в глубину. Например, если записать так ( ( ) ), то внешняя группа скобок образует группу \1, а внутренняя — \2.

Из чего строится фрагмент

править

Следует также обратить внимание на то, из чего строится фрагмент. Возьмем например предложение

«Witch» and her «broom» is one

Начнем с очевидного. Фрагмент строится из печатаемых (видимых) символов, невидимых символов (например, простой пробел или символы, которые по каким-то причинам не могут быть выведены на устройство) и управляющих последовательностей, которые нужны только компьютеру для вывода текста в удобочитаемом виде на устройство (символы табуляции, символы переноса строки и др.). Хотя управляющие последовательности в большинстве случаев не печатаются, процессор регулярных выражений не игнорирует их запись в исходном тексте и считает их частью входящего фрагмента.

При написании шаблонов важно также понимать, что фрагмент состоит и из менее очевидных частей:

  • Граница слова. Под границей слова в большинстве реализаций понимается переход литерала класса «алфавитно-цифровой или символ _» в литерал другого класса для правой границы слова и обратный переход для левой границы. Если фрагмент начинается со слова, первый символ которого относится к классу «алфавитно-цифровой или символ _», то этот символ является левой границей этого слова. Для нашего примера левыми границами тогда считаются следующие символы (именно символы, потому что компьютер может выбирать только те места фрагмента, куда попадает каретка. Для людей граница слова обычно ассоциируется с промежутком между буквами)
    «Witch» and her «broom» is one 
    • Левые границы:
      • Буква W в слове Witch. Не открывающая кавычка, потому что в класс «алфавитно-цифровой или символ _» она не входит;
      • Буква a в слове and;
      • Буква h в слове her;
      • Буква b в слове broom;
      • Буква i в слове is;
      • Буква o в слове one.
    • Правые границы:
      • Закрывающая кавычка » в словах Witch и broom. Здесь интересно то, что для левой и правой границы берется правый символ в переходе, поэтому для правой границы в этих словах так получилось, что это кавычка;
      • Каждый первый пробел, следующий за другими словами, кроме слова one;
      • После слова one может идти управляющая последовательность (либо \n, если это только фрагмент текста и текст разбивается на фрагменты по переносам строки, либо \0, если это последний фрагмент текста) и именно эта управляющая последовательность будет считаться границей слова.
  • Граница фрагмента. Границами фрагмента являются левый и правый символ всего фрагмента. Если текст нашего примера был захвачен как фрагмент, то левой границей является открывающая кавычка «, а правой — управляющая последовательность.
    «Witch» and her «broom» is one 
  • Граница текста. Очень важно понимать, что граница текста и граница фрагмента не одно и то же. Некоторые процессоры регулярных выражений имеют опции обработки входящего текста в многострочном или однострочном режиме. В многострочном режиме текст разбивается на фрагменты по некоторому разделителю (обычно по переносам строк) и каждый фрагмент сравнивается с шаблоном отдельно. В этом режиме границы текста и границы фрагмента не совпадают, потому что левая граница текста будет только у самого первого фрагмента, а правая граница — у самого последнего. При этом каждый фрагмент будет иметь также свои границы. Это важно понимать, потому что многие диалекты имеют разные метасимволы для обозначения разных границ.
    «Witch» and her «broom» is one
    Witch» and her «broom» is one
    Witch» and her «broom» is one 

    В однострочном режиме весь текст по сути является одним большим фрагментом, поэтому левая и правая границы текста совпадают с левой и правой границами фрагмента.

Более продвинутые реализации процессоров регулярных выражений могут иметь метасимволы, отслеживающие управляющие последовательности для определения границ их действия. Также в реализацию может быть включено отслеживание переходов смены регистра в словах. О таких возможностях следует читать в документации к каждой конкретной реализации.

Рассмотрим несколько примеров. В PCRE диалекте граница слов обозначается мнимым метасимволом \b, \A и \Z — начало и конец текста соответственно и метасимволы ^ и $ — начало и конец фрагмента соответственно в многострочном режиме процессора. Теперь сравните регулярные выражения

W.*
# Соответствует
Witch» and her «broom» is one    # Примечание: открывающая кавычка не захватывается

W.*?\b
# Соответствует
Witch                            # До первой правой границы слова, благодаря ленивой квантификации *?

W.*\b
# Соответствует
Witch» and her «broom» is one    # Из-за жадной квантификации захватывается все до правой границы слова 'one'.

W.*h\b
# Соответствует
Witch                            # В общем случае, любая серия символов, начинающаяся на 'W' и оканчивающаяся 'h'.

\bb.*m\b
# Соответствует                
broom                            # В общем случае, любая серия символов, начинающаяся на 'b' и оканчивающаяся 'm'.

# Примечание:
# Важно понять, что запись
#    \bW   
# фактически означает буквально "левая граница слова начинается на 'W'", а
#    h\b
# - "правая граница слова оканчивается на 'h'".
#

^«.*e$
# Соответствует
«Witch» and her «broom» is one    # В общем случае для многострочного режима, любой фрагмент, начинающийся на открывающую кавычку '«' и оканчивающийся на 'e'.

\A«.*e\Z
# Соответствует
«Witch» and her «broom» is one    # В любом режиме процессора, только такой текст, который начинается открывающейся кавычкой '«', и оканчивающийся на 'e'.
# Например текст "«Witch» was gone" также подпадает под предыдущее выражение.

Традиционные регулярные выражения в Unix (BRE)

править

Синтаксис «базовых» регулярных выражений Unix на данный момент определён POSIX как устаревший, но он до сих пор широко распространён из соображений обратной совместимости. Многие Unix-утилиты используют такие регулярные выражения по умолчанию.

В этом синтаксисе большинство символов соответствуют сами себе («a» соответствует «a» и т. д.). Исключения из этого правила называются метасимволами:

. Соответствует любому единичному символу.
[ ] Произвольный класс литералов. Соответствует любому единичному символу из числа заключённых в скобки. Символ - интерпретируется буквально только в том случае, если он расположен непосредственно после открывающей или перед закрывающей скобкой: [abc-] или [-abc]. В противном случае, он обозначает интервал символов. Например, [abc] соответствует «a», «b» или «c». [a-z] соответствует буквам нижнего регистра латинского алфавита. Эти обозначения могут и сочетаться: [abcq-z] соответствует a, b, c, q, r, s, t, u, v, w, x, y, z.

Чтобы установить соответствие символам [ или ], достаточно, чтобы закрывающая скобка была первым символом после открывающей: [][ab] соответствует «]», «[», «a» или «b».

[^ ] Соответствует единичному символу из числа тех, которых нет в скобках. Например, [^abc] соответствует любому символу, кроме «a», «b» или «c». [^a-z] соответствует любому символу, кроме символов нижнего регистра в латинском алфавите.
^ Соответствует началу текста (или началу любой строки в многострочном режиме). Ищет с начала текста.
$ Соответствует концу текста (или концу любой строки в многострочном режиме).
\(\) Объявляет «отмеченное подвыражение» (иногда называется «захват»), которое может быть использовано позже (см. следующий элемент: \n). «Отмеченное подвыражение» также является «блоком». В отличие от других операторов, этот (в традиционном синтаксисе) требует обратного слеша.
\n Где n — это цифра от 1 до 9; соответствует n-му отмеченному подвыражению. Эта конструкция теоретически нерегулярна, она не была принята в расширенном синтаксисе регулярных выражений.
*
  • Звёздочка после выражения, соответствующего единичному символу, соответствует нулю или более копий этого выражения. Например, [xyz]* соответствует пустой строке, «x», «y», «zx», «zyx», и т. д.
  • \n*, где n — это цифра от 1 до 9, соответствует нулю или более вхождений для соответствия n-го отмеченного подвыражения. Например, \(a.\)c\1* соответствует «abcab» и «abcaba», но не «abcac».
  • Выражение, заключённое в \( и \) и сопровождаемое «*», следует считать неправильным. В некоторых случаях оно соответствует нулю или более вхождений строки, которая была заключена в скобки. В других, оно соответствует выражению, заключённому в скобки, учитывая символ «*».
\{x,y\} Соответствует последнему блоку, встречающемуся не менее x и не более y раз. Например, a\{3,5\} соответствует «aaa», «aaaa» или «aaaaa». В отличие от других операторов, этот (в традиционном синтаксисе) требует обратного слеша.

Различные реализации регулярных выражений интерпретируют обратную косую черту перед метасимволами по-разному. Например, egrep и Perl интерпретируют скобки и вертикальную черту как метасимволы, если перед ними нет обратной косой черты и воспринимают их как обычные символы, если черта есть.

Многие диапазоны символов зависят от выбранных настроек локализации. POSIX стандартизовал объявление некоторых классов и категорий символов, как показано в следующей таблице:

POSIX класс Аналогично произвольному
классу литералов
PCRE Значение
[:upper:] [A-Z] символы верхнего регистра
[:lower:] [a-z] символы нижнего регистра
[:alpha:] [A-Za-z] символы верхнего и нижнего регистра
[:alnum:] [A-Za-z0-9] цифры, символы верхнего и нижнего регистра
[A-Za-z0-9_] \w цифры, символы верхнего, нижнего регистра и "_"
[^A-Za-z0-9_] \W не цифры, символы верхнего, нижнего регистра и "_"
[:digit:] [0-9] \d цифры
[^0-9] \D не цифры
[:xdigit:] [0-9A-Fa-f] шестнадцатеричные цифры
[:punct:] [.,!?:…] знаки пунктуации
[:blank:] [ \t] пробел и TAB
[:space:] [ \t\n\r\f\v] \s символы пробелов(пропуска)
[^ \t\n\r\f\v] \S не символы пробелов(пропуска)
[:cntrl:] [\x00-\x1F\x7F] символы управления
[:graph:] [:alnum:] ∪ [:punct:] символы печати
[:print:] [\x20-\x7E] символы печати и символы пропуска(видимые символы и пробелы)

Защита метасимволов

править

Cпособ представить сами метасимволы ., - [ ] и другие в регулярных выражениях без интерпретации, то есть, в качестве простых (не специальных) символов — предварить их обратной косой чертой: \. Например, чтобы представить сам символ «точка» (просто точка, и ничего более), надо написать \. (обратная косая черта, а за ней – точка). Чтобы представить символ открывающей квадратной скобки [, надо написать \[ (обратная косая черта и следом за ней скобка [) и т.д. Сам метасимвол \ тоже может быть защищен, то есть представлен как \\ (две обратных косых черты), и тогда интерпретатор регулярных выражений воспримет его как простой символ обратной косой черты \.

«Жадные» выражения

править

Квантификаторам в регулярных выражениях соответствует максимально длинная строка из возможных (квантификаторы являются «жадными», англ. greedy). Это может оказаться значительной проблемой. Например, часто ожидают, что выражение (<.*>) найдёт в тексте теги HTML. Однако этому выражению соответствует целиком строка

<p><b>Википедия</b> — свободная энциклопедия, в которой <i>каждый</i> может изменить или дополнить любую статью</p>.

Эту проблему можно решить двумя способами. Первый состоит в том, что в регулярном выражении учитываются символы, не соответствующие желаемому образцу (<[^>]*> для вышеописанного случая). Второй заключается в определении квантификатора как нежадного (ленивого, англ. lazy) — большинство реализаций позволяют это сделать, добавив после него знак вопроса.

Например, выражению (<.*?>) соответствует не вся показанная выше строка, а отдельные теги (выделены цветом):

<p><b>Википедия</b> — свободная энциклопедия, в которой <i>каждый</i> может изменить или дополнить любую статью</p>

  • *? — «не жадный» («ленивый») эквивалент *
  • +? — «не жадный» («ленивый») эквивалент +
  • {n,}? — «не жадный» («ленивый») эквивалент {n,}

Использование «ленивых» квантификаторов, правда, может повлечь за собой обратную проблему, когда выражению соответствует слишком короткая строка.

Также существуют квантификаторы повышения жадности, то, что захвачено ими однажды, назад уже не отдается. Сверхжадные квантификаторы (possessive quantifiers)

  • *+ — «сверхжадный» эквивалент *
  • ++ — «сверхжадный» эквивалент +
  • {n,}+ — «сверхжадный» эквивалент {n,}

Современные (расширенные) регулярные выражения в POSIX (ERE)

править

Регулярные выражения в POSIX аналогичны традиционному Unix-синтаксису, но с добавлением некоторых метасимволов:

+ Указывает на то, что предыдущий символ или группа может повторяться один или несколько раз. В отличие от звёздочки, хотя бы одно повторение обязательно.
? Делает предыдущий символ или группу необязательной. Другими словами, в соответствующей строке она может отсутствовать, либо присутствовать ровно один раз.
| Разделяет альтернативные варианты регулярных выражений. Один символ задаёт две альтернативы, но их может быть и больше, достаточно использовать больше вертикальных чёрточек. Необходимо помнить, что этот оператор использует максимально возможную часть выражения. По этой причине, оператор альтернативы чаще всего используется внутри скобок.

Также было отменено использование обратной косой черты: \{…\} становится {…} и \(…\) становится (…).

Perl-совместимые регулярные выражения (PCRE)

править

Регулярные выражения в Perl имеют более богатый и в то же время предсказуемый синтаксис, чем даже в POSIX. По этой причине очень многие приложения используют именно Perl-совместимый синтаксис регулярных выражений.

Метасимволы PCRE (без классов):

\ ^ . $ | ( ) [ ] * + ? { } *? +? ??

Комментарий:
(?#comment)

Продвинутые группы:
(?:pattern) (?=pattern) (?!pattern) (?<=pattern) (?<!pattern)

Примечание: символ ',' (запятая) может использоваться в квантификаторах
Метасимвол Аналог Значение Метасимвол Аналог Значение
Классы литералов
\t
[\x09]
Управляющая последовательность «горизонтальный табулятор»
\n
[\x0a]
Управляющая последовательность «перевод строки»
\r
[\x0d]
Управляющая последовательность «возврат каретки»
\f
[\x0c]
Управляющая последовательность «разрыв страницы»
\a
[\x07]
Управляющая последовательность «перевод формата/звонок спикера»
\v
[\x0b]
Управляющая последовательность «вертикальная табуляция»
\e
[\x1b]
Escape-символ
\nnn
\0nn
\o{nnn..}
где n – восьмеричная цифра
Символ в виде восьмеричного кода. Обычно коды используют, когда символ невозможно ввести с клавиатуры. Обратите внимание, что \nnn совпадает с ссылкой на группу с захватом (если она есть в шаблоне), поэтому чтобы отметить восьмеричный код существуют разные варианты
\xnn
\x{nnn..}
где n – шестнадцатеричная цифра
Символ в виде шестнадцатеричного кода. Обычно коды используют, когда символ невозможно ввести с клавиатуры
\cX
где X – ASCII-символ
Сочетание Ctrl + X, передаваемое в потоке символов
\N
В многострочном режиме эквивалентно
.
Не символ перевода строки
\V
[^\x0b]
Не вертикальная табуляция
\uNNN
где N – десятичная цифра
Символ в кодировке Unicode
\v
[\x0b]
Управляющая последовательность «вертикальная табуляция»
\w
[a-zA-Z0-9_]
Буквы, цифры и символ нижнего подчеркивания. Другими словами, это все символы, из которых можно составить слово. Обратите внимание, что аналог не совсем точно передает смысл метасимвола: современные процессоры поддерживают множество алфавитов, а не только латинский, поэтому более корректным было бы поместить в произвольную группу множество других печатаемых символов
\W
[^a-zA-Z0-9_]
Не символ класса \w
\s
[ \t\n\r\f\v]
Символ-разделитель
\S
[^ \t\n\r\f\v]
Не символ-разделитель
\d
[0-9]
Десятичная цифра
\D
[^0-9]
Не десятичная цифра
Мнимые метасимволы
\b
\W\w
\w\W
Граница слова
\B
Не граница слова
\A
Левая граница текста. В однострочном режиме аналогичен ^
\Z
Правая граница текста. В однострочном режиме аналогичен $

Группы

править

Группа по сути является шаблоном внутри другого шаблона. Группы используются по-разному:

  • Обычно группы используются, чтобы выделить во всем шаблоне некоторую характерную часть. В группах с захватом результат раскрытия сохраняется в отдельном буфере, из которого процессор регулярных выражений позволяет читать. Хорошим примером являются XML-структуры. Вам может потребоваться найти только определенные элементы в структуре, при этом вас могут интересовать только данные в элементе. В этом случае вы должны использовать группу с захватом:
    # Пусть вы ищете по регулярным выражениям такие XML-элементы
    <id>1</id>
    
    # Вероятнее всего вы будете использовать такой шаблон для их поиска
    <(id)\b[^>]*>(.*)</\1>
    #            ^^^^
    #       интересующая нас часть
    #      помещается в группу с захватом
    
    Реальные процессоры регулярных выражений позволяют вам извлечь данные из буфера группы. Обратите внимание, что в предыдущем примере группы две.
  • Группы с захватом могут использоваться, чтобы не дублировать повторяющиеся части в шаблоне. Например в предыдущем примере мы использовали первую группу захвата как раз для этой цели. На группы с захватом мы можем ссылаться, чтобы использовать результат раскрытия в последующих частях основного шаблона. Вы можете понять это через следующий искусственный пример
    "На дворе трава, на траве дрова. Не руби дрова, на траве двора."
    
    # Можно использовать такой шаблон, чтобы обращаться к повторным частям
    ^На (двор)е (трав)а, на \2е (дров)а\. Не руби \3а, на \2е \1а\.$
    
  • Группы без захвата не сохраняют результат раскрытия в буфере, т.е. на результаты ссылаться нельзя.
    • Простая группа без захвата обычно используется для группировки альтернатив, когда на результат раскрытия вам не нужно ссылаться.
    • Группы с опережающими проверками являются аналогом условного оператора if в мире регулярных выражений. Обычно они используются для первичного отсеивания фрагментов, прежде чем применять основной шаблон.
()
Простая группа с захватом.
(?:)
Группа без захвата. То же самое, но заключённое в скобках выражение не добавляется к списку захваченных фрагментов. Например, если требуется найти или «здравствуйте», или «здраститя», но не важно, какое именно приветствие найдено, можно воспользоваться выражением здра(?:ститя|вствуйте).
(?=)
Группа с положительной опережающей проверкой (positive lookahead assertion). Продолжает поиск только если справа от текущей позиции в тексте находится заключённое в скобки выражение. При этом само выражение не захватывается. Например, говор(?=ить) найдёт «говор» в «говорить», но не в «говорит». Иными словами, ищет в строке «говор», после которого сразу идут символы «ить» — если находит, выдает истину, иначе — ложь (FALSE).
(?!)
Группа с отрицательной опережающей проверкой (negative lookahead assertion). Продолжает поиск только если справа от текущей позиции в тексте не находится заключённое в скобки выражение. При этом само выражение не захватывается. Например, говор(?!ить) найдёт «говор» в «говорит», но не в «говорить».
(?<=)
Группа с положительной ретроспективной проверкой (positive lookbehind assertion). Продолжает поиск только если слева от текущей позиции в тексте находится заключённое в скобки выражение. При этом само выражение не захватывается. Например, (?<=об)говорить найдёт «говорить» в «обговорить», но не в «уговорить».
(?<!)
Группа с отрицательной ретроспективной проверкой (negative lookbehind assertion). Продолжает поиск только если слева от текущей позиции в тексте не находится заключённое в скобки выражение. При этом само выражение не захватывается. Например, (?<!об)говорить найдёт «говорить» в «уговорить», но не в «обговорить».

По первости очень легко можно запутаться между Lookahead и Lookbehind группами, из-за многообразия в синтаксисе. Можно воспользоваться мнемоническим приемом, который позволяет правильно вспомнить нужную специальную группу:

  • После открывающей скобки в любой из Lookahead или Lookbehind группе следующим символом идет знак вопроса.
  • Если это разновидность Lookbehind группы, то следующим за вопросом идет знак меньше <, который указывает, что просмотр идет справа налево. В Lookahead группах такого символа нет.
  • Негативный характер специальной группы подчеркивается восклицательным знаком !. Негативность это отсутствие совпадений в шаблоне группы, а позитивность наоборот это совпадение шаблона. Позитивный характер специальной группы подчеркивается знаком равно =.

Модификаторы шаблонов

править

Некоторые реализации процессоров регулярных выражений позволяют управлять процессором путем использования модификаторов. Модификаторы впервые появились по-видимому в Perl и, вероятно, обязательно реализуются в любых Perl-подобных процессорах.

Оригинальный синтаксис в Perl выглядит так

/<шаблон>/<модификаторы>
# например
/\d{4}-\d{2}-\d{2}/gm

# Можно явно указывать модификаторы для отдельно взятой части в шаблоне
# следующим образом
(?[-]<модификатор>...)<шаблон>
# например
 (?im)tvset(?-i)set
#^^^^^     ^^^^^
# Эти модификаторы действуют для шаблона с момента их ввода и до конца
# фрагмента. При этом модификаторы могут перекрывать друг друга. Знак '-' перед
# модификатором отключает модификатор.

# Для специальных групп модификатор указывается после знака вопроса
# например
(?i:tv)set

Использование модификаторов зависит от ситуации. В основном их используют, чтобы ускорить поиск, когда известны некоторые особенности текста. Ниже перечислены некоторые модификаторы, которые есть в Perl и PCRE.

  • g (от global) — данный модификатор говорит процессору регулярных выражений не останавливать обработку фрагмента, после того как было найдено первое совпадение по шаблону. Обычно этот модификатор используется по умолчанию в текстовых редакторах. При программировании иногда приходится его указывать явно, так как библиотеки, реализующие регулярные выражения, пытаются оптимизировать свою работу и не включают этот модификатор по умолчанию. Если он не будет включен, вы всегда будете получать только самую первую совпадающую подстроку, даже если их несколько в данном фрагменте.
  • i (от insensitive) — отключает чувствительность к регистру в шаблоне.
  • m (от multi line) — включает режим многострочности. Модификатор говорит процессору считать входящую строку многострочной, т.е. символ \n является разделителем строк. Без этого модификатора, по умолчанию, входящий текст считается однострочным, даже если в нем есть символы \n. В режиме многострочности процессор применяет шаблон к каждой строке отдельно. Метасимвол ^ указывает на начало обрабатываемой строки, а метасмивол $ — на позицию перед символом перевода строки, или на позицию, заканчивающую текст.
  • s (от single line) — данный модификатор влияет на поведение метасимвола точка (.). Если модификатор установлен, то метасимвол точка интерпретируется как любой символ, даже символ перевода строки.
  • x (от extended) — все неэкранированные пробелы, символы табуляции и пустые строки игнорируются, если не являются частью символьного класса []. Также игнорируются все символы между неэкранированным символом решетки # и следующим за ним символом перевода строки \n, если они не являются частью символьного класса, что позволяет игнорировать комментарии в тексте, оформленные через решетку.
  • A (от anchored) — соответствие по шаблону будет достигаться только, если последовательность литералов соответствует началу строки.
  • D (от dollar end only, в Perl отсутствует, но есть в PCRE) — метасимвол $ соответствует в шаблоне окончанию обрабатываемых данных. Игнорируется, если используется модификатор m.
  • U (от ungreedy) — инвертирует жадность квантификаторов: т.е. по умолчанию жадные квантификаторы становятся ленивыми, а ленивые — жадными.
  • X (от extra, в Perl отсутствует, но есть в PCRE) — если используется, то процессор регулярных выражений будет браковать шаблоны, в которых хотя бы один обратный слеш \ используется с символом, который не несет специального значения.
  • u (от UTF8, в Perl отсутствует, но есть в PCRE) — включает обработку шаблонов как UTF8 строк.

Реализации

править
  • NFA (Nondeterministic Finite State Machine; Недетерминированные Конечные Автоматы) используют «жадный» алгоритм отката, проверяя все возможные расширения регулярного выражения в определённом порядке и выбирая первое подходящее значение. NFA может обрабатывать подвыражения и обратные ссылки. Но из-за алгоритма отката традиционный NFA может проверять одно и то же место несколько раз, что отрицательно сказывается на скорости работы. Поскольку традиционный NFA принимает первое найденное соответствие, он может и не найти самое длинное из вхождений (этого требует стандарт POSIX, и существуют модификации NFA, выполняющие это требование — GNU sed). Именно такой механизм регулярных выражений используется, например, в Perl, Tcl и .NET.
  • DFA (Deterministic Finite-state Automaton; Детерминированные Конечные Автоматы) работают линейно по времени, поскольку не используют откаты и никогда не проверяют какую-либо часть текста дважды. Они могут гарантированно найти самую длинную строку из возможных. DFA содержит только конечное состояние, следовательно, не обрабатывает обратных ссылок, а также не поддерживает конструкций с явным расширением, то есть, не способен обработать и подвыражения. DFA используется, например, в lex и egrep.

Базовый практикум по регулярным выражениям

править

Для большинства новых пользователей регулярные выражения кажутся сродни высшей математики. К сожалению, единственный способ освоить их — это постоянно ими пользоваться. Данный раздел не является исчерпывающим пособием, но он дает вам небольшой начальный толчок, чтобы вы поняли основные принципы.

Каждое регулярное выражение выполняет конкретную задачу, поэтому мы не можем рассматривать приемы без приведения конкретных примеров. Для примеров мы будем предполагать синтаксис PCRE ввиду его широкой популярности. Если для примера используется другой синтаксис, то это будет явно подчеркнуто.

Для всех примеров мы будем полагать, что текст сохранен в файле с Unix-style окончаниями строк, т.е. перенос строки в них кодируется одной управляющей последовательностью \n. Это сделано для упрощения написания шаблонов. Если бы текст был сохранен в Windows-style, то пришлось бы каждый раз в шаблоне учитывать управляющую последовательность \r, кодирующую возврат каретки в начало.

Поиск подстрок

править

Давайте для примера возьмем код HTML-страницы, в которой нам, как разработчикам, понадобилось что-то поискать. Код страницы приведен ниже. Скопируйте текст в современный текстовый редактор, например Notepad++, и откройте окно для поиска. Переключитесь в режим поиска через регулярные выражения.

<!DOCTYPE html>
<html>
<head>
<style>
p.one {
  border-style: solid;
  border-color: #0000ff;
}
 
p.two {
  border-style: solid;
  border-color: #ff0000 #0000ff;
}
 
p.three {
  border-style: solid;
  border-color: #ff0000 #00ff00 #0000ff;
}
 
p.four {
  border-style: solid;
  border-color: #ff0000 #00ff00 #0000ff rgb(250,0,255);
}
</style>
</head>
<body>
 
<p class="one">One-colored border!</p>
<p class="two">Two-colored border!</p>
<p class="three">Three-colored border!</p>
<p class="four">Four-colored border!</p>
<p><b>Note:</b> The "border-color" property does not work if it is used alone. Use the "border-style" property to set the borders first.</p>
 
</body>
</html>

Допустим мы хотим поискать в этом тексте все коды цветов, например вот один из них #ff0000. Наша задача облегчается тем, что мы точно знаем что ищем и у нас есть все правила написания кода цвета, а именно:

  • цветовой код всегда начинается с символа решетки #;
  • цветовой код может состоять из 4-7 символов вместе с символом решетки (например #000 или #000000);
  • после решетки идут шестнадцатеричные цифры, т.е. допустимыми литералами могут быть символы цифр и буквы от A до F, причем регистр не имеет значения.

Взяв все правила и хорошо подумав, на ум приходит такое регулярное выражение, описывающее все приведенные правила.

#[a-fA-F0-9]{3,6}

Давайте разберем его по частям:

  • Первый символ # это простой литерал, так как мы знаем, что цветовой код всегда начинается с решетки.
  • [a-fA-F0-9] — это класс допустимых литералов. Здесь мы перечислили все литералы, которые могут быть раскрыты в позиции.
  • {3,6} — это квантификатор, ограничивающий возможную серию символов в искомой подстроке. Данный квантификатор ограничивает серию количеством не менее трех и не более шести.

Если прочитать регулярное выражение целиком, то получается: я хочу видеть подстроки, начинающиеся на #, после которого следует серия из 3-6 символов, среди которых могут быть только буквы a-f и A-F и цифры 0-9.

Это наверное наилучшее регулярное выражение для данной задачи, которое можно придумать. Тем не менее, разные люди думают по-разному, и с тем же успехом будет работать следующее регулярное выражение

#([a-fA-F]|[0-9]){3,6}

Это выражение сложнее предыдущего за счет того, что в нем используется группировка с альтернативой. Разберем выражение по частям:

  • Первый символ все тот же литерал решетки.
  • [a-fA-F]|[0-9] — альтернатива. Здесь мы говорим через альтернативу, что в позиции может быть раскрыта либо буквы a-f и A-F, либо цифры. Хотя для данной задачи писать так избыточно, на практике к такому методу прибегают, чтобы четко выделить классы допустимых литералов. Здесь мы выделили класс букв и класс цифр.
  • Далее мы заключили альтернативу в группу ([a-fA-F]|[0-9]). В данном примере мы так сделали потому, что квантификатор нельзя применять к альтернативам.
  • Наконец, квантификатор {3,6} выполняет ту же задачу что и раньше.

Более прямолинейный человек возможно бы использовал следующее регулярное выражение для решения поставленной задачи

#[a-fA-F0-9]{1,2}[a-fA-F0-9]{1,2}[a-fA-F0-9]{1,2}

Это выражение одновременно и простое и сложное. Так как мы знаем, что в RGB кодировке на кодирование цветового оттенка тратится 1 или 2 шестнадцатеричные цифры, то мы после решетки объявили три класса, каждый со своим квантификатором.

Мы показали, что одна задача может быть решена по разному, тем не менее, не все регулярные выражения работают одинаково быстро. Одни требуют большего числа проходов процессора по фрагменту текста, другие меньшего. Это следует помнить, когда вы используете регулярные выражения в своих приложениях.

Теперь выберем себе другую цель для поиска. Пусть это будут теги, например <p> или </p>. Задача кажется простой, потому что у тега всегда есть границы в виде знаков меньше < и больше >. На первый взгляд достаточно такого выражения

# НЕПРАВИЛЬНО
<.*>

Здесь < и > это простые литералы. Метасимвол точка . означает любой символ (кроме управляющих последовательностей \n и \r) и метасмивол * — жадный квантификатор, так как число символов в теге в теории не ограничено. Однако на практике мы столкнемся с проблемой жадной квантификации, которая будет захватывать строки типа

<p class="one">One-colored border!</p>

Чтобы не захватывать текст, что находится между тегами, нужно использовать ленивый квантификатор *?, который будет захватывать подстроки во фрагментах до первого совпадения (наименьшая возможная длина результата). Окончательный вариант поиска любых тегов будет таким

# ПРАВИЛЬНО
<.*?>

Давайте усложним задачу и будем искать пары тегов вместе с содержимым. Хотя выражение <.*> кажется подходящим для этой задачи, оно не учитывает, что содержимое между парой тегов может быть многострочным. Так как конечный результат будет сложным, мы будем приходить к нему постепенно.

В первом приближении выражение должно выглядеть так

<(\w+).*>.*</\1>

Здесь мы используем группу (\w+), которая захватывает имя тега, когда он открывается. Так как имя не может быть пустым, мы используем квантификатор +, который требует, чтобы серия символов состояла по меньшей мере из одного символа. Остальное вам должно быть уже знакомо. Обратите внимание, как мы захватываем закрывающий тег. Так как мы знаем, что закрывающий тег всегда парен открывающему с той лишь разницей, что в нем есть еще символ слеша, мы можем сослаться на группу по ссылке (\1). Это позволяет нам привязать результат группы в открывающем теге к закрывающему тегу.

К сожалению, данное регулярное выражение все еще грубое и не учитывает некоторые нюансы. Так, между парами тегов в содержимом может попасться символ > (например <p>5 > 2</p>), который приведет к неправильному захвату границы открывающего тега. Однако, мы знаем, что внутри тега этот символ встречаться не может, поэтому мы можем сделать регулярное выражение строже следующим образом

<(\w+)\b[^>]*>.*</\1>

Обратите внимание, что мы добавили инвертированную группу [^>]* внутри открывающего тега, чтобы лучше обозначить границу открывающего тега. Мы также используем мнимый метасимвол \b, чтобы учесть любое количество пробельных символов между именем тега и любого его атрибута, если он там есть.

Казалось бы все хорошо, но теперь регулярное выражение не учитывает, что между парой тегов содержимое может быть многострочным. Самый простой метод обозначить многострочную строку через регулярное выражение следующий

(?:.|\n)*?
# или следующий вариант, когда группа без захвата не поддерживается
(.|\n)*?

Мы используем группу без захвата, так как ей не нужно тратить память на хранение результата раскрытия (небольшая оптимизация). Объединяя все вместе, конечный результат будет выглядеть так

<(\w+)\b[^>]*>(?:.|\n)*?</\1>

Данное регулярное выражение описывает любую пару тегов вместе с их содержимым, однако это выражение не учитывает возможную вложенность. Если вы попытаетесь применить его в лоб, то единственным результатом будет пара <html>...</html>, а все что вложено в нее искаться не будет. К сожалению, проблему вложенности регулярными выражениями решить невозможно. На практике обычно поступают так:

  • Программируют рекурсию. Другими словами данный шаблон нужно применять каскадом: сначала захватить внешнюю пару и извлечь ее тело; затем применить шаблон к извлеченному телу и так далее. Шаблон применяется пока есть результаты.
  • Если нужно искать конкретные теги, то группу в открывающем теге нужно написать конкретнее. Например, если нам нужны только теги <b> и <style>, то регулярное выражение может быть записано конкретнее:
    <(style|b)\b[^>]*>(.|\n)*?</\1>

Поиск слов-дубликатов

править

Следующий пример демонстрирует работу модификаторов шаблонов, когда они поддерживаются. Часто бывает, что по невнимательности люди пишут одно и то же слово в тексте дважды подряд. В больших текстах искать такие ошибки глазами становится трудновато. Однако такие ошибки легко искать регулярными выражениями.

Допустим у нас такой текст

HeLLo hello Hello hEllo HELLO Hello world

Наша задача найти все повторы слов подряд. Самое простое это попробовать искать повторы слов попарно. На первый взгляд нас устраивает такое регулярное выражение

(\w+)\s+\1

Здесь мы через группу захватываем первое слово в повторе, затем мы используем \s+, чтобы учесть один и более пробелов между повторами, и наконец мы делаем ссылку на группу, чтобы раскрыть ее результат в позиции ссылки.

Единственной проблемой является то, что слова могут быть набраны символами в разном регистре. Для нашей задачи регистр нужно игнорировать. Для этого мы должны воспользоваться модификатором следующим образом.

Модификатор i говорит процессору игнорировать регистр
(?i)(\w+)\s+\1

или так, когда модификатор g по умолчанию отключен
/(?i)(\w+)\s+\1/g

или со всеми модификаторами в одном месте
/(\w+)\s+\1/gi

Для нашего примера будет найдено три совпадения для данного шаблона

HeLLo hello Hello hEllo HELLO Hello world

Обрезание лишних пробелов

править

На практике очень часто возникает задача обрезки лишних пробелов вокруг отдельных слов, передаваемых фрагментами. Снизу показан пример из трех строк, вокруг которых серым выделены лишние пробелы, которые мы намереваемся удалить.

        Два слова          
             Два слова     
                  Два слова

Прежде чем их удалить, их нужно сначала найти. Чтобы искать такие пробелы, необходимо чтобы процессор регулярных выражений работал в многострочном режиме. В этом случае мы сможем использовать такое регулярное выражение

/^\s+|\s+$/m

В этом примере мы явно указали модификатор m, который переключает процессор в многострочный режим. Здесь используется альтернатива, в первом варианте которой анализируется начало фрагмента, а во втором варианте конец фрагмента. Мы используем метасимволы ^ и $, чтобы захватывать пробельные символы только между границами фрагмента и первым от каждой границы словом.

Валидатор на основе регулярного выражения

править

Безопасная строка

править

Некоторые программные валидаторы могут быть построены через регулярные выражения. Они работают просто: если переданный пользователем (или другой программой) текст удовлетворяет регулярному выражению (т.е. что-то находится), то проверка считается пройденной. Такие реализации, как правило, медленные с точки зрения производительности, но быстрые с точки зрения их написания.

Типичным примером такого валидатора является проверка на безопасную строку, когда приложение ограничивает использование входящих от клиентов символов. Это требуется для защиты от всевозможных инъекций. Построить такой валидатор очень просто через класс допустимых литералов, например так

^[a-zA-Z0-9:!@#$%^&~+-\{\}() ]{0,1024}$

Обратите внимание, что мы проверяем весь входящий фрагмент, отметив границы метасимволами ^ и $. Далее мы объявляем класс литералов, в который мы помещаем все символы, которые с точки зрения приложения безопасны. Квантификатором мы ограничиваем длину строки, так как обычно входящие буферы ограничены по длине.

Опережающие проверки

править

Следующее регулярное выражение взято из жизни и демонстрирует специальную группу с положительной опережающей проверкой. Одной из тривиальных проверок является проверка сложности пароля. Как правило к паролю предъявляют такие требования:

  • длина не меньше 8 символов;
  • должен иметь по меньшей мере одну букву;
  • должен иметь по меньшей мере одну цифру;
  • должен иметь по меньшей мере один из символов !@#$%^&*.

Такую задачу можно решить следующим регулярным выражением

(?=.*[0-9])(?=.*[!@#$%^&*])(?=.*[a-z])(?=.*[A-Z])[0-9a-zA-Z!@#$%^&*]{8,}

Здесь используется 4 группы с положительной опережающей проверкой, каждая из которых проверяет утверждение должен иметь по меньшей мере .... Мы не можем их в данном случае не использовать, так как простое выражение [0-9a-zA-Z!@#$%^&*]{8,} не страхует нас от таких паролей 11111111 или password.

В данном случае вам нужно понять как работает данная группа в контексте выражения:

  • (?=.*[0-9]) — проверяет, что фрагмент имеет хотя бы одну цифру. Если нет, то обработка по регулярному выражению прервется на этом месте, иначе произойдет переход на следующую по порядку группу.
  • (?=.*[!@#$%^&*]) — аналогично, но проверяет наличие хотя бы одного специального символа.
  • (?=.*[a-z])(?=.*[A-Z]) — проверяют наличие хотя бы одной буквы.
  • [0-9a-zA-Z!@#$%^&*]{8,} — если все опережающие группы отработали успешно, то проверяется основное регулярное выражение, которое в данном случае проверяет длину.

Обратите внимание, что позитивная проверка означает, что результат раскрытия признается успешным, когда есть хотя бы одно вхождение во фрагменте по указанному шаблону. Данная проверка обрабатывает фрагмент с позиции, в которой она объявлена в шаблоне, в сторону конца фрагмента. Если говорить просто, то слева направо.

Фильтрация данных

править

Пусть нам дан такой текст

file.doc
table.xls
  archive1.zip   
file2.txt
archive2.rar   
table2.xls
music.mp3
   zip
rar
7z    
    archive3.tar.gz    
some_line
archive4.7z
archive5.tar.bz    archive6.zip     tar

Как мы видим, текст представляет собой мешанину из имен файлов. Пусть нам требуется захватить только подстроки с именами архивов, т.е. это файлы с расширениями zip, rar, tar, 7z, gz, bz. Все остальные серии символов мы должны игнорировать.

Чтобы оптимизировать процедуру отсеивания, здесь следует использовать группу с опережающей проверкой. В этой группе мы должны проверять основной шаблон, и если проверка в шаблоне будет успешной, захватить все символы. Обрабатывать текст мы будем также построчно.

/(?=\b[^\s]+\.(zip|rar|tar|7z|gz|bz)\b)[^\s]+/gm

В этом шаблоне мы используем опережающую проверку, чтобы проверить расширение файла. Если расширение совпадает с одним из объявленных вариантов, то мы захватываем подстроку целиком.

Литература

править

Ссылки

править