Регулярные выражения для новичков. Практическое введение в регулярные выражения для новичков

Приведём несколько примеров регулярных выражений.

    карова - очевидно, шаблон, под который подходит слово карова;

    \b(shift|unshift|pop|push|splice)\b - любое из перечисленных слов;

    ^\s+ - один или несколько пробелов или знаков табуляции, стоящих в начале строки.

В регулярных выражениях алфавитно-цифровые символы обычно обозначают сами себя. Например, шаблон Hello указывает искать символ H , за которым следует e , затем l и т. д.

Если символ трудно или неудобно задать буквально, можно использовать уже известные нам литералы: \n , \t и другие. Это означает, что знак \ , входящий в регулярное выражение, уже не может обозначать сам себя, поскольку он меняет смысл следующего за ним символа: в частности, буква n , вместе с предшествующим знаком бэкслэш обозначает символ конца строки. Если требуется включить в шаблон знак \ как таковой, следует использовать литерал \\ .

Имеются и другие символы, которые в шаблонах получают особый смысл вместо того, чтобы обозначать самих себя. Такие символы называются метасимволами . Приведём несколько примеров метасимволов, не указывая пока их особый смысл (список не является исчерпывающим): \.-(){}?*+^$| .

Некоторые символы оказываются метасимволами не всегда, а только тогда, когда попадают в определённый контекст. У некоторых метасимволов в зависимости от контекста оказывается разный смысл.

Если нужно вставить в регулярное выражение метасимвол, лишив его особого смысла, его следует защитить (экранировать ), поставив перед ним бэкслэш. Например, знак плюса в регулярное выражение вставляется как \+ .

Шаблон обозначает один из символов, перечисленных в квадратных скобках. Если, к примеру, нас интересует слово Hello , неважно, с большой или маленькой буквы, шаблон будет таким: ello . Вот шаблон, обозначающий маленькую гласную букву английского алфавита: . Ещё один пример - символьный класс, состоящий из обеих квадратных скобок: [\[\]] .

Если символьный класс включает символы, идущие подряд в кодовой таблице, достаточно указать первый и последний символы, вставив между ними дефис. К примеру, класс, обозначающий любую десятичную цифру, можно задать как . Буква английского алфавита обозначается как (здесь мы полагаемся на тот факт, что в любой кодовой таблице заглавные и маленькие английские буквы идут непрерывными блоками в алфавитном порядке; однако блок маленьких букв не следует сразу за блоком заглавных).

Можно определить символьный класс, состоящий из всех символов за исключением перечисленных - так называемое отрицание символьного класса . Для этого сразу за открывающей квадратной скобкой перед перечислением вставляется знак циркумфлекса ^ . Любой символ, не являющийся цифрой, можно обозначить как [^0-9] .

Для некоторых популярных классов символов есть специальные обозначения:

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

Наиболее часто используемые анкеры - привязка к началу (^) и концу ($) строки. Привязка к началу строки должна помещаться в начале шаблона, а привязка к концу - в конце.

Например, к шаблону ^анти подходят такие слова русского языка, как антидот, антисемитизм или античастица. Без привязки также подошли бы строки, не начанающиеся с «анти-», но содержащие это буквосочетание внутри, например, меркантилизм. Для поиска слов, заканчивающихся на «-ться» нужен шаблон ться$ (мы уверены почти на 100%, что все такие слова - это возвратные глаголы в инфинитиве). Ничто не мешает применять в шаблоне обе эти привязки.

Другой полезный анкер - привязка к границе между словами \b . Он соответствует месту в строке, находящемуся между символами, один из которых принадлежит классу \w , а другой - \W (в любом порядке). Эта привязка может соответствовать также началу или концу строки (в этом случае считается, что срока как бы окружена воображаемыми символами из класса \W).

Если мы ищем фрагмент, который должен подойти под один из нескольких шаблонов, нужно перечислить эти шаблоны, разделив вертикальной чертой | . Например, понедельник|вторник|среда|четверг|пятница|суббота|воскресенье. Для того, чтобы сделать список альтернатив самостоятельной единицей и отделить от соседних, его нужно заключить в скобки. Например, шаблон Уважаем(ый|ая) означает строку Уважаем, за которой следует одна из строк ый или ая. Без скобок шаблон Уважаемый|ая обозначал бы одну из строк Уважаемый или ая. У скобок имеется важное побочное действие, о котором будет сказано в разделе «Группировка и захват» .

Для того, чтобы указать, сколько раз может повторяться шаблон, после него ставят так называемые квантификаторы (от латинского слова quantum - сколько):

Квантификаторы * , + и? являются избыточными, поскольку они могут быть выражены иначе с помощью фигурных скобок. А именно, * равносилен {0,} , + равносилен {1,} , а? - то же самое, что и {0,1} . Но данные квантификаторы очень часто используются, и этим заслужили отдельных обозначений.

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

Вот несколько примеров:

    ^\d+$ - последовательность из одной или нескольких десятичных цифр (шаблон для целых неотрицательных чисел в десятичной записи);

    ^\-?\d+$ - то же самое, но для всех (возможно, отрицательных) целых чисел;

    ^\-?(\d+(\.\d*)?|\.\d+)$ - шаблон для вещественных чисел;

Разберём последний пример подробнее. Помимо необязательного минуса в начале, в шаблоне присутствует группа с двумя альтернативами: \d+(\.\d*)? и \.\d+ . Первая альтернатива включает обязательную целую часть \d+ (как минимум одна цифра), и следующую за ней необязательную дробную (\.\d*)? . В дробной части, если она есть, имеется десятичная точка, и, возможно, несколько цифр. Таким образом, этой альтернативе соответствуют строки 15 , 15. , 15.487 . Другая альтернатива нужна для строк вида.618 с отсутствующей целой частью - во многих компьютерных языках эта запись имеет право на существование.

Если простейшие элементы регулярного выражения - символы, символьные классы и анкеры, записываются подряд, это означает, что при поиске в строке по шаблону эти элементы будут сопоставляться с частями строки последовательно, в той же последовательности. Этот порядок нарушается, если применяются альтернативы. Можно представлять себе, что составное регулярное выражение составляется из простейших при помощи двух операций: последовательного соединения (композиции ) и альтернативы. Композиция - аналог операции умножения в арифметике. Альтернатива - аналог сложения. Первое сходство с арифметикой состоит в том, что операция альтернативы имеет более низкий приоритет, чем композиция, поэтому могут потребоваться скобки для группировки, как в примере Уважаем(ый|ая) .

Примечание

Многие, хотя и не все, законы арифметики действуют и для регулярных выражений:

коммутативность альтернативы x | y = y | x ; ассоциативность альтернативы x | y | z = x | y | z ; ассоциативность композиции x ⁣ y ⁣ z = x ⁣ y ⁣ z ; дистрибутивность альтернативы относительно композиции (левая и правая) x ⁣ y | z = x ⁣ y | x ⁣ z , x | y ⁣ z = x ⁣ z | y ⁣ z .

В этой странной арифметике регулярных выражений не имеет место закон коммутативности для композиции. Кроме того, отсутствует аналог нуля из-за очевидного соотношения x | x = x . Роль единицы (правой и левой) для композиции выполняет пустой шаблон (обозначим его 𝟙): 𝟙 ⁣ x = x ⁣ 𝟙 = x . Квантификаторы вида { n } играют роль возведения в n -ю степень.

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

А, может быть, этилендиамин- N N N ′ N ′ -тетрауксусной кислоты?

Рассмотрим пример, в котором в тексте отыскиваются упоминания различных кислот. Наши школьные воспоминания из химии навели нас на мысль, что названия кислот оканчиваются или на вая, или на ная, или на тая, а затем, после пробела, следует слово кислота. Составляем шаблон: \S+[внт]ая кислота. Сопоставляем с шаблоном текст. Удача! Но, спрашивается, упоминание какой именно кислоты нашлось в тексте? Соляной? Серной? Азотной? Плавиковой? Хлорной? Хлорноватой? Хлорноватистой? Лимонной? Синильной? Дезоксирибонуклеиновой?

Вот здесь пригодится захват. Ту часть шаблона, который, по нашему замыслу, должен соответствовать названию, заключим в скобки: (\S+[внт]ая) кислота. Тогда машина, найдя в тексте упоминание кислоты, сохранит её название (то, что соответствует заключённому в скобки фрагменту шаблона) в специальной переменной - буфере захвата

Регулярное выражение может содержать несколько групп захвата. Такие группы могут не только следовать друг за другом, но и вкладываться одна в другую. Иными словами, регулярное выражение должно быть сбалансировано по отношению к круглым скобкам в том же смысле, какой обсуждался в главе 23. «Проверка баланса скобок » (конечно, это относится только к круглым скобкам, служащим цели группировки и захвата; скобки, которым предшествует бэкслэш, не сказываются на балансе групп). При успешном поиске каждая группа захватит какую-то часть текста: первая - в первый буфер, вторая - во второй, и так далее. Как же нумеруются группы в случае, когда они вложены друг в друга? Нумерация идёт в том порядке, в каком появляются открывающие скобки:

2 4 5 ┝┑ ┝┑┝┑ (()(()())) │ ┝━━━━┙│ │ 3 │ ┝━━━━━━━━┙ 1

При желании группу можно исключить из нумерации, то есть лишить её «захватнической» функции, оставив только группирующую. Для этого вместо ограничителей группы (⋯) используем (?: ⋯) . Здесь вопросительный знак не обозначает квантификатор, поскольку квантификатору должны предшествовать либо символ, либо символьный класс, либо группа.

Использование нумерованных групп захвата не всегда удобно, особенно в больших регулярных выражениях. Стоит только вставить в шаблон новую группу захвата, как нумерация сбивается. Тогда придётся во всех местах в программе, где происходит обращение к буферам захвата по номерам, вносить исправления. Но можно связать с группой имя, которое позволит обратиться к соответствующему буферу по этому имени. Для создания именованной группы используются ограничители (? ⋯) , где вместо name подставляется нужное имя.

Захваченные в буферы части строки могут использоваться двумя путями. Во-первых, программа, использующая регулярное выражение для поиска или замены, может обратиться к буферам как к специальным переменным. О таком использовании речь пойдёт в разделе «Операторы поиска и замены» . Вторая возможность предусматривает использование ссылок на группы прямо в регулярном выражении, см. раздел «Обратные ссылки» .

Рассмотрим задачу поиска слов, содержащих три одинаковые гласных буквы подряд. Наивное решение [аеёиоуэюя]{3} , использующее квантификаторы, не будет работать, поскольку такому шаблону соответствуют строки с тремя подряд идущими гласными, но необязательно одинаковыми. Чудовищное решение с полным перечислением альтернатив, ааа|еее|ёёё|иии|ооо|ууу|эээ|ююю|яяя, мы с негодованием отвергаем: ведь стоит взять другой, более обширный символьный класс, или заменить тройку в квантификаторе на большее значение, как размер шаблона катастрофически вырастет.

Тем не менее возможно элегантное решение, использующее группы захвата. Захватим гласную в группу, а затем сошлёмся на содержимое буфера захвата. Ссылки на первый, второй, третий буферы записываются в регулярном выражении как \g1 , \g2 , \g3 . Итак, решением будет шаблон ([аеёиоуэюя])\g1{2} . Обратите внимание, что ссылка на буфер захвата должна следовать в регулярном выражении строго после соответствующей группы.

Обратные ссылки могут ссылаться не только на нумерованные буферы, но и на именованные. Такие ссылки имеют вид \k , где, опять же таки, вместо name стоит конкретное имя. Наш пример можно переписать, применяя именованные группы: (?[аеёиоуэюя])\k{2} (vowel - гласная).

Иногда возникает необходимость в поиске, при котором не делается отличий между строчными и прописными буквами. Такой поиск называется нечуствительным к регистру (case-insensitive ). Вместо того, чтобы всюду в шаблоне заменять буквы на двухбуквенные классы (a → , b → , …), Просто заключим шаблон в специальную группу, включающую режим case-insensitive поиска: (?i: ⋯) . Такая группа не является группой захвата. Если case-insensitive поиск должен быть реализован только в части регулярного выражения, в группу следует поместить только нужную часть.

Наоборот, если какая-то часть регулярного выражения, в которой осуществляется case-insensitive поиск, нуждается в отключении этого режина, то вернуться к обычному, case-sensitive поиску можно, используя группу (?-i: ⋯) .

Режимы чувствительности/нечуствительности к регистру влияют лишь на буквы. Что считается буквой, а что нет, зависит от языка, как и правила соответсвия между прописными и строчными буквами. С точки зрения английского языка, например, буквой не является символ Щ. В немецком языке имеется буква ß (между прочим, заглавный вариант этой буквы состоит из двух букв SS: Carl Friedrich Gauß → CARL FRIEDRICH GAUSS).

Секреты регулярных выражений (regular expressions)

Часть 1. Диалекты и возможности. Составление регулярных выражений

Серия контента:

1. Введение. Используем ли мы регулярные выражения в полной мере?

Если задуматься над вопросом: "А что такое "регулярное выражение" вообще?", то ответ найдётся не сразу. Можно сказать, что это специализированный язык описания символьного шаблона (последовательности символов) поиска в строках текста. Здесь важно то, что при поиске совпадений выполняется именно посимвольное сравнение. Автор энциклопедии по регулярным выражениям (Mastering Regular Expressions) Джеффри Фридл (J.E.F. Friedl) советует развивать привычку буквально интерпретировать регулярные выражения. Например, глядя на шаблон "^cat", обозначающий "строка должна начинаться со слова cat", следует рассуждать так: "совпадение будет найдено, если мы находимся в начале строки и обнаруживаем символ c, непосредственно за которым располагается символ a, сразу после которого находится символ t". Это позволяет максимально точно оценить смысл и сущность регулярного выражения.

Большинство пользователей знают, что для поиска достаточно задать слово-образец. Например, в Web-браузере в поле "Поиск" после ввода "Linux" вы получите длинный список ссылок на страницы, в тексте которых найдено совпадение с заданным шаблоном "Linux". В локальной файловой системе используется команда grep "Linux" или графические средства поиска.

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

2. Различные диалекты регулярных выражений. Соответствие стандарту POSIX

Вообще говоря, существуют два основных диалекта (или типа) регулярных выражений: простые и расширенные. При этом граница между ними является условной и со временем становится всё менее чёткой.

Программы vi(m), sed, grep, less, ed, expr, lex понимают только простые регулярные выражения, а утилиты (g)awk, egrep, а также интерпретаторы языков Perl, Tcl, Python – расширенные регулярные выражения. В то же время в каждой из программ существуют собственные усовершенствования, т.е. создаются поддиалекты регулярных выражений. Рассмотрим сходства и различия этих диалектов.

2.1. Общая схема регулярного выражения

Как правило, регулярное выражение состоит из трёх основных частей:

  1. Якорь – определяет позицию шаблона в строке текста:
    • ^ – якорь, определяющий начало строки;
    • $ – якорь, определяющий конец строки.
  2. Набор (последовательность) символов – для поиска соответствий в заданных позициях строки текста:
    • символ "точка" (.) соответствует любому произвольному символу;
    • алфавитно-цифровые символы и пробел представляют сами себя;
    • прочие символы – интерпретация зависит от диалекта.
  3. Модификатор – задаёт количество повторов предыдущего символа или набора символов (в зависимости от диалекта):
    • * – любое количество повторов символа/набора, в том числе и нулевое;
    • ? – соответствует нулю или одному экземпляру символа/набора;
    • + – соответствует одному или большему количеству экземпляров символа/набора.

Пример: необходимо найти все директивы определения макроконстант в исходном коде на языке С.

grep "^ *#define.*" *.c *.h

Здесь учтено, что в начале строки макроопределения может быть вставлено любое количество пробелов или же пробелы отсутствуют. Часть шаблона #define является литеральной, т.е. каждый символ интерпретируется "как есть". Заключительная часть шаблона означает "любые символы в любых количествах".

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

2.2. Определение диапазонов символов в регулярных выражениях

Если возникает необходимость задать символ из определённой группы, например, только цифровой символ, или только гласную букву нижнего регистра, или только символы пунктуации, то используются квадратные скобки, внутри которых определяются требуемые символы. Таким образом:

  • – соответствует одному цифровому символу из заданного набора;
  • [аеёиоуыэюя] – соответствует одной из перечисленных гласных букв;
  • [,.:;] – соответствует одному из символов пунктуации.

Обратите внимание на то, что в последнем случае точка в квадратных скобках утрачивает свой особый статус и обозначает не "любой символ", а собственно символ "точка".

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

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

  • [^0-9] – соответствует любому символу, кроме цифрового;
  • [^аеёиоуыэюя] – соответствует любой НЕ гласной букве.

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

2.3. Модификаторы количества повторений символов

Здесь сложность состоит в том, что модификатор * для поиска IP-адреса не годится – попытка использовать шаблон *\.*\.*\. приведёт к выводу строк, содержащих элементы типа 2344.5657.11.00000, не являющихся IP-адресами. Для уточнения количества повторений наборов символов применяется модификатор \{min,max\}. Зная, что в каждой части IP-адреса может содержаться от одной до трёх цифр, запишем модификатор в виде \{1,3\}. Символы "обратный слэш" перед точками необходимы для того, чтобы отменить их специальный статус универсального метасимвола. Также следует учесть, что значение 0 не используется в качестве первого байта обычных IP-адресов. В итоге получим следующий шаблон поиска:

grep "\{0,2\}\.\{1,3\}\.\{1,3\}\.\{1,3\}" *.txt

Модификатор \{min,max\} работает только в простых регулярных выражениях. В расширенных регулярных выражениях нельзя использовать конструкции \{ \}, но можно применять модификатор? в качестве эквивалента выражения \{0,1\}, а модификатор + как эквивалент выражения \{1,\}. Во втором случае после запятой не указано числовое значение – это означает, что максимальное количество совпадений не ограничено.

2.4. Запоминание и повторное использование элемента шаблона

Этот механизм также работает только в простых регулярных выражениях. (Впрочем, в языках программирования Perl, Python и т.п. данный механизм поддерживается – граница между диалектами становится всё менее различимой, помните?)

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

  • \(\)\(\)\2\1 – для пятибуквенных палиндромов (например, level, rotor, madam и т.д.)
  • \(\)\(\)\(\)\3\2\1 – для шестибуквенных палиндромов (например, redder, succus, terret и т.д.)

2.5. Соответствие стандарту POSIX

Стандарт POSIX также делит регулярные выражения на две категории: BRE (Basic Regular Expressions) и ERE (Extended Regular Expressions). В обеих категориях поддерживаются метасимволы. и *, якоря ^ и $, группирование символов в скобках (для BRE скобки экранируются обратным слэшем), применение квантификаторов \{min,max\} к группам в скобках. Запоминание и повторное использование \1...\9 поддерживает только категория BRE, а квантификаторы + и? и конструкцию выбора – только категория ERE.

В стандарте POSIX используется понятие локального контекста (locale) – совокупности параметров, описывающих языковые и культурные правила: формат даты и времени, интерпретация символов активной кодировки и т.д. Это не относится напрямую к регулярным выражениям, но влияет на их функционирование. При работе в локальном контексте с кодировкой UTF-8, принятой почти во всех современных дистрибутивах, корректно обрабатываются символы русского алфавита и их диапазоны, т.е. можно указывать диапазоны [а-я] и [А-Я] в шаблонах поиска.

3. Примеры составления полезных регулярных выражений

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

3.1. Пример шаблона для поиска денежной суммы, записываемой в формате "10000 руб. 00 коп."

\{1,\} руб\. \{2\} коп\.

Необходимое пояснение: если в модификаторе типа \{min,max\} отсутствует и запятая, и максимальное значение, то такая конструкция задаёт точное количество ожидаемых повторов элемента шаблона. В нашем примере определяются ровно два цифровых символа для обозначения копеек.

3.2. Пример шаблона для поиска URL-строки, соответствующей Web-ресурсу в Интернете:

http://\{1,\}\.[-a-zA-Z0-9_]\{1,\}/*

Необходимое пояснение: дефис теряет своё специальное значение, если он указан в самой первой позиции сразу после открывающей квадратной скобки в диапазоне. По данному шаблону могут быть найдены и такие "экзотические" URL-строки, как, например, http://my.home-server/

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

http://+\.[-a-zA-Z0-9_]+/*

Такую запись понимают, например, утилиты egrep и awk.

3.3. Шаблон для поиска любого HTML-тэга выглядит на удивление просто:

<[^>]+>

Совпадает с любой последовательностью символов за исключением > в количестве от одного и более, заключённой в угловые скобки. Иными словами, будет найден и односимвольный тэг

И более "многословные" тэги, подобные


.

3.4. Вариант шаблона для поиска дат

Расширенные регулярные выражения позволяют написать несколько громоздкий, но тем не менее корректно работающий шаблон для поиска дат, имеющих вид "13 ноября 2009 г.":

? (янв|фев|мар|апр|мая|июн|июл|авг|сен|окт|ноя|дек).* г\.

Недостаток этого шаблона заключается в том, что с его помощью невозможно найти даты из древней истории, например, "13 ноября 245 г." или 1 января 88 г.", но для работы с современными документами он вполне годится (учитываем контекст поиска!).

3.5. Практическое применение нумерованных частей шаблона

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

\(.\)\(.\)\(.\)\3\2\1

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

Более практичным способом использования запомненных и пронумерованных частей шаблона является поиск стоящих рядом повторяющихся слов, что позволяет обнаружить такие часто встречающиеся в текстах ошибки (опечатки), как "для для". Шаблон можно записать так:

\<\(..*\)\> \<\1\>

Здесь применяются ещё два элемента регулярных выражений: \< для обозначения начальной границы слова и \> для обозначения конечной границы слова. Таким образом, мы запоминаем только отдельные слова, а не любые последовательности символов. Выражение..* соответствует любому слову, состоящему по крайней мере из одного символа. В результате мы сможем найти такие опечатки-повторения, как "и и", "не не", "для для" и т.п.

3.6. Ограничение размера совпадающей части шаблона

Ещё одна особенность "характера" регулярных выражений – они являются неимоверно "жадными" (greedy), т.е. стремятся обеспечить совпадение с как можно более длинной строкой. Из-за этой "жадности" могут возникать неожиданные проблемы. Например, имеется шаблон для поиска любого количества символов, заключённых в кавычки:

".*"

Строки, в которых производится поиск, имеют следующий вид:

"Петров" "охранник" "Иванов" "отдел снабжения" "экспедитор" "Сидоров" "администрация" "директор"

Если была поставлена задача извлечения из данных строк только первого аргумента (фамилия сотрудника), то предложенный выше шаблон выполнит её некорректно, поскольку вторая кавычка шаблона соответствует последней кавычке строки (из-за стремления получить максимальное совпадение). Изменение шаблона:

".*" ".*"

решает проблему только для первой строки, а во второй и третьей к фамилии подцепляется ещё и место работы – опять не то, что нам нужно!

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

"[^"]*"

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

4. Заключение

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

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

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

Ресурсы для скачивания

static.content.url=http://www.сайт/developerworks/js/artrating/

ArticleID=487264

ArticleTitle=Секреты регулярных выражений (regular expressions): Часть 1. Диалекты и возможности. Составление регулярных выражений

Если вам когда-нибудь приходилось работать с командной строкой, вы, вероятно, использовали маски имён файлов. Например, чтобы удалить все файлы в текущей директории, которые начинаются с буквы «d», можно написать rm d* .

Регулярные выражения представляют собой похожий, но гораздо более сильный инструмент для поиска строк, проверки их на соответствие какому-либо шаблону и другой подобной работы. Англоязычное название этого инструмента - Regular Expressions или просто RegExp . Строго говоря, регулярные выражения - специальный язык для описания шаблонов строк.

Реализация этого инструмента различается в разных языках программирования, хоть и не сильно. В данной статье мы будем ориентироваться в первую очередь на реализацию Perl Compatible Regular Expressions.

Основы синтаксиса

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

Однако уже здесь следует быть аккуратным - как и любой язык, регулярные выражения имеют спецсимволы, которые нужно экранировать. Вот их список: . ^ $ * + ? { } \ | () . Экранирование осуществляется обычным способом - добавлением \ перед спецсимволом.

Набор символов

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

Здесь нам на помощь придут наборы - вместо указания конкретного символа, мы можем записать целый список, и если в исследуемой строке на указанном месте будет стоять любой из перечисленных символов, строка будет считаться подходящей. Наборы записываются в квадратных скобках - паттерну будет соответствовать любой из символов «a», «b», «c» или «d».

Внутри набора бо льшая часть спецсимволов не нуждается в экранировании, однако использование \ перед ними не будет считаться ошибкой. По прежнему необходимо экранировать символы «\» и «^», и, желательно, «]» (так, обозначает любой из символов «]» или «[», тогда как [х] – исключительно последовательность «[х]»). Необычное на первый взгляд поведение регулярок с символом «]» на самом деле определяется известными правилами, но гораздо легче просто экранировать этот символ, чем их запоминать. Кроме этого, экранировать нужно символ «-», он используется для задания диапазонов (см. ниже).

Если сразу после [ записать символ ^ , то набор приобретёт обратный смысл - подходящим будет считаться любой символ кроме указанных. Так, паттерну [^xyz] соответствует любой символ, кроме, собственно, «x», «y» или «z».

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

Предопределённые классы символов

Для некоторых наборов, которые используются достаточно часто, существуют специальные шаблоны. Так, для описания любого пробельного символа (пробел, табуляция, перенос строки) используется \s , для цифр - \d , для символов латиницы, цифр и подчёркивания «_» - \w .

Если необходимо описать вообще любой символ, для этого используется точка - . . Если указанные классы написать с заглавной буквы (\S , \D , \W) то они поменяют свой смысл на противоположный - любой непробельный символ, любой символ, который не является цифрой, и любой символ кроме латиницы, цифр или подчёркивания соответственно.

Также с помощью регулярных выражений есть возможность проверить положение строки относительно остального текста. Выражение \b обозначает границу слова, \B - не границу слова, ^ - начало текста, а $ - конец. Так, по паттерну \bJava\b в строке «Java and JavaScript» найдутся первые 4 символа, а по паттерну \bJava\B - символы c 10-го по 13-й (в составе слова «JavaScript»).

Диапазоны

У вас может возникнуть необходимость обозначить набор, в который входят буквы, например, от «б» до «ф». Вместо того, чтобы писать [бвгдежзиклмнопрстуф] можно воспользоваться механизмом диапазонов и написать [б-ф] . Так, паттерну x соответствует строка «xA6», но не соответствует «xb9» (во-первых, из-за того, что в диапазоне указаны только заглавные буквы, во-вторых, из-за того, что 9 не входит в промежуток 0-8).

Механизм диапазонов особенно актуален для русского языка, ведь для него нет конструкции, аналогичной \w . Чтобы обозначить все буквы русского алфавита, можно использовать паттерн [а-яА-ЯёЁ] . Обратите внимание, что буква «ё» не включается в общий диапазон букв, и её нужно указывать отдельно.

Квантификаторы (указание количества повторений)

Вернёмся к нашему примеру. Что, если в «смеющемся» междометии будет больше одной гласной между буквами «х», например «Хаахаааа»? Наша старая регулярка уже не сможет нам помочь. Здесь нам придётся воспользоваться квантификаторами.

Обратите внимание, что квантификатор применяется только к символу, который стоит перед ним.

Некоторые часто используемые конструкции получили в языке регулярных выражений специальные обозначения:

Таким образом, с помощью квантификаторов мы можем улучшить наш шаблон для междометий до [Хх][аоеи]+х[аоеи]* , и он сможет распознавать строки «Хааха», «хееееех» и «Хихии».

Ленивая квантификация

Предположим, перед нами стоит задача - найти все HTML-теги в строке

Tproger - мой любимый сайт о программировании!

Очевидное решение <.*> здесь не сработает - оно найдёт всю строку целиком, т.к. она начинается с тега абзаца и им же заканчивается. То есть содержимым тега будет считаться строка

P>Tproger - мой любимый сайт о программировании!

Это происходит из-за того, что по умолчанию квантификатор работают по т.н. жадному алгоритму - старается вернуть как можно более длинную строку, соответствующую условию. Решить проблему можно двумя способами. Первый - использовать выражение <[^>]*> , которое запретит считать содержимым тега правую угловую скобку. Второй - объявить квантификатор не жадным, а ленивым . Делается это с помощью добавления справа к квантификатору символа? . Т.е. для поиска всех тегов выражение обратится в <.*?> .

Ревнивая квантификация

Иногда для увеличения скорости поиска (особенно в тех случаях, когда строка не соответствует регулярному выражению) можно использовать запрет алгоритму возвращаться к предыдущим шагам поиска для того, чтобы найти возможные соответствия для оставшейся части регулярного выражения. Это называется ревнивой квантификацией. Квантификатор делается ревнивым с помощью добавления к нему справа символа + . Ещё одно применение ревнивой квантификации - исключение нежелательных совпадений. Так, паттерну ab*+a в строке «ababa» будут соответствовать только первые три символа, но не символы с третьего по пятый, т.к. символ «a», который стоит на третьей позиции, уже был использован для первого результата.

Скобочные группы

Для нашего шаблона «смеющегося» междометия осталась самая малость - учесть, что буква «х» может встречаться более одного раза, например, «Хахахахааахахооо», а может и вовсе заканчиваться на букве «х». Вероятно, здесь нужно применить квантификатор для группы [аиое]+х, но если мы просто напишем [аиое]х+ , то квантификатор + будет относиться только к символу «х», а не ко всему выражению. Чтобы это исправить, выражение нужно взять в круглые скобки: ([аиое]х)+ .

Таким образом, наше выражение превращается в [Хх]([аиое]х?)+ - сначала идёт заглавная или строчная «х», а потом произвольное ненулевое количество гласных, которые (возможно, но не обязательно) перемежаются одиночными строчными «х». Однако это выражение решает проблему лишь частично - под это выражение попадут и такие строки, как, например, «хихахех» - кто-то может быть так и смеётся, но допущение весьма сомнительное. Очевидно, мы можем использовать набор из всех гласных лишь единожды, а потом должны как-то опираться на результат первого поиска. Но как?…

Запоминание результата поиска по группе (обратная связь)

Оказывается, результат поиска по скобочной группе записывается в отдельную ячейку памяти, доступ к которой доступен для использования в последующих частях регулярного выражения. Возвращаясь к задаче с поиском HTML-тегов на странице, нам может понадобиться не только найти теги, но и узнать их название. В этом нам может помочь регулярное выражение <(.*?)> .

Tproger - мой любимый сайт о программировании!

Результат поиска по всем регулярному выражению: «

», «», «», «», «», «

».
Результат поиска по первой группе: «p», «b», «/b», «i», «/i», «/i», «/p».

На результат поиска по группе можно ссылаться с помощью выражения \n , где n - цифра от 1 до 9. Например выражению (\w)(\w)\1\2 соответствуют строки «aaaa», «abab», но не соответствует «aabb».

Если выражение берётся в скобки только для применения к ней квантификатора (не планируется запоминать результат поиска по этой группе), то сразу первой скобки стоит добавить?: , например (?:+\w) .

С использованием этого механизма мы можем переписать наше выражение к виду [Хх]([аоие])х?(?:\1х?)* .

Перечисление

Чтобы проверить, удовлетворяет ли строка хотя бы одному из шаблонов, можно воспользоваться аналогом булевого оператора OR, который записывается с помощью символа | . Так, под шаблон Анна|Одиночество попадают строки «Анна» и «Одиночество» соответственно. Особенно удобно использовать перечисления внутри скобочных групп. Так, например (?:a|b|c|d) полностью эквивалентно (в данном случае второй вариант предпочтительнее в силу производительности и читаемости).

С помощью этого оператора мы сможем добавить к нашему регулярному выражению для поиска междометий возможность распознавать смех вида «Ахахаах» - единственной усмешке, которая начинается с гласной: [Хх]([аоие])х?(?:\1х?)*|[Аа]х?(?:ах?)+

Полезные сервисы

Потренироваться и / или проверить своё регулярное выражение на каком-либо тексте без написания кода можно с помощью таких сервисов, как RegExr , Regexpal или Regex101 . Последний, вдобавок, приводит краткие пояснения к тому, как регулярка работает.

Разобраться, как работает регулярное выражение, которое попало к вам в руки, можно с помощью сервиса

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

1. Введение

Пара слов для тех, кто не совсем в курсе, о чем идет речь. Вы видели когда-нибудь маски имен файлов — всякие там *.html, filename.{txt|csv} и тд? Так вот, регулярные выражения — это те же «маски», только более сложные. В умелых руках регулярные выражения могут быть невероятно мощным инструментом . Так или иначе они используются в 95% моих скриптов.

Многие небезосновательно считают, что регулярные выражения — это скорее самостоятельный язык программирования, чем часть какого-либо языка. Регулярные выражения есть в Perl, PHP, Python , JavaScript, конфигурационных файлах Apache… В зависимости от языка, могут иметь место небольшие различия в синтаксисе регулярных выражений, но основные идеи везде одни и те же.

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

if (preg_match ("//" , $text ) ) {
// в тексте есть цифры
} else {
// в тексте нет ни одной цифры
}

и такой — на Perl:

if ($text =~ // ) {
# в тексте есть цифры
} else {

}

делают одно и то же. Как не сложно догадаться по комментариям в коде, здесь идет проверка, содержит ли строка $text хотя бы одну цифру.

2. Простые примеры

Как всегда, учиться будем на примерах. Квадратные скобки в регулярных выражениях означают «здесь должен быть один из перечисленных символов». Например, приведенному выше выражению соответствует любая строка, содержащая хотя бы одну цифру. Аналогично, выражению соответствует любая строка, содержащая хотя бы одну из первых трех букв латинского алфавита. Чтобы обозначить любой символ, кроме заданных, используется запись [^abcdef] , то есть с символом крышки сразу за открывающейся квадратной скобкой.

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

if ($text =~ // ) {
# в тексте есть цифры
} else {
# в тексте нет ни одной цифры
}

И еще пара примеров:

if ($text =~ // ) {
# в тексте есть цифры и/или строчные буквы
# подходит: abc, ZZaZZ, ===17
# не подходит: EPIC FAIL, @^*!@#
}

if ($text =~ /[^0-9]/ ) {
# в тексте есть символы, отличные от цифр
# подходит: abc, 123abc456, 0x1111111111
# не подходит: 123, 123456, 9999999999
}

if ($text =~ // ) {
# в тексте есть буквы латинского алфавита
# подходит: ___Abba___, zyx
# не подходит: 0123, ^_^
}

if ($text =~ // ) {
# текст содержит цифры и буквы от A до F
# подходит: ***777***, DeadC0de, intel, 0_o
# не подходит: Xor, wiki
}

Усложним задачу. Теперь нам нужно проверить не просто наличие или отсутствие определенных символов, а соответствие строки определенному формату. Вот несколько простых примеров:

if ($text =~ /num=/ ) {
# подходит: num=1, some_num=000, bebenum=2(&^*
# не подходит: NUM=1, my_num=-1, num=abc
}

if ($text =~ // ) {
# подходит:
# zzzzzz
#
# не подходит:
#
#
}

Внимательный читатель поинтересуется, что это за знак плюса стоит в последнем регулярном выражении? Этот символ означает «один или более символов, указанных перед этим плюсом». Почти то же самое обозначает символ звездочка «от нуля до сколько угодно символов, указанных перед звездочкой». Например, выражению A+ будет соответствовать последовательность из одного и более символов A, а выражению * — любое количество цифр, в том числе и ни одной.

Иногда количество символов нужно задать точнее. Это можно сделать с помощью фигурных скобок . Например, выражению {8} соответствует любая последовательность из ровно восьми цифр, а выражению {3,8} — последовательность, содержащая от 3-х до 8-и символов латинского алфавита.

Число на второй позиции можно не указывать. То есть выражение {3,} также может иметь место. Оно означает «не менее трех строчных букв латинского алфавита». Выражение {0,} полностью аналогично звездочке, а {1,} — плюсу. Выражение {0,1} можно записать более коротко, используя знак вопроса .

Пример (не самый простой, зато интересный):

if ($text =~ // ) {
# подходит:
# dfgddfgdfg
#
# не подходит:
#
#
}

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

3. Как выдрать кусок строки?

Символ вертикальной черты (он же «пайп» или просто «палка») в регулярных выражениях означает «или». Например, выражению {20}|{25} соответствуют все строки, содержащие 20 символов латинского алфавита или 25 цифр подряд. Обычно этот символ используется совместно с круглыми скобками , предназначенных для группировки частей регулярного выражения. Пример:

if ($filename =~ /backup(19|20){2}-{2}-{2}/ ) {
# подходит: backup2011-04-01, backup1999-01-13
# не подходит: backup1873-12-12, backup2101-07-07
}

У круглых скобок есть еще одна функция. С их помощью можно выдирать куски соответствующих строк. В PHP результат сохраняется в переменную, указанную третьим аргументом функции preg_match . В Perl совпадения для 1-ой, 2-ой … 9-ой пары скобок сохраняются в переменные $1, $2, …, $9 . Но удобнее использовать такую конструкцию:

if (my ($y , $m , $d ) =
$filename =~ /backup({4})-({2})-({2})/ ) {
print ;
}

Спрашивается, под каким номером искать совпадение в возвращаемом массиве, если регулярное выражение содержит вложенные скобки? Все просто — совпадения возвращаются в том же порядке, в котором идут открывающиеся скобки. Пример:

my $filename = "./dumps/backup2011-04-01.tgz" ;
$filename =~ /backup((20|19){2})-({2})-({2})/ ;
print "$1, $2, $3, $4\n " ;
# выведет: 2011, 20, 04, 01

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

if (my ($y , $m , $d ) =
$filename =~ /backup((?:20|19){2})-({2})-({2})/ ) {
print "year = $y, month = $m, day = $d\n " ;
}

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

4. Начало и конец строки

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

if ($text =~ /^*/ ) {
# текст, начинающийся с десятичной цифры
# подходит: 3, 801403, 6543bebebe
# не подходит: 0275, -123, abc11111
}

if ($text =~ /^0x{1,8}$/ ) {
# шестнадцатеричное число в C-нотации
# подходит: 0x5f3759df, 0xDEADBEEF
# не подходит: 0x1234xxx, xxx0x5678, xxx0x9ABCxxx
}

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

Примечание: Если кого-нибудь интересует, что это за «магические числа» 0x5f3759df и 0xDEADBEEF , обращайтесь к Википедии.

5. Специальные символы

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

if (my ($name ) = $arg =~ /^--name=(.+)$/ ) {
print "Hello, $name!\n " ;
}

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

<span > Text <em > text</ em > text</ span > Source: http://сайт/</ span >

Следующий код вернет нам не то, что хотелось бы:

# в регулярном выражении содержится слэш, поэтому
# приходится использовать вместо него другой ограничитель
(.*)#;
print $text ;
# выведет наиболее длинное совпадение:
# Text text textSource: http://сайт/

А вот что произойдет, если отключить жадный разбор (внимание на знак вопроса):

my ($text ) = $data =~ m #(.*?)#;
print $text ;
# выведет первое совпадение:
# Text text text

Да, следующие строки делают одно и то же:

# обычная запись...
$text =~ /({4})-({2})-({2})/ ;
# на самом деле - лишь сокращение оператора m//
$text =~ m/({4})-({2})-({2})/ ;
# вместо слэша можно использовать разные скобочки:
$text =~ m { ([ 0 - 9 ] { 4 } ) - ([ 0 - 9 ] { 2 } ) - ([ 0 - 9 ] { 2 } ) } ;
$text =~ m< ([ 0 - 9 ] { 4 } ) - ([ 0 - 9 ] { 2 } ) - ([ 0 - 9 ] { 2 } ) >;
$text =~ m [ ([ 0 - 9 ] { 4 } ) - ([ 0 - 9 ] { 2 } ) - ([ 0 - 9 ] { 2 } ) ] ;
$text =~ m (([ 0 - 9 ] { 4 } ) - ([ 0 - 9 ] { 2 } ) - ([ 0 - 9 ] { 2 } ) ) ;
# или даже такие символы:
$text =~ m ! ([ 0 - 9 ] { 4 } ) - ([ 0 - 9 ] { 2 } ) - ([ 0 - 9 ] { 2 } ) !;
$text =~ m | ([ 0 - 9 ] { 4 } ) - ([ 0 - 9 ] { 2 } ) - ([ 0 - 9 ] { 2 } ) |;
$text =~ m #({4})-({2})-({2})#;
# а также крышку, кавычки, двоеточие, запятую, точку, ...

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

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

# экранированная обратным слэшем точка
# означает именно точку, а не "любой символ"
my ($ext ) = $fname =~ /\.(+)$/ ;
print "file name: $fname, extension: $ext\n " ;

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

  • \t — обозначает символ табуляции (t ab)
  • \r и \n — символы возврата каретки (r eturn) и новой строки (n ew line)
  • \xNN — соответствует символу с ASCII кодом NN, например \x41 соответствует заглавной букве A латинского алфавита
  • \s — соответствует пробелу (s pace), табуляции, символу новой строки или символу возврата каретки
  • \d — означает любую цифру (d igit), а точнее — то, что считается цифрой в Юникоде (см слайд номер 102 в этой презентации)
  • \w — означает так называемое «слово» (w ord), аналог

В последних трех выражениях запись буквы в верхнем регистре означает отрицание. Например, \D соответствует выражению [^0-9] , \W — выражению [^0-9a-zA-Z_] , а \S — любому «не пробельному» символу.

Все эти «буквенные» выражения можно использовать внутри квадратных скобок. Например, выражение полностью эквивалентно .

Особого внимания заслуживают выражения \b и \B , означающие границу слова (в том же понимании «слова», как и в случае с \w ) и отсутствие границы слова соответственно. Например, выражению perl\b соответствует строка «perl rulez!», но не соответствует «perlmonk». С выражением perl\B все с точностью наоборот. Надеюсь, идея ясна.

И еще один пример:

# разбиваем полное имя файла на путь и имя
my ($path , $fname ) = $full_name =~ /^(.*)\/([^\/]+)$/ ;

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

6. Модификаторы

Поведение регулярных выражений можно менять с помощью модификаторов. Например, как вы уже могли заметить, соответствие строки регулярному выражению проверяется с учетом регистра символов. Изменить это поведение можно с помощью модификатора #(.*?)#g;
# будьте осторожны при использовании /g в скалярном контексте
# подробности здесь: http://koorchik.blogspot.com/2011/07/perl-5.html
print "$_\n " for (@words ) ;

Как было сказано выше, точка обозначает любой символ, кроме символа новой строки . Изменить такое поведение можно с помощью модификатора /s :

# выдираем из HTML-файла содержимое статьи,
# которое может содержать далеко не одну и не две строчки
my ($article ) = $html =~ m #

(.*?)
#s;

Кстати, если в регулярном выражении нужно обозначить «любой символ» без использования модификатора /s , используйте выражение [\d\D] . Оно означает «любой символ, являющийся цифрой, или не являющийся цифрой», то есть вообще любой символ.

Наконец, ничто не мешает использовать несколько модификаторов одновременно:

# выдираем из HTML-файла все, что выделено жирным
my @words = $html =~ m #(.*?)#gi;
# сработает для , или даже

Дополнение: Еще один полезный модификатор — /o . Он означает «компилировать регулярное выражение только один раз». В некоторых случаях этот модификатор может существенно ускорить скрипт. Правда, я не уверен, что он поддерживается где-то, кроме как в Perl. За наводку спасибо товарищу

Давно хотели изучить regexp? Это небольшое руководство поможет разобраться с ними в 6 этапов, а обилие примеров позволит закрепить материал.

Что такое regexp?

Regexp представляет собой группу символов или знаков, которая используется для поиска определенного текстового шаблона.

Регулярное выражение – это шаблон, который сравнивается с предметной строкой слева направо. Словосочетание “regular expression” применяется не так широко, вместо него обычно употребляют “regex” и “regexp”. Регулярное выражение используется для замены текста внутри строки, проверки формы, извлечения подстроки из строки на основе соответствия шаблона и т. д.

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

Это выражение принимает строки john_doe , jo-hn_doe и john12_as . Однако имя пользователя Jo не будет соответствовать этому выражению, потому что оно содержит прописную букву, а также является слишком коротким.

1. Базовые совпадения

Регулярное выражение — это всего лишь шаблон из символов, который мы используем для выполнения поиска в тексте. Например, регулярное выражение the означает букву t , за которой следует буква h , за которой следует буква e .

"the" => The fat cat sat on the mat.

Регулярное выражение 123 соответствует строке 123 . Регулярное выражение сопоставляется входной строке путем сравнения каждого символа в regexp с каждым символом входной строки. Регулярное выражение и входная строка сравниваются посимвольно. Обычно regex чувствительны к регистру, поэтому The не соответствует строке the .

"The" => The fat cat sat on the mat.

2. Метасимволы

Метасимволы служат строительными блоками regexp. Они не являются независимыми и обычно интерпретируются каким-либо образом. Некоторые метасимволы имеют особое значение, а потому помещаются в квадратные скобки. Метасимволы:

Метасимволы Описание
. Любой единичный символ, исключая новую строку.
Поиск набора символов, помещенных в скобки.
[^ ] Negated character class. Matches any character that is not contained between the square brackets
* 0 или больше повторений предшествующего символа.
+ 1 или больше повторений предшествующего символа.
? Делает предшествующий символ опциональным.
{n,m} Возвращает как минимум «n», но не более «m» повторений предшествующего символа.
(xyz) Находит группу символа в строго заданном порядке.
| Разделяет допустимые варианты.
\ Исключает следующий символ. Позволяет искать служебные символы () { } . * + ? ^ $ \ |
^ Находит начало введенной строки.
$ Находит конец введенной строки.

2.1 Точка

. — это простейший пример метасимвола. Метасимвол. соответствует любому единичному символу. Например, регулярное выражение.ar означает: любой символ, за которым следует буква a , за которой следует буква r .

«.ar» => The car par ked in the gar age.

2.2 Интервал символов

Интервал или набор символов также называют символьным классом. Для его обозначения используются квадратные скобки. Чтобы указать диапазон символов внутри класса, необходимо поставить знак тире. Порядок ряда символов в наборе неважен. Так, например, регулярное выражение he означает: T или t , за которым следует буква h , за которой следует буква e .

«he » => The car parked in the garage.

Стоит отметить, что точка, помещенная в квадратные скобки, означает именно точку, а ничто другое. Таким образом регулярное выражение ar[.] означает строчный символ a , за которым следует буква r , за которой следует точка. .

«ar [.]» => A garage is a good place to park a car.

2.2.1 Отрицание набора символов

Обычно символ ^ представляет начало строки, но когда он внутри квадратных скобок, все символы, которые находятся после него, исключаются из шаблона. Например, выражение [^c]ar поможет отыскать все символы кроме c , за которыми следуют а и r .

"[^c]ar" => The car par ked in the gar age.

2.3 Повторения

Следующие мета-символы + , * или? используются для того, чтобы обозначить допустимое количество повторения подшаблона. Их роль зависит от конкретного случая.

2.3.1 Звездочка

Этот символ поможет найти одно или более копий какого-либо символа. Регулярное выражение a* означает 0 или более повторений символа a. Но если этот символ появится после набора или класса символов, тогда будут найдены повторения всего сета. Например, выражение * означает любое количество этих символов в строке.

"*" => The car parked in the garage #21.

Также символ может быть использован вместе с метасимволом. для подбора строки из любых символов.* .

Еще звездочку можно использовать со знаком пробела \s , чтобы подобрать строку из пробелов. Например, выражение \s*cat\s будет означать 0 или более пробелов, за которыми следует символ с, за ним а и t , а за ними снова 0 либо больше пробелов.

"\s*cat\s*" => The fat cat sat on the concat enation.

2.3.2 Плюс

Соответствует одному или нескольким повторениям предыдущего символа. Например, регулярное выражение c.+t означает: строчная буква c , за которой следует хотя бы один символ, за которым следует строчный символ t . Необходимо уточнить, что буква t должна быть последней t в предложении.

"c.+t" => The fat cat sat on the mat .

2.3.3. Вопросительный знак

В regexp метасимвол? делает предшествующий символ необязательным. Этот символ соответствует полному отсутствию или же одному экземпляру предыдущего символа. Например, регулярное выражение [T]?he означает: необязательно заглавную букву T , за которой следует строчный символ h , за которым следует строчный символ e .
"[T]he" => The car is parked in the garage.
Тестировать выражение

"[T]?he" => The car is parked in the garage.

2.4 Скобки

Скобки в regexp, которые также называются квантификаторами, используются для указания допустимого количества повторов символа или группы символов. Например, регулярное выражение {2,3} означает, что допустимое количество цифр должно быть не менее двух цифр, но не более 3 (символы в диапазоне от 0 до 9).

"{2,3}" => The number was 9.999 7 but we rounded it off to 10 .0.

Мы можем убрать второе число. Например, выражение {2,} означает 2 или более цифр. Если мы также уберем запятую, то тогда выражение {3} будет находить только лишь 3 цифры, ни меньше и ни больше.

"{2,}" => The number was 9.9997 but we rounded it off to 10 .0.

"{3}" => The number was 9.999 7 but rounded it off to 10.0.

2.5 Символьная группа

Группа символов — это группа подшаблонов, которая записывается внутри скобок (...) . Как было упомянуто раньше, если в регулярном выражении поместить квантификатор после символа, он повторит предыдущий символ. Но если мы поставим квантификатор после группы символов, он просто повторит всю группу. Например, регулярное выражение (ab)* соответствует нулю или более повторениям символа «ab». Мы также можем использовать | — метасимвол чередования внутри группы символов. Например, регулярное выражение (c|g|p)ar означает: символ нижнего регистра c , g или p , за которым следует символ a , за которым следует символ r .

"(c|g|p)ar" => The car is par ked in the gar age.

2.6 Перечисление

В regexp вертикальная полоса | используется для определения перечисления. Перечисление — это что-то вроде условия между несколькими выражениями. Можно подумать, что набор символов и перечисление работают одинаково, но это совсем не так, между ними существует огромная разница. Перечисление работает на уровне выражений, а набор символов на уровне знаков. Например, регулярное выражение (T|t)he|car означает: T или t , сопровождаемая строчным символом h , сопровождаемый строчным символом e или строчным символом c , а затем a и r .

"(T|t)he|car" => The car is parked in the garage.

2.7 Исключение специального символа

Обратная косая черта \ используется в regexp, чтобы избежать символа, который следует за ней. Это позволяет нам указывать символ в качестве символа соответствия, включая зарезервированные { } / \ + * . $ ^ | ? . Чтобы использовать специальный символ в качестве подходящего, перед ним нужно поставить \ .

Например, регулярное выражение. используется для нахождения любого единичного символа. Регулярное выражение (f|c|m)at\.? означает строчную букву f , c или m , а затем a , за ней t с последующим дополнительным символом. .

"(f|c|m)at\.?" => The fat cat sat on the mat .

2.8 Анкеры — Привязки

В regexp мы используем привязки, чтобы проверить, является ли соответствующий символ первым или последним символом входной строки. Привязка бывает двух типов: первый — это ^ , который проверяет является ли соответствующий символ первым введенным, а второй — знак доллара, который проверяет, является ли соответствующий символ последним символом введенной строки.

2.8.1. Caret

Символ ^ используется в regexp, чтобы проверить, является ли соответствующий символ первым символом в введенной строке. Если мы применяем следующее регулярное выражение ^a (проверяем является ли a первым символом) для введенной строки abc , то оно будет равно a . Но если мы применим регулярное выражение ^b к той же строке, то оно ничего не вернет, потому что во входной строке abc символ «b» не является первым. Давайте посмотрим на другое регулярное выражение ^(T|t)he , которое означает: T или t — это символ начала входной строки, за которым следует строчный символ h , а затем e .

"(T|t)he" => The car is parked in the garage.

"^(T|t)he" => The car is parked in the garage.

2.8.2 Доллар

Знак доллара используется для проверки, является ли символ в выражении последним в введенной строке. Например (at\.)$ означает строчную а, за которой следует t , за которой следует a . , которые должны заканчивать строку.

"(at\.)" => The fat cat. sat. on the mat.

"(at\.)$" => The fat cat. sat. on the mat.
Тестировать выражение

3. Сокращения для обозначения символов

Regexp позволяет использовать сокращения для некоторых наборов символов, что делает работу с ними более комфортной. Таким образом, здесь используются следующие сокращения:

4. Lookaround Позиционная проверка

Lookbehind и lookahead (также называемые lookaround) — это определенные типы non-capturing групп (Они используются для поиска, но сами в него не входят). Lookaheads используются, когда у нас есть условие, что этому шаблону предшествует или следует другой шаблон. Например, мы хотим получить все числа, которым предшествует символ $ из входной строки $4.44 and $10.88 . Мы будем использовать регулярное выражение (?<=\$)* , которое означает: получить все числа, содержащие. и которым предшествует символ $ . Ниже приведены lookarounds, что используются в регулярных выражениях:

4.1 Положительный Lookahead

Положительный lookahead означает, что эта часть выражения должна следовать за впереди идущим выражением. Возвращаемое значение содержит текст, который совпадает с первой частью выражения. Чтобы определить позитивный lookahead, используют скобки. Внутри них размещают знак вопроса и знак равенства: (?=...) . Само же выражение пишется после = . Например, выражение (T|t)he(?=\sfat) — это T в верхнем или нижнем регистре, за которым следует h и e . В скобках мы определяем позитивный lookahead, который говорит движку регулярного выражения искать The или the , за которыми следует fat .

"(T|t)he(?=\sfat)" => The fat cat sat on the mat.

4.2 Отрицательный Lookahead

Негативный lookahead используется, когда нам нужно получить все совпадения в строке, за которой не следует определенный шаблон. Негативный lookahead определяется так же, как и позитивный, с той лишь разницей, что вместо знака равенства мы используем знак отрицания! . Таким образом, наше выражение приобретает следующий вид: (?!...) . Теперь рассмотрим (T|t)he(?!\sfat) , что означает: получить все The или the в введенной строке, за которыми не следует слово fat , предшествующее знаку пробела.

"(T|t)he(?!\sfat)" => The fat cat sat on the mat.

4.3 Положительный Lookbehind

Положительный lookbehind используется для получения всех совпадений, которым предшествует определенный шаблон. Положительный lookbehind обозначается так: (?<=...) . Например, регулярное выражение (?<=(T|t)he\s)(fat|mat) означает получить все fat или mat из строки ввода, которые идут после слова The или the .

"(? The fat cat sat on the mat .

4.4 Отрицательный Lookbehind

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

5. Флаги

Флаги также часто называют модификаторами, так как они могут изменять вывод regexp. Флаги, приведенные ниже являются неотъемлемой частью и могут быть использованы в любом порядке или сочетании regexp.

5.1 Нечувствительные к регистру

Модификатор i используется для поиска совпадений, нечувствительных к регистру. Например, выражение /The/gi означает прописную букву T , за которой следуют h и e . И в самом конце выражения стоит i , благодаря которому можно проигнорировать регистр. g применяется для того, чтобы найти шаблон во всей введенной строке.
"The" => The fat cat sat on the mat.
Тестировать выражение

"/The/gi" => The fat cat sat on the mat.

5.2 Глобальный поиск

Модификатор используется для выполнения глобального поиска шаблона(поиск будет продолжен после первого совпадения). Например, регулярное выражение /.(at)/g означает любой символ, кроме новой строки, за которым следует строчный символ a , а затем t . Поскольку мы использовали флаг g в конце регулярного выражения, теперь он найдет все совпадения в вводимой строке, а не только в первой (что является стандартом).

"/.(at)/" => The fat cat sat on the mat.

"/.(at)/g" => The fat cat sat on the mat .

5.3 Многострочный поиск

Модификатор m нужен для выполнения многострочного поиска. Как было сказано раннее, привязки (^, $) используются для проверки, является ли шаблон началом или концом строки. Но если мы хотим, чтобы привязки работали в каждой строке, нужно использовать флаг m . Например, регулярное выражение /at(.)?$/gm означает: строчный символ a , за которым следует t и что угодно, только не новая строка. А благодаря флагу m этот механизм регулярных выражений соответствует шаблону в конце каждой строки строки.

"/.at(.)?$/" => The fat
cat sat
on the mat .

"/.at(.)?$/gm" => The fat
cat sat