1 1 1 1 1 1 1 1 1 1 Рейтинг 0.00 (0 Голоса(ов)

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

Как выглядят регулярные выражения в глазах человека, впервые с ними столкнувшегося?

Примерно вот так:

Код RegExp:
무의미한 문자 세트

или, в лучшем случае, так:

Код RegExp:
مجموعة أحرف لا معنى لها

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

Так что такое регулярное выражение? Прежде всего это - шаблон. Шаблон, которым мы пытаемся наложить на наши данные, пытаясь выяснить, соответствуют ли эти данные этому шаблону. С помощью этого шаблона мы можем разбивать наши данные на части, объединять в другом порядке, добавлять, либо убирать часть данных.

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

  • Оканчиваться регулярное выражение должно тем же символом, что и начато
  • В тексте регулярно выражения не должные встречаться символы, являющиеся ограничителем, либо эти символы должны быть экранированы (об этом позже)

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

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

Код RegExp:
/текст регулярного выражения/модификаторы

Так, мы с вами обратили внимание, что существуют еще какие-то загадочные модификаторы. А я про них ничего еще не говорил. Обязательно скажу! Ведь именно модификаторы указывают парсеру, КАК именно рассматривать строку при поиске соответствия шаблону. Пока же мы их просто не будем использовать.

Начнем с простого:

^ - начало строки

$ - конец строки

А теперь составим совсем простые регулярные выражения:

/^a/ - символ a, стоящий в начале строки (но не строка, начинающаяся с a) - aaa aaa

/a$/ - символ a, стоящий в концестроки (но не строка, оканчивающаяся с a)- aaa aaa

А что делать, если нам надо выделить не один символ в начале/конце строки, а два, три, или просто все?

Для этого используются такие конструкции (называемые квантификаторами):

  • Символ + стоящий за смысловой конструкцией означает "повторенная подряд любое число раз более нуля".
    /^a+/ - все символы a в начале строки - aaa aaa и
    /a+$/ - все символы a в начале строки - aaa aaa
  • Конструкция {n} стоящая за смысловой конструкцией означает "повторенная подряд n раз".
    /^a{2}/ - 2 символа a в начале строки - aaa aaa и
    /a{2}$/ - 2 символа a в начале строки - aaa aaa

На самом деле конструкция {n} является частным случаем конструкции {n,m}, которая означает "от n до m раз". В случае необходимости m может быть опущен, и тогда смысл конструкции зависит от того, оставили ли мы запятую, или убрали. Как я уже упоминал {n} означает "n раз", а вот {n,} означает уже "n раз, либо больше".

Для распространенных случаев существуют короткие записи.

{0,1} - одно вхождение, либо нет вхождения может быть записано как ?

{1,} - одно, или более вхождение (его мы уже использовали) - +

{0,} - любое число вхождений, включая отсутствие - *

Кроме начала строки можно указать

  • начало всего текста - \A - /\Aa/ - символ a в начале текста
    aaa aaa
    aaa aaa                                                                                                    
  • конец текста - \z - /a\z/ - символ в конце текста
    aaa aaa
    aaa aaa
  • граница слова (обратите внимание, что для начала слова использует то же значение, что для начала текста, и что нужно найти определяется из положения в шаблоне) -\b
    /a\b/ - символ a в конце слова - aaa aaa
    /\ba/ - символ a в начале слова - aaa aaa
  • не граница слова - \B - /\Ba\B/ - aaa aaa
  • \< - начало слова (работает не везде)
  • \> - конец слова (работает не везде

С этим, вроде, разобрались. Но вот проблема - ведь в алфавите существуют и другие буквы, кроме a. Мало того, эти буквы бывают еще и строчными и заглавными. А еще есть цифры и знаки препинания. Неужели регулярные выражения способны искать только одну букву? Нет, конечно!

  • . (точка) - обозначает "любой символ, кроме перевода строки". 
  • \n - перевод строки
  • \r - возврат каретки
  • \f - разрыв страницы
  • \t - табуляция
  • \v - вертикальная табуляция
  • \xhh - символ с шестнадцатиричным кодом hh
  • \Oooo - символ с восьмиричным кодом ooo
  • (n|m) - строка n, либо строка m, например (ab|cd) - либо "ab", либо "cd". Если является самостоятельным шаблоном, то может быть записан без скобок /[a-z]{10}|[A-Z]{5}/ - либо пять заглавных букв, либо десять прописных.
  • [набор] - обозначает "любой символ из набора". При этом можно испольльзовать дефис - для задания диапазона. Например запись [a-zA-Z] означает "любая буква английского алфавита".
  • [набор1[набор2]] = [набор1набор2] - объединение наборов, например [a-d[x-z]] = [a-dx-z]
  • [набор1&&[^набор2]] - вычитание наборов, например [a-z&&[^cd]] - любой символ от a до z, кроме c и d
  • [^набор] - обозначает "любой символ, кроме символа из набора".
  • POSIX-наборы:
    • [:upper:] - [A-Z]
    • [:lower:] - [a-z]
    • [:alpha:] - [a-zA-Z]
    • [:alnum:] - [a-zA-Z0-9]
    • [:digit:] - [0-9]
    • [:xdigit:] - [0-9a-fA-F] - для записи шестнадцатиричных чисел
    • [:punct:][.,!?:…]
    • [:blank:][ \t] - пробел и табуляция
    • [:space:][ \t\n\r\f\v]
    • [:cntrl:][\x00-\x1F\x7F]
    • [:graph:][a-zA-Z0-9.,!?:…]
    • [:print:][\x20-\x7E]
  • Есть предопределенные наборы:
    • \d = [0-9] - любая цифра
    • \D = [^\d] = [^0-9] - любой символ, кроме цифры
    • \s = [\t\v\r\n\f] - любой пробельный символ. Перевод строки может быть включен в пробельные символы, либо исключен в зависимости от наличия модификатора s.
    • \S = [^\s][^\t\v\r\n\f] - любой не пробельный символ
    • \w = [0-9a-zA-Z_] - любой символ латиницы, цифра, либо подчеркивание
    • \W = [^\w] = [^0-9a-zA-Z_] - любой символ не являющийся буквой латиницы, цифрой, либо символом подчеркивания

Я уже ранее упоминал про экранирование символов. Пришла пора поговорить об этом конкретнее. Если мы посмотрим на первых пункт приведенного списка, то увидим, что точка выполняет особую роль. Но что делать, если в нашем регулярном выражении нам необходимо искать точку?

Так вот, ее нужно экранировать. Экранирование символа выполняется с помощью обратного слеша. В итоге точка будет выглядеть так: \.

Точно так же, если нам необходимо использовать символ \, то мы напишем \\.

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

А теперь пройдемся еще дальше по списку и увидим выражение в скобках. Там его привел для того, чтобы сгруппировать строки через | ("или"). Однако, у круглых скобок есть еще одно замечательное свойство. Они "запоминают" часть регулярного выражения. Сослаться на запомненную часть можно с помощью конструкции \n, где n - номер запомненной части (нумерация идет слева направо и с единицы). Думаю, тут просто необходим пример:

/(\d)\d([a-z])\1\2/ - как мы видим, у нас первым символом ожидается цифра. Она окружена круглыми скобками, то есть "запомнена". Так как она первая слева, то и имеет номер 1. Вторым символом ожидается также цифра. Она не окружена скобками, то есть не участвует в запоминании и к ней нельзя обратиться. Третьей символом ожидается прописная латинская буква. Она запомнена и запомнена под номером 2. А дальше уже идет обращение к запомненным "кусочкам". Четвертым символом ожидает тот же символ, что и на первой позиции. А пятым - тот же, что на третьей. То есть строка "11a1a" соответствует шаблону, строка "12a1a" - тоже, а вот строка "21a1a" - не соответствует.

Следует учитывать, что то выражение с "или" также сохраняется. Если мы хотим отказаться от сохранения, то мы может поставить после открывающей скобки ?: таким вот образом: /(?:\d)\d([a-z])\1/. В этом случае строки "12aa" и "21aa" будут соответствовать шаблону, а "11ab" - нет.

Конструкции (?что-нибудь ) вообще очень интересные. Одна из них: (?# комментарий).

А теперь мы научимся заглядывать впереди и назад.

Предположим, нам надо найти цифру, за которой идет латинская буква, но так, чтобы сама буква в результаты поиска не попала. Мы можем написать так: /\d[a-z]/, но тогда буква в результат поиска попадает (1a), можем написать /\d/, но тогда найдется любая цифра, даже та, за которой стоит не буква. Для этого мы заглянем вперед с помощью конструкции (?= ): /\d(?=[a-z])/. В том числе мы может написать и так /(\d(?=[a-z]))/ и тогда в \1 у нас вернется найденная цифра без следующей за ней буквой. Точно так же мы можем заглянуть вперед в поисках цифры за которой следует не буква с помощью конструкции (?! )/\d(?![a-z])/.

Точно так же можно заглядывать и назад с помощью немного странноватых конструкций (?<= ) и (?<! ) соответственно. Поиск цифры, перед которой стоит буква: /(?<=[a-z])\d/. Поиск цифры, перед которой стоит не буква: /(?<=[a-z])\d/.

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

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

/\(.+\)/ - выберет всю строку (012)345(678).

Ленивыми эквивалентами этих квантификаторов являются соотвественно: *?, +?, ??, {}?. Они будут стараться выбрать минимальную строку, которая удовлетворяет шаблону.

/\(.+?\)/ - выберет всю строку (012)345(678).

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

Я еще в самом начале обещал рассказать про модификаторы. Модификаторы обычно распространяются на весь шаблон и записываются после закрывающего ограничителя. Однако, можно в любой момент включить модификатор прямо внутри шаблона с помощью конструкций: (?i) (?s) (?m) (?g) (?x) (?r). Так же модификатор можно и выключить: (?-i) (?-s) (?-m) (?-g) (?-x) (?-r). Более того, можно одновременно включать часть модификаторов, одновременно выключая другие: (?i-sm). А можно даже использовать модификатор исключительно внутри группы: (?i-s:шаблон), например если общий модификатор указывает не различать регистры, а к конкретном месте это требуется, то можно написать (?-i:a) - принимать только маленькую букву а, но не A.

А теперь расшифровка этих загадочных букв. Они тоже могут содержать перед собой знак "минус" (за исключением модификатора U), который указывает на отключение данного модификатора в шаблоне:

  • g - Глобальный поиск.
  • i - Поиск без учета регистра.
  • m - По умолчанию PCRE обрабатывает данные как однострочную символьную строку (даже если она содержит несколько разделителей строк). Метасимвол начала строки '^' соответствует только началу обрабатываемого текста, в то время как метасимвол "конец строки" '$' соответствует концу текста, либо позиции перед завершающим текст переводом строки (в случае, если модификатор D не установлен). В Perl ситуация полностью аналогична. Если этот модификатор используется, метасимволы "начало строки" и "конец строки" также соответствуют позициям перед произвольным символом перевода и строки и, соответственно, после, как и в самом начале и в самом конце строки. Это соответствует Perl-модификатору /m. В случае, если обрабатываемый текст не содержит символов перевода строки, либо шаблон не содержит метасимволов '^' или '$', данный модификатор не имеет никакого эффекта.
  • s - Текст рассматривается как одна большая строка, при этом перевод строки будет учитывать в \s
  • u - Используется кодировка UTF-8
  • U - Инвертировать жадность
  • x - Если используется данный модификатор, неэкранированные пробелы, символы табуляции и пустой строки будут проигнорированы в шаблоне, если они не являются частью символьного класса. Также игнорируются все символы между неэкранированным символом '#' (если он не является частью символьного класса) и символом перевода строки (включая сами символы '\n' и '#'). Это эквивалентно Perl-модификатору /x, и позволяет размещать комментарий в сложных шаблонах. Замечание: это касается только символьных данных. Пробельные символы не фигурируют в служебных символьных последовательностях, к примеру, в последовательности '(?(', открывающей условную подмаску.

Оставьте свой комментарий

Оставить комментарий как гость

0
  • Комментариев нет
Dx WW