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

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

Часть 2. Регулярные выражения в конкретных программах

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

1. Введение. Знание особенностей повышает эффективность

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

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

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

Perl является "неофициальным чемпионом" по частоте использования в нём регулярных выражений для решения различных задач среди всех интерпретируемых или скриптовых языков. Несмотря на постоянно растущее скептическое отношение к Perl, он вполне справляется с той работой, для которой главным образом и предназначен – для обработки текстовых данных (вспомним один из вариантов "расшифровки" имени Perl – Practical Extraction and Report Language).

2.1. Корректная версия шаблона для поиска IP-адреса

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

\{0,2\}\.\{1,3\}\.\{1,3\}\.\{1,3\}

будет находить и выводить строки вида "900.800.700.600", никоим образом к IP-адресам не относящиеся. Диалект простых регулярных выражений в данном случае не позволяет без непомерных затрат времени и сил решить эту проблему. Но в Perl реализованы расширенные регулярные выражения, что позволяет упростить решение.

В первой части IP-адреса может находиться трёхзначное число, начинающееся либо с "1" (за которой могут следовать две любые цифры), либо с "2" (но в этом случае число не должно быть больше 255), или любое двузначное число, или однозначное число (цифры от 1 до 9). На диалекте расширенных регулярных выражений для Perl это можно записать следующим образом:

(||1|2|25)

Обратите внимание на использование новой конструкции группирования символов, которую часто называют дизъюнкцией: a|b|c – т.е. должен совпасть только один из указанных вариантов, – либо a, либо b, либо c. В нашем примере таких взаимоисключающих вариантов пять:

  • – соответствует значениям от 1 до 9;
  • – соответствует значениям от 10 до 99;
  • 1 – соответствует значениям от 100 до 199;
  • 2 – соответствует значениям от 200 до 249;
  • 25 – соответствует значениям от 250 до 255.

Одиночный нуль здесь исключается, так как обычные IP-адреса не содержат значение 0 в первом байте. Это выражение можно немного улучшить, если заменить диапазон применяемым в Perl метасимволом \d (обозначение цифрового символа). После замены выражение приобретёт вид:

(|\d|1\d\d|2\d|25)

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

(|\d|1\d\d|2\d|25)

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

Осталось придать нашему шаблону поиска завершённый вид, который в Perl-скрипте может быть, например, таким:

#!/usr/bin/perl -w open(IN, "./filename.txt"); while() { $ip_addr = ; chomp($ip_addr); if($ip_addr =~ /\/|\d|1\d\d|2\d|25\/\. \/|\d|1\d\d|2\d|25\/\. \/|\d|1\d\d|2\d|25\/\. \/|\d|1\d\d|2\d|25\//) { print "Найден IP-адрес в строке:\n $ip_addr\n"; } } close(IN);

Замечание . В Perl и шаблон регулярного выражения, и варианты в конструкции дизъюнкции записываются между парными символами "слэш" (/). Из-за этого слэши, ограничивающие варианты дизъюнкции, требуют предваряющих экранирующих символов "обратный слэш" (\). Конечно, подобная запись шаблона выглядит жутковато, но зато работает правильно.

2.2. Работа с данными, разделёнными запятыми

Многие системы управления базами данных и электронные таблицы поддерживают вывод в виде списков полей, разделённых запятыми, в качестве стандартного формата обмена данными. Этот формат обозначается аббревиатурой CSV (Comma-Separated Values – значения, разделённые запятой). На первый взгляд, решение задачи распределения таких данных по переменным с помощью Perl выглядит достаточно простым: использовать функцию split /,/ из набора штатных средств. Но внутри полей данных могут содержаться собственные запятые (в символьных строках или в числовых значениях денежных сумм в российских рублях). Что получится в результате обработки функцией split /,/ такой, например, строки данных: "Иванов", "инженер, расчётчик-математик", "4356,50 руб." ?

Чтобы обойти все эти "подводные камни", можно написать специализированную процедуру:

sub csv_parse { my $str_txt = shift; # присваивается первый элемент массива @_ my @fields = (); # массив для сохранения выделенных полей # Запись в цикле в массив значения переменной $+ - фрагмента строки, # для которого обнаружено соответствие шаблону в процессе самой # последней операции поиска (последняя обработанная пара круглых # скобок внутри тела шаблона поиска) push(@fields, $+) while $str_txt =~ m{ "([^\"\\]*(?:\\.[^\"\\]*)*)",? | ([^,]+),? | , }gx; # Если самый последний символ в исходной строке - запятая, # то список полей завершается "неопределённым значением" undef push(@fields, undef) if substr($str_txt, -1, 1) eq ","; # вернуть список значений, размещённых в отдельных полях return @fields; }

В приведённой выше процедуре первая часть шаблона позволяет выделить фрагмент исходной строки, заключённый в кавычки и ограниченный первой запятой, найденной вне этой пары кавычек. Внутри кавычек могут встречаться любые символы, в том числе и запятые. Вторая часть шаблона соответствует фрагменту без кавычек до первой следующей за ним запятой. Такой фрагмент тоже сохраняется в массиве fields. Последняя часть шаблона – запятая – завершает очередную итерацию цикла. Ключ g после шаблона означает его глобальное действие, т.е. запись в массив всех найденных фрагментов, а не только первого совпадающего. Ключ x позволяет игнорировать все "пробельные символы" в шаблоне (имеются в виду литеральные пробелы, а не метасимволы \s и escape-последовательности). Это немного облегчает чтение шаблона – можно вставить пробелы между символами дизъюнкции (вертикальная черта – разделитель вариантов).

2.3. Небольшие примеры использования Perl для поиска в тексте из командной строки

Нередко встречаются задачи поиска образцов, в условиях которых определено, что надо найти "образец1 И образец2 И образец3". Средства из группы grep легко справляются с задачами поиска одного из вариантов шаблона (образец1|образец2|образец3), но для предложенной задачи потребуется конвейер или другие ухищрения. С помощью Perl подобные задачи решаются "в одно действие":

perl -ne "print if /рубль/ && /доллар/ && /евро/" список_файлов

Здесь ключ e позволяет определить строку выполняемых команд, а ключ n заставляет интерпретатор Perl считать, что заданная последовательность команд заключена в цикл while(<>), т.е. будет выполняться для всех строк перечисленных файлов.

В тех случаях, когда нужно найти абзацы, в которых встречаются все три указанных слова, поможет режим работы с абзацами. Для Perl этот режим активизируется ключом -00:

perl -n00e "print "$_\n" if /рубль/ && /доллар/ && /евро/" список_файлов

А если необходимо вывести список файлов, которые содержат все три слова, то для ключа -0 надо установить такой разделитель записей, который не содержится в обычных текстовых файлах, например, NUL-символ:

perl -ln0e "print $ARGV if /рубль/ && /доллар/ && /евро/" список_файлов

В общем, не спешите "хоронить" Perl – он ещё способен на многое, особенно там, где требуется интенсивная работа с регулярными выражениями.

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

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

re.set_syntax(RE_NO_BK_PARENS | RE_NO_BK_VBAR)

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

В Python механизм регулярных выражений подключается с помощью модуля re. Поскольку Python по своей сущности является объектно-ориентированным языком, то это в полной мере относится и к его диалекту регулярных выражений. При необходимости создаётся объект типа "регулярное выражение", который в дальнейшем вы можете применять к строкам для выполнения поиска или замены. Рассмотрим следующий фрагмент кода:

undsc_regex = re.compile("\s+(_.+_)\s+") ... result_text = undsc_regex.sub("\\1", input_text)

В первой строке фрагмента создаётся объект-шаблон, соответствующий любой последовательности символов в тексте, начинающейся и заканчивающейся символами подчёркивания (например: "здесь _важно_ отметить"). После создания этот объект можно применять к любым строкам, используя его методы поиска и замены. В данном случае применяется метод замены sub(), который принимает в качестве аргументов строку замены и обрабатываемый текст input_text. Обратите внимание на элемент \1, обозначающий найденный фрагмент и соответствующий той части шаблона, которая заключена в круглые скобки. В отличие от Perl, обозначение \1 включается и в строку замены. В результате обработки текст (сохраняемый в result_text) будет заключён в HTML-тэги "подчёркнутый текст", например: "здесь важно отметить".

А вот как решается проблема с повторяющимися словами-опечатками ("не не", "для для" и т.д.) на языке Python:

#!/usr/bin/python # -*- coding: utf-8 -*- import sys import re # Потребуются три объекта типа "регулярное выражение" RegEx1 = re.compile("\b(\w+)((\s|<[^>]+>)+)(\\1\b)") RegEx2 = re.compile("^([^\033]*\n)+") RegEx3 = re.compile("^(.)") # Обработка всех файлов, имена которых заданы в командной строке for filename in sys.argv: try: fd = sys.open(filename) except: raise "Ошибка при попытке открыть файл" continue # Считать содержимое файла, обработать с помощью трёх подготовленных # регулярных выражений и вывести найденные совпадения txt_data = fd.read() txt_data = RegEx1.gsub("\033 регулярное_выражение строка_поиска [строка_приёмник... ]

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

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

if 666[А-Я][А-Я]) (.+)} $str_txt {} num owner] { puts "$num $owner" }

В этом примере будут найдены все владельцы автомобилей с "числом зверя" в номере вне зависимости от регистра букв, которыми записан номерной знак (ключ -nocase). Весь совпавший фрагмент не будет сохранён, так как на первом месте в списке строк-приёмников стоит пара фигурных скобок {}, а не имя переменной. Первый фрагмент в скобках (номерной знак) запоминается в переменной num, второй фрагмент в скобках (фамилия владельца) – в переменной owner. Затем значения этих переменных выводятся.

Функция regsub работает аналогично функции regexp:

regsub [ключи] регулярное_выражение строка_поиска строка_замены строка_приёмник

Отличие состоит лишь в том, что после строки поиска записывается строка замены, а строка-приёмник может быть задана только одна.

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

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

sed "/^$/d" filename

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

sed "/^[ TAB]*$/d" filename

Здесь под обозначением TAB подразумевается "настоящий", литеральный символ табуляции (генерируемый при нажатии клавиши Tab на клавиатуре).

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

sed " */ /g" filename

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

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

sed "/^$/!s/^/ /g" filename

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

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

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

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

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

ArticleID=494958

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

6.4.1. Синтаксис регулярных выражений

Регулярные выражения представляют собой образцы для поиска заданных комбинаций символов в текстовых строках и замены их на другие комбинации символов (эти операции называются соответственно сопоставление с образцом и подстановка ). Регулярное выражение в языке PERL имеет вид

/pattern/modifiers

Здесь pattern — это строка, задающая регулярное выражение, а modifiers — необязательные однобуквенные , уточняющие правила использования этого регулярного выражения.

Регулярное выражение может состоять из обычных символов; в этом случае оно будет соответствовать заданной комбинации символов в строке. Например, выражение /кат/ соответствует выделенным подстрокам в следующих строках: "кат ок", "закат ", "укат ить". Однако, подлинную силу регулярным выражениям PERLа придает возможность использования в них специальных метасимволов .

Таблица 6.9. Метасимволы в регулярных выражениях
Символ Описание
\ Для символов, которые обычно трактуются буквально, означает, что следующий символ является метасимволом. Например, /n/ соответствует букве n, а /\n/ соответствует символу перевода строки.
Для метасимволов означает, что символ должен пониматься буквально. Например, /^/ означает начало строки, а /\^/ соответствует просто символу ^. /\\/ соответствует обратной косой черте \.
^ Соответствует началу строки (ср. модификатор ).
$ Соответствует концу строки (ср. модификатор ).
. Соответствует любому символу, кроме разрыва строки (ср. модификатор ).
* Соответствует повторению предыдущего символа нуль или более раз.
+ Соответствует повторению предыдущего символа один или более раз.
? Соответствует повторению предыдущего символа нуль или один раз.
(pattern ) Соответствует строке pattern и .
x | y Соответствует x или y .
{ n } n — неотрицательное число. Соответствует ровно n вхождениям предыдущего символа.
{ n ,} n — неотрицательное число. Соответствует n или более вхождениям предыдущего символа. /x{1,}/ эквивалентно /x+/. /x{0,}/ эквивалентно /x*/.
{ n , m } n и m — неотрицательные числа. Соответствует не менее чем n и не более чем m вхождениям предыдущего символа. /x{0,1}/ эквивалентно /x?/.
[ xyz ] Соответствует любому символу из заключенных в квадратные скобки.
[^ xyz ] Соответствует любому символу, кроме заключенных в квадратные скобки.
[ a - z ] Соответствует любому символу в указанном диапазоне.
[^ a - z ] Соответствует любому символу, кроме лежащих в указанном диапазоне.
\a Соответствует символу звонок (BEL).
\A Соответствует только началу строки, даже с модификатором .
\b Соответствует границе слова, т. е. позиции между \w и \W в любом порядке.
\B Соответствует любой позиции, кроме границы слова.
X Соответствует символу Ctrl+X . Например, /\cI/ эквивалентно /\t/.
\C Соответствует одному байту, даже при директиве use utf8 .
\d Соответствует цифре. Эквивалентно .
\D Соответствует нецифровому символу. Эквивалентно [^0-9].
\e Соответствует символу escape (ESC).
\E Конец преобразований \L , \Q , \U .
\f Соответствует символу перевода формата (FF).
\G Соответствует позиции в строке, равной pos() .
\l Преобразует следующий символ в нижний регистр.
\L Преобразует символы в нижний регистр до \E .
\n Соответствует разрыву строк.
\p property Соответствует символам Unicode, обладающим свойством property . Если property \p{ property } .
\P property Соответствует символам Unicode, не обладающим свойством property . Если property задается несколькими символами, используйте синтаксис \P{ property } .
\Q Добавляет символ "\" перед метасимволами до \E .
\r Соответствует символу возврата каретки (CR).
\s Соответствует символу пробела. Эквивалентно /[ \f\n\r\t]/.
\S Соответствует любому непробельному символу. Эквивалентно /[^ \f\n\r\t]/.
\t Соответствует символу табуляции (HT).
\u Преобразует следующий символ в верхний регистр.
\U Преобразует символы в верхний регистр до \E .
\w Соответствует латинской букве, цифре или подчеркиванию. Эквивалентно / /.
\W Соответствует любому символу, кроме латинской буквы, цифры или подчеркивания. Эквивалентно /[^A-Za-z0-9_] /.
\X Соответствует последовательности символов Unicode из основного символа и набора диакритических значков. Эквивалентно выражению /C<(?:\PM\pM*)>/.
\z Соответствует только концу строки, даже с модификатором .
\Z Соответствует только концу строки или разрыву строк в конце строки, даже с модификатором .
\ n n — положительное число. Соответствует . Если левых скобок до этого символа меньше, чем n , и n > 9, то эквивалентно \0n .
\0 n n — восьмеричное число, не большее 377. Соответствует символу с восьмеричным кодом n . Например, /\011/ эквивалентно /\t/.
\x n n — шестнадцатеричное число, состоящее из двух цифр. Соответствует символу с шестнадцатеричным кодом n . Например, /\x31/ эквивалентно /1/.
\x{ n } n — шестнадцатеричное число, состоящее из четырех цифр. Соответствует символу Unicode с шестнадцатеричным кодом n . Например, /\x{2663}/ эквивалентно /♣/.

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

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

Игнорирует регистр символов при сопоставлении с образцом. При использовании директивы use locale приведение символов к одному регистру производится с учетом национальной настройки. Рассматривает исходную строку как буфер из нескольких строк текста, разделенных разрывами строк. Это означает, что метасимволы ^ и $ соответствуют не только началу и концу всей строки, но и началу и концу строки текста, ограниченной разрывами строк. Рассматривает исходную строку как единую строку текста, игнорируя разрывы строк. Это означает, что метасимвол . соответствует любому символу, включая разрыв строки. Разрешает использование пробелов и комментариев. Пробелы, не имеющие предшествующего символа \ и не заключенные в , игнорируются. Символ # начинает комментарий, который также игнорируется.

6.4.3. Классы символов Unicode и POSIX

Мы можем использовать в регулярных выражениях синтаксис

[:class:]

где class задает название класса символов POSIX, т. е. мобильного стандарта на язык C. При использовании директивы use utf8 вместо классов POSIX можно использовать классы символов Unicode в конструкции

\p{class}

В следующей таблице сведены все классы символов POSIX, соответствующие классы символов Unicode и метасимволы, если они есть.

Таблица 6.10. Классы символов
POSIX Unicode Метасимвол Описание
alpha IsAlpha Буквы
alnum IsAlnum Буквы и цифры
ascii IsAscii Символы ASCII
cntrl IsCntrl Управляющие символы
digit IsDigit \d Цифры
graph IsGraph Буквы, цифры и знаки пунктуации
lower IsLower Строчные буквы
print IsPrint Буквы, цифры, знаки пунктуации и пробел
punct IsPunct Знаки пунктуации
space IsSpace \s Символы пробела
upper IsUpper Прописные буквы
word IsWord \w Буквы, цифры и подчеркивание
xdigit IsXDigit Шестнадцатеричные цифры

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

/\d+/ /[:digit:]+/ /\p{IsDigit}+/ # use utf8

Для указания того, что символ не принадлежит к заданному классу, используются конструкции

[:^class:] \P{class}

Например, следующие выражения имеют одинаковый смысл:

[:^digit:] \D \P{IsDigit} [:^space:] \S \P{IsSpace} [:^word:] \W \P{IsWord}

6.4.4. Запоминание подстрок

Использование круглых скобок в регулярном выражении приводит к тому, что подстрока, соответствующая образцу в скобках, запоминается в специальном буфере. Для доступа к n -ной запомненной подстроке внутри регулярного выражения используется конструкция \ n , а вне него — $ n , где n может принимать любые значения, начиная с 1. Однако, следует помнить, что PERL использует выражения \10 , \11 и т. д. как синонимы для восьмеричных кодов символов \010 , \011 и т. д. Неоднозначность здесь разрешается так. Символ \10 считается обращением к 10-й запомненной подстроке, если перед ним в регулярном выражении стоит не менее десяти левых круглых скобок; в противном случае, это символ с восьмеричным кодом 10. Метасимволы \1 , … \9 всегда считаются обращениями к запомненным подстрокам. Примеры:

If (/(.)\1/) { # ищем первый повторяющийся символ print ""$1" - первый повторяющийся символ\n"; } if (/Time: (..):(..):(..)/) { # извлекаем компоненты времени $hours = $1; $minutes = $2; $seconds = $3; }

Помимо переменных $1 , $2 , … есть еще несколько специальных переменных, в которых сохраняются результаты последней операции с регулярным выражением, а именно:

Приведем пример:

"AAA111BBB222"=~/(\d+)/; print "$`\n"; # AAA print "$&\n"; # 111 print "$"\n"; # BBB222 print "$+\n"; # 111

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

6.4.5. Расширенные образцы

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

(?#text ) Комментарий. Вся конструкция игнорируется. (?modifiers -modifiers ) Включает или выключает заданные . Модификаторы, стоящие до символа - , включаются, стоящие после него — выключаются. Пример:

If (/aaa/) { … } # сопоставление с учетом регистра if (/(?i)aaa/) { … } # сопоставление без учета регистра

(?:pattern ) (?modifiers -modifiers :pattern ) Позволяет группировать подвыражения регулярного выражения без запоминания найденного соответствия. Вторая форма дополнительно включает или выключает заданные . Например, выражение /ко(?:т|шка)/ — это краткая запись выражения /кот|кошка/ . (?=pattern ) Соответствие с заглядыванием вперед без запоминания найденного соответствия. Например, выражение /Windows (?=95|98|NT|2000)/ соответствует "Windows" в строке "Windows 98", но не соответствует в строке "Windows 3.1". После сопоставления поиск продолжается с позиции, следующей за найденным соответствием, без учета заглядывания вперед. (?!pattern ) Несоответствие с заглядыванием вперед без запоминания найденного соответствия. Например, выражение /Windows (?!95|98|NT|2000)/ соответствует "Windows" в строке "Windows 3.1", но не соответствует в строке "Windows 98". После сопоставления поиск продолжается с позиции, следующей за найденным соответствием, без учета заглядывания вперед. (?<=pattern ) Соответствие с заглядыванием назад без запоминания найденного соответствия. Например, выражение /(?<=\t)\w+/ соответствует слову, следующему за символом табуляции, и символ табуляции не включается в $& . Фрагмент, соответствующий заглядыванию назад, должен иметь фиксированную ширину. (?pattern ) Несоответствие с заглядыванием назад без запоминания найденного соответствия. Например, выражение /(?6.4.6. Операции с регулярными выражениями

До сих пор мы заключали регулярные выражения в символы // . На самом деле символы-ограничители регулярного выражения определяются q-операцией , которую мы к ним применяем. В этом разделе подробно описаны все операции языка PERL с регулярными выражениями.

6.4.6.1. Сопоставление с образцом

Синтаксис : /pattern /modifiers m/pattern /modifiers

pattern и возвращает истину или ложь в зависимости от результата сопоставления. Сопоставляемая строка задается левым операндом операции =~ или!~ , например:

$mynumber = "12345"; if ($mynumber =~ /^\d+$/) { # если строка $mynumber состоит из десятичных цифр, то… ... }

Если строка не задана, то производится сопоставление с содержимым специальной переменной $_ . В частности, предыдущий пример можно переписать так:

$_ = "12345"; if (/^\d+$/) { ... }

Если регулярное выражение заключено в // , то начальное m необязательно. Конструкция с начальным m позволяет использовать в качестве ограничителей регулярного выражения любые символы, допустимые в q-операциях. Полезные частные случаи:

Если pattern

Если не задан модификатор g и результат сопоставления присваивается списку, то при неудачном сопоставлении возвращается пустой список. Результат удачного сопоставления зависит от наличия круглых скобок в образце. Если их нет, то возвращается список (1) . В противном случае возвращается список, состоящий из значений переменных $1, $2 и т. д., т. е. список всех запомненных подстрок. Следующий пример

($w1, $w2, $rest) = ($x =~ /^(\S+)\s+(\S+)\s*(.*)/);

заносит в переменную $w1 первое слово строки $x , в переменную $w2 ее второе слово, а в переменную $rest — остаток этой строки.

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

$_ = "12:23:45"; @result = /\d+/g; foreach $elem (@result) { print "$elem\n"; }

выведет на экран строки 12 , 23 и 45 .

В скалярном контексте сопоставление с модификатором g каждый раз ищет следующее соответствие образцу и возвращает истину или ложь в зависимости от результата поиска. Позиция в строке после последнего сопоставления может быть считана или изменена функцией pos() . Неудачный поиск обычно сбрасывает позицию поиска в нуль, но мы можем избежать этого, добавив модификатор c . Изменение строки также приводит к сбросу позиции поиска в ней.

Дополнительные возможности предоставляет метасимвол \G , который имеет смысл только в сочетании с модификатором g . Этот метасимвол соответствует текущей позиции поиска в строке. Использование конструкции m/\G…/gc удобно, в частности, для написания лексических анализаторов, выполняющих различные действия для встреченных в анализируемом тексте лексем. Следующий пример

$_ = "Word1, word2, and 12345."; LOOP: { print("number "), redo LOOP if /\G\d+\b[,.;]?\s*/gc; print("word "), redo LOOP if /\G+\b[,.;]?\s*/gc; print("unknown "), redo LOOP if /\G[^A-Za-z0-9]+/gc; }

выведет на экран строку word word word number .

6.4.6.2. Единственное сопоставление с образцом

Синтаксис : ?pattern ? m?pattern ?

Эта конструкция полностью аналогична конструкции m/pattern / с единственным отличием: успешное сопоставление с образцом выполняется только один раз между вызовами функции reset() . Это удобно, например, когда нам нужно найти только первое вхождение образца в каждом файле из просматриваемого набора, например:

While (<>) { if (?^$?) { ... # обработать первую пустую строку файла } } continue { reset if eof; # сбросить статус?? для следующего файла }

6.4.6.3. Создание регулярного выражения

Синтаксис : qr/string /modifiers

Эта конструкция создает регулярное выражение с текстом string и модификаторами modifiers и компилирует его. Если ограничителями являются символы "" , то интерполяция строки string o

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

$re = qr/\d+/; $string =~ /\s*${re}\s*/; # включение в другое регулярное выражение $string =~ $re; # самостоятельное использование $string =~ /$re/; # то же самое $re = qr/$header/is; s/$re/text/; # то же, что s/$header/text/is

6.4.6.4. Подстановка

Синтаксис : s/pattern /string /modifiers

Эта операция сопоставляет заданную строку с образцом pattern и заменяет найденные фрагменты на строку string . Она возвращает количество произведенных замен или ложь (точнее, пустую строку), если сопоставление закончилось неудачей. Сопоставляемая строка задается левым операндом операции =~ или!~ . Она должна быть скалярной переменной, элементом массива или элементом ассоциативного массива, например:

$path = "/usr/bin/perl"; $path =~ s|/usr/bin|/usr/local/bin|;

$_ = "/usr/bin/perl"; s|/usr/bin|/usr/local/bin|;

Помимо стандартных, здесь могут употребляться следующие модификаторы:

pattern string должен иметь собственную пару ограничителей, например s(foo) или s/bar/ .

Если ограничителями являются символы "" , то интерполяция строки pattern не производится. В остальных случаях происходит интерполяция образца и если он содержит переменные, то при каждом сопоставлении производится его компиляция. Чтобы избежать этого, используйте модификатор o (разумеется, если вы уверены, что значения переменных, входящих в образец, остаются неизменными).

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

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

Модификатор e указывает, что string является выражением. В этом случае к string сначала применяется функция eval() , а затем производится подстановка. Пример:

$_ = "123"; s/\d+/$&*2/e; # $_ = "246" s/\d/$&*2/eg; # то же самое

Приведем еще несколько типичных примеров использования операции подстановки. Удаление комментариев вида /*…*/ из текста Java- или C-программы:

$program =~ s { /\* # Начало комментария.*? # Минимальное количество символов \*/ # Конец комментария }gsx;

Удаление начальных и конечных пробелов в строке $var:

For ($var) { s/^\s+//; s/\s+$//; }

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

S/([^ ]*) *([^ ]*)/$2 $1/;

Замена табуляций на пробелы с выравниванием по колонкам, кратным восьми:

1 while s/\t+/" " x (length($&)*8 - length($`)%8)/e;

6.4.6.5. Транслитерация

Синтаксис : tr/list1 /list2 /modifiers y/list1 /list2 /modifiers

Транслитерация состоит в замене всех символов из списка list1 соответствующими символами из списка list2 . Она возвращает количество замененных или удаленных символов. Списки должны состоять из отдельных символов и/или диапазонов вида a-z . Преобразуемая строка задается левым операндом операции =~ или!~ . Она должна быть скалярной переменной, элементом массива или элементом ассоциативного массива, например:

$test = "ABCDEabcde"; $test =~ tr/A-Z/a-z/; # замена строчных букв на прописные

Если строка не задана, то операция подстановки производится над специальной переменной $_ . В частности, предыдущий пример можно переписать так:

$_ = "ABCDEabcde"; tr/A-Z/a-z/;

Мы можем использовать вместо // любой символ, допустимый в q-операциях. Если list1 заключен в парные скобки, то list2 должен иметь собственную пару ограничителей, например tr(A-Z) или tr/a-z/ .

Обычно эта операция называется tr . Синоним y введен для фанатиков редактора sed и используется только ими. Транслитерация поддерживает следующие модификаторы:

Модификатор c вызывает транслитерацию всех символов, не входящих в список list1 . Например, операция tr/a-zA-Z/ /c заменит все символы, не являющиеся латинскими буквами, на пробелы.

По умолчанию, если list2 короче, чем list1 , он дополняется последним своим символом, а если он пуст, то принимается равным list1 (это удобно для подсчета количества символов определенного класса в строке). Модификатор d изменяет эти правила: все символы из list1 , которым нет соответствия в list2 , удаляются из строки. Например, операция tr/a-zA-Z//cd удалит из строки все символы, не являющиеся латинскими буквами.

Модификатор s удаляет повторы: если несколько символов подряд заменились на один и тот же символ, то будет оставлено только один экземпляр этого символа. Например, операция tr/ / /s удаляет в строке повторяющиеся пробелы.

Модификаторы C и U предназначены для перекодировки символов из системной кодировки в UTF-8 и обратно. Первый из них указывает на исходную кодировку, а второй — на кодировку результата. Например, tr/\0-\xFF//CU перекодирует строку из системной кодировки в UTF-8, а tr/\0-\xFF//UC выполнит обратную перекодировку.

Транслитерация производится без интерполяции списков символов, поэтому для использования в ней переменных необходимо вызвать функцию eval() , например.

В данной главе описывается синтаксис регулярных выражений. Чаще всего в Перл они используюстя в операторах поиска и замены таких как s// m/ операторах связки =~ или!= и т.д.

Как правило все эти операторы имеют схожие опции такие как:

i - не различать строчные и заглавные буквы
m - считать строку многострочной
s - однострочная строка
x - расширенный синтаксис (использование пробелов и комментариев)

Обычно все эти опции обозначают как "/x". Их можно использовать даже внутри шаблонов использую новую конструкцию (?...)

Регулярные выражения или шаблоны (pattern) то же самое что и regexp процедуры в Юниксе. Выражения и синтаксис заимствован из свободно распространяемых процедур V8 Генри Спенсера (Henry Spencer) там же они подробно и описаны.

В шаблонах используются следующие метасимволы (символы обозначающие группы других символов) часто называемых egrep - стандартом:

\ - считать следующий метасимвол как обычный символ
^ - начало строки
. - один произвольный символ. Кроме "\n" - конец строки
$ - конец строки
| - альтернатива (или)
() - группировка
- класс символов

Метасимволы имеют модификаторы (пишутся после метасимвола):

* - повторяется 0 или большее число раз
+ - повторяется 1 или большее число раз
? - 1 или 0 раз
{n} - точно n раз
{n,} - по меньшей мере раз
{n,m} - не менше n, но и не больше m

Во все других случаях фигурные скобки считаются обычными (регулярными) символами. Таким образом "*" эквивалентна {0,} , "+" - {1,} и "?" - {0,1}. n и m не могут быть больше 65536.

По умолчанию действие метасимволов "жадно" (greedy). Совпадение распространяется столько раз сколько возможно не учитывая результат действия следуюющих метасимволов. Если вы хотите "уменьшить их аппетит" то используйте символ "?". Это не изменяет значение метасимволов просто уменьшает распространение. Таким образом:

*? - станет 0 и более
+? - 1 и более
?? - 0 или 1 раз
{n}? - точно n раз
{n,}? - не меньше n раз
{n,m}? - больше или равно n и меньше m раз

Шаблоны работают так же, как и двойные кавычки поэтому в них можно использовать `\` - символы (бакслэш-символы):

\t - символ табуляции
\n - новая строка
\r - перевод каретки
\а - перевол формата
\v - вертикальная табуляция
\a - звонок
\e - escape
\033 - восьмеричная запись символа
\x1A - шестнадцатеричная
\c[ - control символ
\l - нижний регистр следующего символа
\u - верхний регистр следующего символа
\L - все символы в нижнем регистре до \E
\U - в верхнем регистре до \E
\E - ограничитель смены регистра
\Q - отмена действия как метасимвола

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

\w - алфавитно-цифровой или "_" символ
\W - не алфавитно-цифровой или "_" символ
\s - один пробел
\S - один не пробел
\d - одна цифра
\D - одна не цифра

Обратите внимание что все это "один" символ. Для обозначения последовательности применяйте модификаторы. Так:

\w+ - слово
\d+ - целое число
[+-]?\d+ - целое со знаком
[+-]?\d+\.?\d* - число с точкой

Кроме того существуют мнимые метасимволы. Обозначающие не существующие символы в месте смены значения. Такие как:

\b - граница слова
\B - не граница слова
\A - начало строки
\Z - конец строки
\G - конец действия m//g

Граница слова (\b) - это мнимая точка между символами \w и \W. Внутри класса символов "\b" обозначает символ backspace (стирания). Метасимволы \A и \Z - аналогичны "^" и "$" но если началостроки "^" и конец строки "$" действуют для каждой строки в многосторочной строке, то \A и \Z обозначают начало и конец всей многосторчной строки.

Если внутри шаблона применяется группировка (круглые скобки) то номер подстроки группы обозначается как "\цифра".

Заметьте что за шаблоном в пределах выражения или блока эти группы обозначаются как "$цифра". Кроме этого существуют дополнительные переменные:

$+ - обозначает последнее совпадение
$& - все совпадение
$` - все до совпадения
$" - все после совпадения

$s = "Один 1 два 2 и три 3"; if ($s =~ /(\d+)\D+(\d+)/) { print "$1\n"; # Результат "1" print "$2\n"; # "2" print "$+\n"; # "2" print "$&\n"; # "1 два 2" print "$`\n"; # "Один " print "$"\n"; # " и три 3" }

Перл версии 5 содержит дополнительные конструкции шаблонов:

(?#комментарий) - комментарий в теле шаблона.
(?:шаблон) - группировка как и "()" но без обратной ссылки
(?=шаблон) - "заглядывание" вперед.

Например /\w+(?=\t)/ соответствует слову, за которым идет табуляция, но символ "\t" не включается в результат.

$s = "1+2-3*4"; if ($s =~ /(\d)(?=-)/) # Наити цифру за которой стоит "-" { print "$1\n"; # Результат "2" } else { print "ошибка поиска\n"; }

(?!шаблон) - "заглядывание" вперед по отрицанию.

$s = "1+2-3*4"; if ($s =~ /(\d)(?!\+)/) # Наити цифру за которой не стоит "+" { print "$1\n"; # Результат "2" } else { print "ошибка поиска\n"; }

(?ismx) - "внутренние" модификаторы. Удобно применять в шаблонах, где например нужно внутри шаблона указать модификатор.

Правила регулярного выражения. (regex)

  1. Любой символ обозначает себя самого если это не метасимвол. Если вам нужно отменить действие метасимвола то поставьте перед ним "\".
  2. Строка символов обозначает строку этих символов.
  3. Множество возможных символов (класс) заключается в квадратные скобки "" это значит что в данном месте может стоять один из указанных в скобках символ. Если первый символ в скобках это "^" - значит не один из указанных символов не может стоять в данном месте выражения. Внутри класса можно употреблять символ "-" обозначающий диаппазон символов. Например a-z один из малых букв латинского алфавита, 0-9 - цифра и т.д.
  4. Все символы, включая специальные можно обозначать с помощью "\" как в языке С.
  5. Альтернативные последовательности разделяются символом "|" Заметьте, что внутри квадратных скобок это обычный символ.
  6. Внутри регулярного выражения можно указыват "подшаблоны", заключая их в крунлые скобки и ссылаться на них как "\номер" Первая скобка обозначается как "\1".

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

Для начала разберемся - что же такое регулярное выражение. По-английски пишется так - Regular Expression (отсюда часто встречается сокращение "regexp" и даже по-русски "регэксп"). Во-первых, не стоит искать смысл в самом термине - это дословный перевод с английского языка, который представляется слишком абстрактным. Но что бы понять по какому принципу работают регулярные выражения, нам и нужно именно что абстрагироваться на уровень предположений. Пример с поиском вхождения подстроки должен быть понятен всем. Но, на самом деле, хотя с помощью регулярных выражений можно легко найти любое вхождение, этот пример не раскрывает всей прелести регэкспов. Лучше вспомните как работает поиск файлов по шаблону (или по маске). Алгоритм подразумевает использование определенных символов (wildcards), которые позволяют как бы закрыть ту часть имени, которая для нас не имеет значения. Однако сами wildcards не используются в именах файлов (что делает алгоритм менее гибким). Так вот, поиск файлов по шаблону позволяет отобрать те имена файлов, которые удовлетворяют заданному условию. При этом, можно указать и точное имя, а можно в каком-то месте имени сделать предположение (с помощью все тех же wildcards). Так вот, регулярные выражения позволяют выполнять аналогичный поиск в пределах некоторой последовательности байт. Добавьте к этому возможность работы с различными частями образованной маски как с отдельными единицами и вы поймете прелесть регэкспов.

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

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

По частям и все сразу

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

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

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

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

$source = "Pupkin";

$source =~ /^Pupkin/; # Оператор вернет истину, так

# как в $source Pupkin с самого начала

$source = "Vasya Pupkin";

$source =~ /^Pupkin/; # А здесь уже будет ложь, так как перед

# Пупкиным стоит его имя.

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

$source = "Pupkin";

$source =~ /Pupkin/; # Оператор вернет истину, так

# как в $source Pupkin с самого начала

$source = "Vasya Pupkin";

$source =~ /Pupkin/; # Здесь то же будет истина, так как

# Пупкин в строке есть, хотя и не с начала.

# Но ведь и шаблон не требовал Пупкина в начале строки

Теперь понятно, что такое мнимые символы? Просто дополнительное условие, а не часть искомого.

Итак, вернемся к нашим баранам

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

И что мы видим? У нас два контейнера, разделенных s. s - это специальный символ, указывающий на любой символ из подмножества пробельных (пробел, табуляция, etc...) Уточню. То что у нас между контейнерами указывает на единичный пробельный символ. Мы подошли к самой важной основополагающей - в регулярном выражении (попросту шаблоне) любой символ соответствует самому себе, если только он не является метасимволом со специальным значением. Вот s как раз и относится к таким метасимволам. Признаюсь, что наш пример вообще сплошь и рядом состоит из метасимволов. Да, да, в нем нет ни одного символа, соответствующего самому себе.

Итак, что же мы выяснили? Мы выяснили, что будем искать нечто, состоящее из двух контейнеров, которые разделены между собой единичным пробельным символом. Теперь пора разобраться с содержимым контейнеров. Начнем с правого - он проще. Точка в регэкспе определяет любой символ, кроме символа новой строки (есть некоторые моменты, когда абсолютно любой). Надеюсь, что такое любой символ понятно? Это может быть "1","2","8","D","a","r" или "b" и так далее и тому подобное от кодов с нуля до самого 255.

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

Итак, чего мы достигли? Мы будем искать нечто, состоящее из двух контейнеров, разделенных единичным пробельным символом. Правый контейнер у нас будет содержать всю ту часть строки, которая находится после единичного пробельного символа. После выполнения регулярного выражения мы сможем использовать содержимое правого (ну и левого то же) контейнера по своему усмотрению. Вот такой вывод на данный момент.

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

Квадратные скобки определяют класс символов. Что такое класс символов? Предположим, что искомое не может быть представлено последовательностью символов, то есть подстрокой. Иначе говоря, в примере с Пупкиным мы не можем явно указать

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

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

Хотя для цифровых символов есть более эффективное решение - метасимвол d. Итак, у нас в левой части определен класс символов. Но какой-то интересный класс получается - вроде привязанный к началу строки. Нет, метасимвол ^ внутри класса указывает на отрицание символов класса. Это значит, что на месте этой части шаблона должен находиться любой символ, не входящий в состав класса. То есть, для примера

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

#!C:/per/bin/perl -w

reg("Vasya Pupkin");

reg(" Vasya Pupkin");

reg("Vasyattpupkin");

print "$1=$1n$2=$2nn" if $_ =~ /([^s]*)s(.*)/;

В результате получится

Теперь давайте разберемся почему и как. Первый тест однозначно попадает под шаблон: Vasya не состоит из пробельных символов, далее следует один пробельный символ (натурально пробел), а Pupkin составляет оставшуюся часть строки. Результат второго теста у нас какой то странный. Первый контейнер у нас оказался пуст, а второй почему то содержит всю строку без ведущего пробела. С чем это связано? Да с тем, что квантификатор * означает ноль или более символов. Так как первым в строке у нас пробельный символ, в правый контейнер, согласно условию, попадает ноль непробельных символов. Далее, пробел то не входит в состав контейнеров. Ну а второй контейнер жрет всю строку до конца. Третий вариант, я думаю, понятен. Я уже говорил, что каждый символ регулярного выражения соответствует единичному. И только квантификаторы позволяют кушать несколько символов одного класса. В шаблоне контейнеры разделены одиночным пробельным символом. В левый контейнер попадает Vasya. Самым законным образом первый пробельный символ (табуляция в примере) пропускается, а правый контейнер кушает все что осталось - в том числе и второй табулятор. Таким образом, получаем Пупкина с ведущей табуляцией.

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

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

Осталось выяснить, каким образом правильно интерпретировать значения второго теста. Во-первых нужно избавиться от привязки к началу строки (по моему этот спецсимвол уже успел потеряться в наших примерах:). Итак, шаблон должен обрабатывать ситуации, когда в начале строки может быть один или несколько пробельных символов. Ну это же элементарно, скажете вы, нужно просто добавить в начало шаблона s и сделать из него квантификатор.

/s*([^s]*)s*(.*)/

Поздравляю! Вы прошли вводный курс по регэкспам;)

Про обжору и другие тонкости

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

Смысл его очевиден - искать Пупкина перед которым может быть что то еще. Однако если источник содержит несколько Пупкиных, то квантификатор сожрет все вплоть до последнего Пупкина. Например поиск по этому регэкспу в строке

Vasya pupkin pupkin

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

для корректного поедания "Vasya " из строки "Vasya pupkin pupkin". Далее, конструкции с фигурными скобками позволяют определять границы апетита квантификатора. Внутри фигурных скобок (естественно после самого квантификатора) может быть указано одно или два значения, перечисленных через запятую, которые соответственно определяют пределы жадности. Впомним про спецификатор *. Аналогичный ему + превращает шаблон в обжору, которого не удовлетворяет менее одного совпадения. То есть при использовании + условие отбора является истинным только когда имеются 1 и более совпадений. Заметьте, что верхний предел у нас неопределен и может быть опущен внутри конструкции с фигурными скобками. Если внутри фигурных скобок указать всего одно значение без запятых, то квантификатор сожрет только такую строку, в которой совпадений с шаблоном будет именно указанное количество.

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

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

$1= Vasya Pupkin

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

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

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

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

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

одиночные символы (characters) - он и есть одиночный, чего его комментировать;)

классы символов (character classes) - , [^]

альтернативные шаблоны (alternative match patterns) - (X|X|X)

квантификаторы (quantifiers) - {}, ?, +, *

мнимые символы (assertions) - s, ^, $, etc...

контейнеры (backreferences) - $1,$2,$x

дополнительные конструкции

От теории к практике

В perl имеются три основных оператора которые работают со строками. Это

m// - проверка совпадений (или поиск)

s/// - подстановка

tr/// - замена

Каждый оператор имеет свои свои модификаторы. Для начала рассмотрим для чего нужны все три оператора.

Первый - m// (или просто //) используется для поиска совпадений с указанным шаблоном. Это как раз то, на чем мы тренировались выше. Там же и пример, как можно его использовать. Второй оператор s/// позволяет не только находить определенные участки, совпадающие с заданным шаблоном, но и выполнять неравнозначную подстановку. Фактически, s/// это то же что и m// (даже модификаторы совпадают), но с возможностью произвольной подстановки. Смысл неравнозначной подстановки открывается когда мы обращаемся к третьему оператору tr///. Оператор замены может заменять участки только на равнозначные по длине. Как следствие - он работает быстрее s///. Из всех операторов s/// самый гибкий - он позволяет выполнять все то, что могут m// и tr///. С его помощью можно свернуть горы. Но, за все приходится платить и здесь мы расплачиваемся скоростью. tr/// можно вообще не рассматривать (если конечо вы не фанат скорости). А вот на s/// хочется остановиться поподробнее.

Прежде всего хочу предупредить - не пытайтесь запихать в правую часть оператора s/// (то есть в ту, которая определяет что будем подставлять вместо найденного шаблона) квантификаторы, мнимые символы и вообще всякие другие неопределенности. Все должно быть четко и однозначно. Работа оператора s/// (в прочем как и m///) подразумевает компиляцию на каждом этапе обращения к регулярному выражению. Если вы не ленились (да и так он часто встречается) то уже знаете про модификатор глобального поиска g, который заставляет работать регэксп на протяжении остатка от предыдущего результата и так до конца строки. Так вот, если в правой части разместить имя переменной-контейнера и заюзать регэксп с модификаторами o и g, то наверняка выйдет бардак, так как o запрещает повторную компиляцию шаблона. В общем тут нужно быть предельно внимательным. Еще хочу обратить ваше внимание на модификаторы e и ee. Они позволяют выполнять код непосредственно в процессе работы регулярного выражения. Если у вас очень сложное задание и его очень трудно реализовать в одном регулярном выражении, разбейте их на составные в правой части - и работать будет быстрее и отлаживать проще.

Список литературы

Для подготовки данной работы были использованы материалы с сайта http://prolib.ru/

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

perlre - регулярные выражения Perl
В этом руководстве описан синтаксис регулярных выражений в языке Perl. Описание того, как практически использовать регулярные выражения в операциях сопоставления с образцом, а также разнообразные примеры на эту тему можно найти в разделах m// и s/// на странице справочного руководства perlop .

ОПИСАНИЕ регулярных выражений

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

Последний обычно называют "модификатор /x", хотя рассматриваемый разделитель может и не быть косой. Фактически, любой из этих модификаторов может быть встроен в регулярное выражение с помощью новой конструкции (?...) . См. ниже.

Сам модификатор /x требует немного более подробного рассмотрения. Он заставляет синтаксический анализатор регулярных выражений игнорировать пробельные символы, не замаскированные обратной косой и не входящие в класс символов. Это можно использовать для разбиения регулярного выражения на (немного) более понятные части. Символ # также рассматривается как метасимвол начала комментария, как в остальном коде на Perl. Взятые вместе, эти возможности делают Perl 5 намного более удобочитаемым языком. См. пример кода для удаления комментариев в программе на C на странице справочного руководства perlop .

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

Шаблоны, используемые при сопоставлении с образцом, являются регулярными выражениями типа используемых в версии 8 библиотеки regexp . (Фактически, соответствующие функции являются производными (хотя и весьма далекими) от свободно распространяемой реализации версии 8, которую выполнил Henry Spencer.) Подробнее см. раздел "Регулярные выражения версии 8" .

В частности, следующие метасимволы имеют стандартные, знакомые по egrep , значения:

По умолчанию, символ "^ " гарантированно соответствует только началу строки, а символ "$ " - только концу строки (или позиции перед символом перевода строки в конце), причем Perl выполняет ряд оптимизаций исходя из предположения, что буфер содержит только одну строку. Встроенным переводам строк не будут соответствовать метасимволы "^ " или "$ ". Может, однако, понадобиться рассматривать буфер как многострочный, так чтобы "^ " соответствовал позиции после символа перевода строки в буфере, а "$ " - позиции перед символом перевода строки. За счет незначительного повышения накладных расходов это можно сделать с помощью модификатора /m в операторе сопоставления с образцом. (Старые программы для этого устанавливали $* , но такая практика теряет смысл в Perl 5.)

Чтобы упростить многострочные подстановки, символ ". " никогда не соответствует символу перевода строки, если только не используется модификатор /s , сообщающий Perl о необходимости рассматривать буфер как однострочный, - даже если в нем несколько строк. Модификатор /s также отменяет установку $* , если используется (неудачный) старый код, устанавливающий его в другом модуле.

Распознаются следующие стандартные квантификаторы :

(Если фигурная скобка встречается в любом другом контексте, она рассматривается как обычный символ.) Модификатор "* " эквивалентен {0,} , модификатор "+ " - {1,} , а модификатор "? " - {0,1} . n и m должны иметь целые значения, не превышающие 65536.

По умолчанию, квантифицированный подшаблон - "жадный", т.е. он будет сопоставляться с как можно большим количеством вхождений, при котором остаток шаблона сможет сопоставиться. Все стандартные квантификаторы "жадные", т.к. сопоставляются с максимально возможным количеством вхождений (начиная с данного места). Если необходимо сопоставление с минимально возможным количеством вхождений, после квантификатора необходимо указать "? ".

Учтите, что изменяется не значение квантификаторов, а "вес" , - они будут сопоставляться с наименьшей возможной подстрокой :

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

\t табуляция
\n перевод строки
\r возврат каретки
\f form feed
\a звуковой сигнал
\e escape (вспомните troff )
\033 восьмеричный символ (вспомните PDP-11)
\x1B шестнадцатеричный символ
\c[ управляющий символ
\l перевести следующий символ в нижний регистр (вспомните vi )
\u перевести следующий символ в верхний регистр (вспомните vi )
\L переводить в нижний регистр до \E (вспомните vi )
\U переводить в верхний регистр до \E (вспомните vi )
\E конец изменения регистра символов (вспомните vi )
\Q маскировать метасимволы regexp до \E

Кроме того, Perl определяет следующие метасимволы :

Учтите, что \w соответствует одному алфавитно-цифровому символу, а не целому слову. Чтобы указать соответствие слову, необходимо использовать \w+ . Метасимволы \w , \W , \s , \S , \d и \D можно использовать при задании классов символов (но не в качестве одной из границ диапазона).

Perl определяет следующие утверждения нулевой длины (zero-width assertions):

Граница слова (\b ) определяется как точка между двумя символами, с одной стороны от которой находится \w , а с другой - \W (в любом порядке), считая воображаемые символы начала и конца строки соответствующими \W . (Внутри классов символов \b представляет забой - backspace, а не границу слова.) Метасимволы \A и \Z аналогичны "^ " и "$ ", но не будут сопоставляться несколько раз при использовании модификатора /m , тогда как "^ " и "$ " будут сопоставляться с границей каждой внутренней строки. Чтобы указать соответствие с реальным концом строки, не исключая символ перевода строки, можно использовать \Z(?!\n) .

При использовании скобочной конструкции (...) , \<цифра> соответствует <цифра> -й подстроке. За пределами шаблона всегда используйте перед цифрой "$ " вместо "\ ". (Запись \<цифра> может в редких случаях срабатывать за пределами текущего шаблона, но на это не надо полагаться. См. ниже.) Область действия $ (а также $` , $& и $" ) распространяется до конца охватывающего блока или оцениваемой строки, или до следующего успешного сопоставления с образцом, в зависимости от того, что будет раньше. Если вы хотите использовать скобки для ограничения подшаблона (например, набора альтернатив), не запоминая его как подшаблон, укажите ? после (.

Можно использовать любое количество скобок. Если имеется более 9 подстрок, переменные $10 , $11 , ... будут ссылаться на соответствующую подстроку. В шаблоне \10 , \11 и т.д. ссылаются на уже сопоставленные подстроки, если их уже было столько до этой обратной ссылки. В противном случае (для обратной совместимости) \10 совпадает с \010 , или символом забоя, а \11 совпадает с \011 , символом табуляции. И так далее. (Последовательности от \1 до \9 всегда рассматриваются как обратные ссылки.)

$+ возвращает то, с чем сопоставилась последняя конструкция в скобках. $& возвращает всю сопоставившуюся строку. (Раньше для этого использовался $0 , но больше не используется.) $` возвращает все, что идет до начала сопоставившейся строки. $" возвращает все, что идет после сопоставившейся строки. Примеры:

S/^([^ ]*) *([^ ]*)/$2 $1/; # поменять местами # два первых слова if (/Time: (..):(..):(..)/) { $hours = $1; $minutes = $2; $seconds = $3; }

Обратите внимание, что все метасимволы, предваряемые обратной косой, в Perl - алфавитно-цифровые, например, \b , \w , \n . В отличие от некоторых языков регулярных выражений, здесь обратная косая не предваряет метасимволы, не являющиеся алфавитно-цифровыми. Поэтому все конструкции вида \\ , \(, \) , \< , \> , \{ или \} всегда интерпретируются как литеральные символы, а не как метасимволы. Это упрощает маскировку строки, которую необходимо использовать в качестве шаблона, но которая, как вы опасаетесь, может содержать метасимволы. Просто замаскируйте все не алфавитно-цифровые символы:

$pattern =~ s/(\W)/\\$1/g;

Для этого можно также использовать встроенную функцию quotemeta() . Еще проще замаскировать метасимволы прямо в операторе сопоставления можно следующим образом

/$unquoted\Q$quoted\E$unquoted/

Perl 5 определяет последовательный синтаксис расширений для регулярных выражений. Для этого используется пара круглых скобок, первым символом в которых указан знак вопроса (в Perl 4 это было синтаксической ошибкой). Символ после знака вопроса задает функцию расширения. Поддерживается несколько расширений:

(?#text)

Комментарий. Текст игнорируется. Если использован переключатель /x для вставки форматирующих пробелов, достаточно указать просто # .

(?:regexp) Группирует элементы аналогично "() ", но не создает обратных ссылок, как "() ". Поэтому split(/\b(?:a|b|c)\b/)

аналогично

Split(/\b(a|b|c)\b/)

но не порождает дополнительные поля.

(?=regexp) Положительный просмотр вперед нулевой длины. Например, /\w+(?=\t)/ соответствует слову, после которого идет символ табуляции, но табуляция не включается в $& .
(?!regexp) Отрицательный просмотр вперед нулевой длины. Например, /foo(?!bar)/ соответствует любому вхождению "foo ", за которым не идет "bar ". Учтите, однако, что просмотр вперед и просмотр назад - НЕ одно и то же. Нельзя использовать эту конструкцию для поиска назад: /(?!foo)bar/ не найдет вхождение "bar ", перед которым не идет "foo ". Так происходит потому, что (?!foo) означает, что дальше не должна идти строка "foo " -- а она и не идет, идет "bar ", поэтому "foobar " будет соответствовать этому шаблону. Необходимо задавать что-то вроде /(?foo)...bar/ . "Вроде" - потому, что перед "bar " может и не быть трех символов. Этот случай можно охватить следующим образом: /(?:(?!foo)...|^..?)bar/ . Иногда все же проще написать: if (/foo/ && $` =~ /bar$/)
(?imsx) Один или несколько встроенных модификаторов сопоставления с образцом. Это особенно полезно для шаблонов, заданных в отдельной таблице, когда некоторые из них должны учитывать регистр символов, а другие - нет. Для учитывающих регистр символов достаточно просто включить (?i) перед шаблоном. Например: $pattern = "foobar"; if (/$pattern/i) # более гибкий способ: $pattern = "(?i)foobar";
if (/$pattern/)

Знак вопроса для этого и новой конструкции минимального сопоставления был выбран потому, что 1) знак вопроса редко встречался в прежних регулярных выражениях и 2) когда вы видите знак вопроса, надо остановиться и "спросить" себя, что же на самом деле происходит. Это психология...

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

Фундаментальное свойство сопоставления регулярных выражений связано с понятием, которое называется поиск с возвратом (backtracking) и используется (при необходимости) всеми квантификаторами регулярных выражений, а именно * , *? , + , +? , {n,m} и {n,m}? .

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

Вот пример поиска с возвратом: предположим, необходимо найти слово, идущее после "foo " в строке "Food is on the foo table. ":

$_ = "Food is on the foo table."; if (/\b(foo)\s+(\w+)/i) { print "$2 follows $1.\n"; }

При выполнении сопоставления для первой части регулярного выражения (\b(foo) ) найдется возможное соответствие прямо в начале строки, при этом в $1 будет помещено значение "Foo ". Однако, как только механизм сопоставления увидит, что после сохраненного в $1 значения "Foo " нет пробела, он поймет свою ошибку и начнет снова со следующего символа после неудавшегося сопоставления an. В этот раз он пройдет до следующего вхождения "foo ". Все регулярное выражение в целом теперь сопоставляется и будет получен ожидаемый результат, "table follows foo. ".

Иногда минимальное сопоставление может оказаться очень полезным. Предположим, необходимо найти все, что идет между строками "foo " и "bar ". Сразу можно написать что-то вроде:

$_ = "The food is under the bar in the barn."; if (/foo(.*)bar/) { print "got <$1>\n"; }

Что, возможно, неожиданно, выдает:

Got

Так произошло потому, что шаблон .* был жадным, вот вы и получили все от первого "foo " до последнего "bar ". В этом случае более эффективно использовать минимальное сопоставление, гарантирующее, что вы получите текст между "foo " и первым же вхождением "bar " после него.

If (/foo(.*?)bar/) { print "got <$1>\n" } got

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

$_ = "I have 2 numbers: 53147";
if (/(.*)(\d*)/) { #Ошибка! print "Beginning is <$1>, number is <$2>.\n"; }

Это вообще не сработает, поскольку шаблон .* был жадным и поглотил всю строку. Поскольку \d* может соответствовать пустой строке, все регулярное выражение в целом успешно сопоставляется.

Beginning is , number is <>.

Вот еще несколько вариантов, большинство из которых не сработает:

$_ = "I have 2 numbers: 53147"; @pats = qw{ (.*)(\d*) (.*)(\d+) (.*?)(\d*) (.*?)(\d+) (.*)(\d+)$ (.*?)(\d+)$ (.*)\b(\d+)$ (.*\D)(\d+)$ }; for $pat (@pats) { printf "%-12s ", $pat; if (/$pat/) { print "<$1> <$2>\n"; } else { print "FAIL\n"; } } В результате будет выдано:
(.*)(\d*) <> (.*)(\d+) <7> (.*?)(\d*) <> <> (.*?)(\d+) <2> (.*)(\d+)$ <7> (.*?)(\d+)$ <53147> (.*)\b(\d+)$ <53147> (.*\D)(\d+)$ <53147>

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

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

$_ = "ABC123"; if (/^\D*(?!123)/) { # Ошибка! print "Yup, no 123 in $_\n"; }

Но результата не будет; по крайней мере, такого, как вы ожидали. Утверждается, что в строке нет 123 . Вот более четкая картина того, почему, вопреки популярным ожиданиям, произошло сопоставление:

$x = "ABC123" ; $y = "ABC445" ; print "1: got $1\n" if $x =~ /^(ABC)(?!123)/ ; print "2: got $1\n" if $y =~ /^(ABC)(?!123)/ ; print "3: got $1\n" if $x =~ /^(\D*)(?!123)/ ; print "4: got $1\n" if $y =~ /^(\D*)(?!123)/ ;

Будет выдано

2: got ABC 3: got AB 4: got ABC

Вы могли ожидать, что проверка 3 не сработает, поскольку она кажется более универсальной версией 1. Важное различие между ними состоит в том, что проверка 3 содержит квантификатор (\D*) и поэтому может использовать поиск с возвратом, тогда как проверка 1 - нет. На самом деле вы спрашиваете: "Правда ли, что в начале $x , после 0 или более не цифр, идет нечто, отличающееся от 123 ?". Если механизм сопоставления позволит \D* расшириться до "ABC ", весь шаблон в целом не сопоставится. Поисковая машина первоначально сопоставит \D* с "ABC ". Затем она попытается сопоставить (?!123) c "123 ", что, конечно, невозможно. Но поскольку в регулярном выражении использован квантификатор (\D*) , поисковая машина может вернуться и поискать другое сопоставление в надежде найти сопоставить все регулярное выражение в целом.

Теперь, поскольку сопоставление шаблона так желанно для поисковой машины, она использует стандартный возврат и повторную попытку regexp (backoff-and-retry) и позволяет на это раз \D* расшириться только до "AB ". Теперь и в самом деле имеется нечто после "AB ", что не совпадает с "123 ". Это "C123 ", что вполне устраивает.

Справиться с эти можно, используя совместно утверждение и отрицание. Мы скажем, что после первой части в $1 должна идти цифра, но там должно идти нечто, отличное от "123 ". Помните, что просмотры вперед - это выражения нулевой длины -- при сопоставлении выполняется только проверка, но не берется часть строки. После таких изменений будет получен желаемый результат; т.е. в случае 5 - неудача, а в случае 6 - успех:

Print "5: got $1\n" if $x =~ /^(\D*)(?=\d)(?!123)/ ; print "6: got $1\n" if $y =~ /^(\D*)(?=\d)(?!123)/ ; 6: got ABC

Другими словами, два утверждения нулевой длины (zero-width assertions), идущие подряд, работают так, как если бы проверялась их конъюнкция, так же, как и при использовании любых встроенных утверждений: шаблон /^$/ сопоставляется, только если вы находитесь в начале строки И в конце строки одновременно. Более глубокое основание этого - в том, что соседство в регулярных выражениях всегда означает И, кроме явного указания ИЛИ с помощью вертикальной черты. /ab/ означает сопоставить "a " И (затем) сопоставить "b ", хотя попытки сопоставления и делаются в разных позициях, т.к. "a " - утверждение не нулевой длины, но длины один.

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

/((a{0,5}){0,5}){0,5}/

А если использовать * вместо ограничения количества вхождений от 0 до 5, сопоставление будет выполняться бесконечно -- или пока не исчерпается место в стеке.

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

Если вам не знакомы "стандартные" функции библиотеки regexp версии 8, вот правила сопоставления с образцом, не описанные выше.

Любой одиночный символ сопоставляется с сами собой, если только это не метасимвол, имеющий специальное значение, описанное здесь или выше. Символы, обычно работающие как метасимволы, можно потребовать интерпретировать литерально, предваряя их символом "\ " (например, "\. " соответствует ". ", а не любому символу; "\\ " соответствует "\ "). Последовательность символов сопоставляется с такой же последовательностью символов в целевой строке, поэтому шаблон blurfl сопоставится с "blurfl " в целевой строке.

Можно задать класс символов, включив список символов в квадратные скобки , которые будут сопоставляться с любым из символов в списке. Если первый символ после "[ " - "^ ", класс сопоставляется с любым символом, не указанным в списке. В списке символ "- " используется для указания диапазона, так что a-z представляет все символы от "a " до "z ", включительно.

Символы можно задавать с использованием синтаксиса метасимволов, во многом аналогичного используемому в C: "\n " соответствует переводу строки, "\t " - табуляции, "\r " - возврату каретки, "\f " - form feed и т.д. В общем случае, \nnn , где nnn - это строка восьмеричных цифр, соответствует символу, значение кода ASCII для которого - nnn . Аналогично, \xnn , где nn - это шестнадцатеричные цифры, соответствует символу, значение кода ASCII для которого - nn . Выражение \cx соответствует символу ASCII control-x . Наконец, метасимвол ". " соответствует любому символу, кроме "\n " (если только не используется /s ).

Можно задавать набор альтернатив для шаблона, разделяя их метасимволом "| ", так что fee|fie|foe сопоставится с любой из подстрок "fee ", "fie " или "foe " в целевой строке (так же, как и f(e|i|o)e ). Учтите, что первая альтернатива включает все от последнего разделителя шаблона ("(", "[ " или от начала шаблона) до первого символа "| ", а последняя альтернатива включает все от последнего символа "| " до следующего разделителя шаблона. Поэтому альтернативы обычно берут в круглые скобки, чтобы не сомневаться, где они начинаются и заканчиваются. Учтите, однако, что в квадратных скобках "| " интерпретируется как литерал, поэтому если вы напишите , сопоставление произойдет только с .

В шаблоне можно выделять подшаблоны (путем взятия их в круглые скобки) для дальнейших ссылок и можно ссылаться обратно на n -й подшаблон в дальнейшем с помощью метасимвола \n . Подшаблоны нумеруются слева направо по открывающим круглым скобкам. Учтите, что обратная ссылка сопоставляется с тем, с чем сопоставился подшаблон в рассматриваемой строке, а не с правилами, задающими этот подшаблон. Поэтому (0|0x)\d*\s\1\d* сопоставится с "0x1234 0x4321 ", но не с "0x1234 01234 ", поскольку подшаблон 1 фактически сопоставился с "0x ", хотя правило 0|0x потенциально могло сопоставиться с начальным 0 во втором числе.

ПРЕДУПРЕЖДЕНИЕ о \1 и $1

Некоторые люди слишком привыкли писать вещи типа

$pattern =~ s/(\W)/\\\1/g;

Корни такой привычки восходят к правой части оператора замены в sed , но это плохая привычка. Дело в том, что с точки зрения Perl правая часть s/// - это строка в двойных кавычках. \1 в обычной строке в двойных кавычках означает control-A . Обычное для Unix значение \1 сохранено в s/// . Однако, если вы привыкните делать именно так, у вас будут проблемы при добавлении модификатора /e .

S/(\d+)/ \1 + 1 /eg; или если вы попытаетесь выполнить s/(\d+)/\1000/;

Этой двусмысленности нельзя избежать, написав \{1}000 , но можно, если написать ${1}000 . Просто операцию интерполяции не надо путать с операцией сопоставления с обратной ссылкой. Конечно, они имеют разное значение в левой части оператора s/// .