Регулярные выражения Perl. WDH: PERL - Регулярные выражения

В данной главе описывается синтаксис регулярных выражений. Чаще всего в Перл они используюстя в операторах поиска и замены таких как 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".

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

/pattern/modifiers

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

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

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

Символ Описание
\ Для символов, которые обычно трактуются буквально, означает, что следующий символ является метасимволом. Например, /n/ соответствует букве n, а /\n/ соответствует символу перевода строки.
Для метасимволов означает, что символ должен пониматься буквально. Например, /^/ означает начало строки, а /\^/ соответствует просто символу ^. /\\/ соответствует обратной косой черте \.
^ Соответствует началу строки (ср. модификатор m ).
$ Соответствует концу строки (ср. модификатор m ).
. Соответствует любому символу, кроме разрыва строки (ср. модификатор s ).
* Соответствует повторению предыдущего символа нуль или более раз.
+ Соответствует повторению предыдущего символа один или более раз.
? Соответствует повторению предыдущего символа нуль или один раз.
(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 Соответствует только началу строки, даже с модификатором m .
\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 Соответствует только концу строки, даже с модификатором m .
\Z Соответствует только концу строки или разрыву строк в конце строки, даже с модификатором m .
\ n 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}/ эквивалентно /♣/.

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

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

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

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

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

[:class:]

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

\p{class}

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

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}

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

Использование круглых скобок в регулярном выражении приводит к тому, что подстрока, соответствующая образцу в скобках, запоминается в специальном буфере. Внутри функции, которая будет выполнять операцию со строкой при помощи вышеприведенного условия, совпадение будет запоминаться в специальных переменных, в PHP к ней можно обращаться через \1 в Perl - $1. В одном условии поиска может быть несколько инструкций запоминания: ({5})({4}) - проверит строку на совпадение с условием, в случае удачного совпадения, запомнит пять букв в \1 ($1), четыре цифры в \2 ($2). Если обратиться к переменной \0, то окажется, что в ней хранится вся совпавшая строка, которая была описана условием.

В РНР существует пять функций поиска по шаблону с использованием Perl-совместимых регулярных выражений:

  • preg_match()
  • preg_match_all()
  • preg_replace()
  • preg_split()
  • preg_grep()

Секреты регулярных выражений (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. Регулярные выражения в конкретных программах

Язык, созданный первоначально с главной целью облегчить обработку большого количества отчетов, просто обязан располагать развитыми средствами для работы с текстом. Напомним, что в среде UNIX, из которой вышел язык Perl, средства для обработки текстовых строк имеются в различных утилитах: sed, awk, grep, cut, а командный интерпретатор shell, также обладающий некоторыми средствами для обработки строк, позволяет организовать совместную работу этих утилит, передавая выход одной программы на вход другой через механизм, называемый конвейером. Такой подход требует написания достаточно изощренных сценариев на языке shell в сочетании с обращением к внутренним командам утилит обработки текста sed или awk. Язык Perl, являясь средством создания программ-сценариев, в то же время один обладает всеми возможностями перечисленных утилит и даже их превосходит. Типичная задача, возникающая при обработке текстового файла, заключается в том, чтобы найти в нем фрагмент, удовлетворяющий заданным условиям, и выполнить над найденным фрагментом некоторую операцию: удалить, заменить на другой фрагмент, извлечь для дальнейшего использования и т. д. Условия поиска можно достаточно просто выразить словами. Например: найти строку, содержащую слово Perl. Или: найти все фрагменты, находящиеся в конце строки и содержащие две цифры, за которыми следует произвольное количество прописных букв. Для формализованной записи подобных условий используются регулярные выражения, позволяющие описать образец, или шаблон поиска при помощи специальных правил. Манипуляции с регулярными выражениями осуществляются при помощи соответствующих операций, которые мы также рассмотрим в этой главе.

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

Регулярное выражение, по сути, представляет собой набор правил для описания текстовых строк. Сами правила записываются в виде последовательности обычных символов и метасимволов, которая затем в качестве образца используется в операциях поиска и замены текста. Метасимволы - это символы, имеющие в регулярном выражении специальное значение. Пользователи DOS/Windows хорошо знают метасимвол *, используемый для порождения имен файлов и обозначающий любую допустимую последовательность. Регулярные выражения используются многими программами UNIX, в том числе интерпретатором shell. Каждая из них использует свое множество метасимволов. В большинстве случаев метасимволы разных программ совпадают.

Метасимволы

В языке Perl к метасимволам относятся следующие символы: "\", ".", "^", "$", "|", "[", "]", "(", ")", "*", "+", "?", "{", "}" Различные метасимволы выполняют в регулярном выражении разные функции, в частности, используются для обозначения одиночного символа или группы символов, обозначают привязку к определенному месту строки, число возможных повторений отдельных элементов, возможность выбора из нескольких вариантов и т. д. Регулярное выражение, подобно арифметическому выражению, строится с соблюдением определенных правил. В нем можно выделить операнды (элементы) и операции. Простейшим регулярным выражением является регулярное выражение, состоящее из одного обычного символа. Обычный символ в регулярном выражении представляет самого себя. Соответственно, последовательность обычных символов представляет саму себя и не нуждается в дополнительной интерпретации. Для использования в операциях в качестве образца регулярное выражение заключается между двумя одинаковыми символами-ограничителями. Часто в качестве ограничителя используется символ косая черта (/). Например, образцу /Perl/ будут соответствовать все строки, содержащие слово Perl, Если в регулярном выражении какой-либо метасимвол требуется использовать в буквальном, а не специальном значении, его нужно экранировать, или маскировать, при помощи другого метасимвола - \. Например, образцу /\\\*/ соответствует фрагмент текста \*. Здесь первый метасимвол \ экранирует второй метасимвол \, а третий метасимвол \ экранирует метасимвол * Метасимвол. представляет любой одиночный символ, кроме символа новой строки. Так, образцу /./ будет соответствовать любая непустая строка. Если в операциях сопоставления с образцом установлен флаг s, то метасимволу. соответствует также и символ новой строки. Метасимвол [ используется в конструкции [...] для представления любого одиночного символа из числа заключенных в скобки, то есть он представляет класс символов. Два символа, соединенные знаком минус, задают диапазон значений, например задает все прописные и строчные буквы английского алфавита. Если первым символом в скобках является символ ^, вся конструкция обозначает любой символ, не входящий в число перечисленных в скобках. Например, [^0-9] обозначает все нецифровые символы. Ниже мы рассмотрим и другие способы представления классов символов. Метасимволы ^ и $ используются для задания привязки к определенному месту строки. Метасимвол ^ в качестве первого символа регулярного выражения обозначает начало строки. Метасимвол $ в качестве последнего символа регулярного выражения обозначает конец строки. Например, следующим образцам соответствуют: /^$/ - пустая строка (начало и конец, между которыми пусто); /^Perl/ - слово Perl в начале строки; /Perl$/ - слово Perl в конце строки. Метасимвол | можно рассматривать как символ операции, задающей выбор из нескольких вариантов (подобно логической операции ИЛИ). Например, образцу /а | b | с/ соответствует фрагмент текста, содержащий любой из символов а, b, с. Если вариантами выбора являются одиночные символы, как в данном примере, то лучше использовать конструкцию, определяющую класс символов, в данном случае . Но, в отличие от конструкции [...], операция | применима и тогда, когда вариантами выбора являются последовательности символов. Например, образцу /Word|Excel|Windows/ соответствует фрагмент текста, содержащий любое из слов Word, Excel, Windows. Следующая группа метасимволов служит в качестве коэффициентов, или множителей, определяющих количество возможных повторений отдельных атомарных элементов регулярного выражения. r* - нуль и более повторений r; r+ - одно и более повторений r; r? - нуль или одно повторение r; r{n} - ровно n повторений r; r{n,} - n и более повторений r; r{n,m} - минимум n, максимум m повторений r. Атомарные элементы, или атомы, - это простейшие элементы, из которых строится регулярное выражение. Это не обязательно одиночный символ. Вот несколько примеров использования множителей в регулярных выражениях: /.*/ - любая строка; /.+/ - любая непустая строка; /{3}/ - любая последовательность из трех цифр; /\[+/ - последовательность, состоящая из любого числа символов [. В первых двух примерах атомом является метасимвол. (точка). В третьем образце в качестве атома выступает конструкция , определяющая класс цифровых символов. В четвертом образце атом - это пара символов \[, включающая метасимвол \, отменяющий специальное значение следующего за ним метасимвола [. Полный список атомов мы приведем после изучения всех необходимых синтаксических конструкций. Алгоритм, применяемый в операциях поиска и замены для обработки регулярных выражений, содержащих множители, является «жадным»: он пытается найти для образца, снабженного множителем, максимальный сопоставимый фрагмент текста. Рассмотрим, например, что происходит при поиске в строке: «Скроен колпак не по-колпаковски, надо колпак переколпаковать» фрагмента, удовлетворяющего образцу /.*колпак/. Алгоритм найдет максимальный фрагмент, удовлетворяющий выражению.* (вся строка без завершающего символа новой строки), затем начнет двигаться назад, отбрасывая в найденном фрагменте по одному символу, до тех пор, пока не будет достигнуто соответствие с образцом. Найденный фрагмент будет иметь вид: «Скроен колпак не по-колпаковски, надо колпак переколпак». Можно заставить алгоритм работать иначе, снабдив множитель * модификатором?. В этом случае алгоритм из «жадного» превращается в «ленивый» и будет для образца, снабженного множителем, искать минимальный соответствующий фрагмент. «Ленивый» алгоритм для множителя *? начнет поиск в строке с пустого фрагмента "", добавляя к нему по одному символу из строки до тех пор, пока не достигнет соответствия с образцом. В этом случае найденный фрагмент будет иметь вид: «Скроен колпак». Все сказанное справедливо и для других множителей. Например, в строке "1234567" будет найден: для образца /\d*/ - максимальный фрагмент "1234567"; для образца /\d+/ - максимальный фрагмент "1234567"; для образца/\d?/ - максимальный фрагмент "1"; для образца /\d{2,5}/ - максимальный фрагмент "12345"; для образца /\d*?/ - минимальный фрагмент ""; для образца /\d+?/ - минимальный фрагмент "1"; для образца /\d??/ - минимальный фрагмент ""; для образца /\d{2,5}?/ - минимальный фрагмент "12".

Метапоследовательности

Символ \, непосредственно предшествующий одному из метасимволов, отменяет специальное значение последнего. Если же символ \ непосредственно предшествует обычному символу, то, напротив, такая последовательность символов во многих случаях приобретает специальное значение. Подобного рода последовательности будем называть метапоследовательностями, Метапоследовательности в регулярном выражении служат, в основном, для представления отдельных символов, классов символов или определенного места в строке, дополняя и иногда дублируя функции метасимволов. Рассмотрим существующие метапоследовательности.
  • \nnn - представляет символ, восьмеричный код которого равен nnn. Например, последовательность \120\145\162\154 представляет слово Perl (\120 - восьмеричный код буквы Р, \145 - буквы е, \162 - буквы r, \154 - буквы l).
  • \xnn - представляет символ, шестнадцатеричный код которого равен nn. Слово Perl, например, представляется последовательностью \x50\x65\x72\x6C.
  • \cn - представляет управляющий символ, который генерируется при нажатии комбинации клавиш Ctrl+n, где n- символ, например \cD соответствует Ctrl+D.
  • \$- символ $.
  • \@ - символ @.
  • \% - символ %
  • \а - представляет символ с десятичным ASCII-кодом 7 (звонок). При выводе производит звуковой сигнал.
  • \е - символ Esc, десятичный ASCII-код 27.
  • \f - символ перевода страницы, десятичный ASCII-код 12.
  • \n - символ новой строки, десятичный ASCII-код 10.
  • \r - символ «возврат каретки», десятичный ASCII-код 13.
  • \t - символ горизонтальной табуляции, десятичный ASCII-код 9.
  • \v - символ вертикальной табуляции, десятичный ASCII-код 11.
  • \s - представляет класс пробельных символов. К пробельным символам относятся пробел, символ табуляции, возврат каретки, символ новой строки и символ перевода страницы. То же самое, что и[ \t,\r,\n,\f].
  • \S - представляет класс непробельных символов, то же самое, что и класс [^ \t, \r,\n,\f].
  • \d - класс цифровых символов, тоже, что и .
  • \D - класс нецифровых символов, то же, что и [^0-9].
  • \w - представляет класс буквенно-цифровых символов, состоящий из букв, цифр и символа подчеркивания _. То же самое, что и . Обратите внимание, что в этот класс входят только буквы английского алфавита.
  • \W - представляет класс небуквенно-цифровых символов. То же самое, что и выражение [^a-zA-Z_0-9].
  • \А - обозначает начало строки.
  • \Z - обозначает конец строки.

ПРИМЕЧАНИЕ Последовательность \А эквивалентна метасимволу ^ в начале регулярного выражения, а последовательность \Z - метасимволу $ в конце регулярного выражения, за исключением одного случая. Назовем строку, содержащую внутри себя символы новой строки (ASCII 10), мультистрокой. Фактически мультистрока состоит из отдельных строк, разделенных ограничителями - символами новой строки. При выводе мультистрока отображается в виде нескольких строк. Если к мультистроке применяется операция поиска или замены с опцией /m, то последовательности \А и \Z обозначают соответственно начало и конец всей мультистроки, а метасимволам ^ и $ соответствуют еще и границы внутренних строк, образующих мультистроку.

  • \b - обозначает границы слова. Под словом понимается последовательность символов из класса \w. Граница слова определяется как точка между символами из класса \w и символами из класса \W.
  • \В - обозначает не-границы слова, то есть класс символов [^\b].
  • \l - означает, что следующий символ регулярного выражения преобразуется в нижний регистр. Например, запись /\lP/ означает, что символ Р будет преобразован в нижний регистр, после чего новый образец /p/ может быть использован в соответствующей операции поиска или замены,
  • \u - означает, что следующий символ регулярного выражения преобразуется в верхний регистр.
  • \L...\Е - означает, что все символы в регулярном выражении между \L и \Е преобразуются в нижний регистр.
  • \U...\Е -означает, что все символы в регулярном выражении между \U и \Е преобразуются в верхний регистр.
  • \Q...\Е - означает, что все метасимволы в регулярном выражении между \Q и \Е экранируются при помощи символа \. Например, запись /\Q^*?+\Е/ эквивалентна записи /\^\*\?\+/.
  • \G - обозначает точку, в которой закончился предыдущий поиск m//g (см. описание операции поиска m//).

Атомы

Из всех метасимволов, перечисленных в начале параграфа, нам осталось рассмотреть метасимволы (и). Они служат для группирования ряда элементов, входящих в состав образца, в один элемент. Например, образцу /(abc)+/ соответствует строка, состоящая из одного или более повторений последовательности abc, в то время как образцу /abc+/ - строка, состоящая из начальных символов ab, за которыми следует один или более символов с. Теперь мы можем перечислить атомы, из которых строится регулярное выражение.
  • Регулярное выражение в скобках, представляющее несколько элементов, сгруппированных в один элемент.
  • Любой обычный символ (не метасимвол).
  • Символ. , представляющий любой одиночный символ, кроме символа новой строки.
  • Конструкция [...], представляющая класс символов, перечисленных в квадратных скобках.
  • Метапоследовательность, представляющая символ или класс символов: \а, \n, \r, \t, \f, \e, \d, \D, \w, \W, \s, \S.
  • Метапоследовательность вида \nnn, определяющая символ при помощи его восьмеричного ASCII-кода nnn.
  • Метапоследовательность вида \xnn, определяющая символ при помощи его шестнадцатеричного ASCII-кода nn.
  • Метапоследовательность вида \cn, представляющая управляющий символ Ctrl+n.
  • Конструкция вида \number, представляющая обратную ссылку (см. следующий раздел).
  • Любая конструкция вида \character, не имеющая специального значения, а представляющая собственно символ character, например: \*, \у, \h. Напомним, что в регулярном выражении множители *, +, ?, {n,m} применяются именно к атому, расположенному непосредственно слева.

    Обратные ссылки

    Ранее мы установили, что группу элементов регулярного выражения можно заключить в скобки и рассматривать как один элемент. Заключение группы элементов в скобки имеет дополнительный и весьма полезный эффект. Если в результате поиска будет найден фрагмент текста, соответствующий образцу, заключенному в скобки, то этот фрагмент будет сохранен в специальной переменной, и внутри регулярного выражения к нему можно будет обратиться, используя запись \number, где number- номер конструкции (...) в исходном регулярном выражении. Запись \number, указывающую на найденный по образцу фрагмент текста, будем называть обратной ссылкой. Можно задать любое количество конструкций вида (...) и ссылаться на соответствующие найденные фрагменты текста как на \1, \2 и т. д. Например, образцу /(.+)-\1/ соответствуют слова «ха-ха», «хи-хи», «ку-ку!> и т. п., а образцу /(.)(.).?\2\1/ соответствуют все палиндромы из четырех или пяти букв, (Палиндром - слово или предложение, которое одинаково читается слева направо и справа налево.) Внутри образца конструкция \n (n= 1,...,9) всегда обозначает обратную ссылку. Запись вида \nn также интерпретируется как обратная ссылка, но только в том случае, если в исходном выражении задано не менее, чем nn скобочных конструкций вида (...). Иначе запись \nn обозначает символ с восьмеричным кодом nn. Для ссылки на найденный фрагмент текста за пределами регулярного выражения, например, при задании замещающего текста в операции замены, вместо записи \number используется запись $number. Например, операция замены $str =~ s/(\S+)\s+(\S+)/$2 $1/ меняет местами первые два слова в строке $str. Область действия переменных $1, $2 и т. д. распространяется до наступления одного из следующих событий: конец текущего блока; конец строки, являющейся аргументом функции eval; следующее совпадение с образцом. Аналогичным образом определяется область действия и для следующих предопределенных переменных, используемых в операциях сопоставления с образцом:
    • $& - часть строки, найденная при последней операции сопоставления с образцом;
    • $` - часть строки, стоящая перед совпавшей частью при последней успешной операции сопоставления с образцом;
    • $" - часть строки, стоящая после совпавшей части при последней успешной операции сопоставления с образцом.
    Например, в результате выполнения операции поиска $str =~ m/two/ в строке $str = "one two three" образца /two/ будут присвоены следующие значения переменным:
    • $& - "two";
    • $` - "one";
    • $" - "three".
    Эти значения будут сохраняться до наступления одного из перечисленных выше событий, и их можно использовать, например, для формирования строки с обратным порядком следования слов: $rstr=$".$&.$`. Строка $rstr будет иметь вид: "three two one". Следует отметить, что, если обращение к одной из переменных $&, $`, $" встречается где-либо в программе, то интерпретатор perl будет вычислять и запоминать их для каждой операции сопоставления с образцом, что, в свою очередь, замедляет выполнение всей программы. Поэтому не следует использовать данные переменные без особой необходимости.

    Расширенный синтаксис регулярных выражений

    Выше мы использовали скобки для группирования нескольких элементов регулярного выражения в один элемент. Побочным эффектом данной операции является запоминание найденного фрагмента текста, соответствующего образцу, заключенному в скобки, в специальной переменной. Если скобки используются только для группирования элементов регулярного выражения, то найденный фрагмент текста можно не запоминать. Для этого после открывающей скобки (следует поместить конструкцию?:, например в случае задания альтернативы - /(?:Реrl|реrl)/. Конструкция (?:pattern) относится к классу конструкций общего вида (?...), добавляющих новые возможности для задания образцов за счет расширения синтаксиса регулярного выражения, а не за счет введения новых метасимволов или метапоследовательностей. Символ, следующий за символом?, определяет функцию, выполняемую данной синтаксической конструкцией. В настоящее время определены около десяти расширенных конструкций регулярного выражения, большая часть которых рассмотрена в данном разделе. Оставшиеся конструкции, на наш взгляд, не являются необходимыми для первоначального знакомства с языком. (?#text) - комментарий. Текст после символа # и до закрывающей скобки) игнорируется интерпретатором и используется для добавления комментария непосредственно в регулярное выражение. (?imsx-imsx: pattern) - использовать скобки только для группирования элементов без создания обратных ссылок. Символы imsx-imsx между вопросительным знаком и двоеточием интерпретируются как флаги, модифицирующие функцию данного выражения (см. ниже). (?=pattern) - следующий фрагмент в тексте должен соответствовать образцу pattern. Обычно образец для операций поиска или замены задается при помощи регулярного выражения. Результатом операции поиска является фрагмент, соответствующий образцу, который сохраняется в специальной переменной $&. Конструкция (?=pattern) в составе регулярного выражения позволяет задать условие поиска, не включая найденный фрагмент, соответствующий образцу pattern, в результат, сохраняемый в переменной $&. Конструкция (?=pattern) в регулярном выражении задает условие, что следующий фрагмент текста должен удовлетворять образцу pattern. Обращаем внимание на слово следующий. Данная конструкция неприменима для задания условия, что предыдущий фрагмент текста должен соответствовать заданному образцу. Например, образцу/b+(?=с+)/соответствует часть строки, состоящая из одной или более литер b, за которыми следуют одна или более литер с, причем найденный фрагмент текста будет содержать только последовательность литер b без последовательности литер с. Рассмотрим, например, строку: $str = "aaabbbcccddd"; В результате операции поиска $str =~ m/b+(?=c+)/; будут сохранены следующие значения в специальных переменных: S` - ааа, $& - bbb, $" - cccddd. Если в операции поиска указать образец /b+с+/, то значения специальных переменных будут следующими: S` - ааа, $&- bbbccc, $" - ddd. B свою очередь, операция поиска по образцу /(?=b+)с+/ в нашем примере не даст результата. Данный образец задает условие, что следующий фрагмент текста должен содержать непустую последовательность литер b. В нашей строке такой фрагмент будет найден, это фрагмент bbb, но он не будет включен в результат поиска. Следующий фрагмент, в соответствии с образцом, должен представлять непустую последовательность литер с, но в нашем случае этого соответствия не будет, так как мы остановились перед фрагментом bbb, не включив его в результат, и следующим фрагментом поэтому будет bbb, а не ссс. Конструкцию (?=pattern) будем называть регулярным выражением с положительным постусловием. (?!pattern) - конструкция в регулярном выражении задает условие, что следующий фрагмент текста не должен удовлетворять образцу pattern. Найденный фрагмент не запоминается в переменной $&. Например, результат операции поиска $str =~ m/b+(?!с+)/; в рассмотренной выше строке $str будет зафиксирован в следующих значениях специальных переменных: S` - ааа, S& - bb, $" - bcccddd. Найденная подстрока соответствует образцу: она состоит из двух литер bb, за которыми не следует последовательность литер с. По аналогии с предыдущей конструкцией данную конструкцию назовем регулярным выражением с отрицательным постусловием. (?$str =~ m/(?<=b)b+/; значения специальных переменных будут распределены следующим образом: S` - ааа, $& - bb, $" - cccddd. Данную конструкцию назовем регулярным выражением с положительным предусловием. (?
  • i - поиск без учета регистра;
  • m - строка трактуется как мультистрока, состоящая из нескольких строк, разделенных символом новой строки;
  • s - строка трактуется как одна строка, в этом случае метасимволу. соответствует любой одиночный символ, включая символ новой строки;
  • x - разрешается использовать в образцах пробелы и комментарии. При использовании флага х пробелы в образцах игнорируются. Признаком комментария является символ #, как и в основном тексте Perl-программы. Пробелы позволяют сделать образец лучше читаемым.
Одна из литер i , m, s, x после знака - обозначает отмену соответствующего флага. При помощи данной расширенной конструкции можно задать, например, следующий образец: /(?ix) perl # игнорирование регистра при поиске/ Флаг i предписывает не учитывать регистр в операциях сопоставления с образцом, так что образцу будет соответствовать и слово «perl», и слово «Perl». Флаг х позволяет выделить слово «perl» пробелами и использовать непосредственно в образце комментарий. И пробелы, и комментарий не будут учитываться в операции сопоставления с образцом.

Сводка результатов

Изложенное в данном параграфе можно суммировать в виде набора правил, которыми следует руководствоваться при работе с регулярными выражениями.
  1. Любой одиночный символ, не являющийся метасимволом, представляет самого себя.
  2. Специальное значение метасимвола можно отменить, поместив перед ним специальный экранирующий метасимвол \.
  3. Можно определить класс символов, заключив их в квадратные скобки. Если первым после открывающей скобки [ является символ ^, то вся конструкция обозначает класс символов, не входящих в число перечисленных в скобках. Внутри скобок два символа, соединенные знаком -, определяют диапазон. Чтобы включить в состав класса символ -, его следует поместить в начале или в конце списка либо экранировать при помощи символа \.
  4. Символы можно задавать при помощи метапоследовательностей, состоящих из символа \, за которым следует обычный символ или последовательность символов.
  5. Альтернативный выбор задается перечислением вариантов, разделенных символом |. Обычно вся конструкция при этом заключается в круглые скобки.
  6. Внутри регулярного выражения можно выделить подобразец, заключив его в круглые скобки. На n-ю конструкцию в скобках можно затем сослаться, используя нотацию \n внутри и $n - вне регулярного выражения.
В заключение приведем сводку метасимволов и метапоследовательностей, рассмотренных в данной главе. Таблица 8.1. Символы, имеющие специальное значение в регулярном выражении Perl
Метасимвол Интерпретация
\ Отменяет (экранирует) специальное значение следующего за ним метасимвола
. Любой одиночный символ, кроме символа новой строки. Любой одиночный символ, включая символ новой строки, если в операции сопоставления с образцом задан флаг s
^ Обозначает начало строки, если является первым символом образца
$ Обозначает коней строки, если является последним символом образца
| Разделяет альтернативные варианты
[...] Любой одиночный символ из числа перечисленных в квадратных скобках. Пара символов, разделенных знаком минус, задает диапазон символов. Например, задает все прописные и строчные буквы английского алфавита. Если первым символом в скобках является символ ^, вся конструкция обозначает любой символ, не входящий в число перечисленных в скобках. Внутри скобок символы. * [ и \ теряют свое специальное значение.
(...) Группирование элементов образца в один элемент
* Нуль и более повторений регулярного выражения, стоящего непосредственно перед *
+ Одно или более повторений регулярного выражения, стоящего Непосредственно перед +
? Одно или ни одного повторения регулярного выражения, стоящего непосредственно перед?
{n, m} Минимальное n и максимальное m число повторений регулярного выражения, стоящего перед {n, m}. Конструкция {n} означает ровно n повторений, {n,} - минимум n повторений

Таблица 8.2. Метапоследовательности в регулярных выражениях Perl
Метапоследовательность Значение
\0nn Символ, восьмеричный код которого равен nn
При выводе производит звуковой сигнал
Обозначает начало строки
\b Обозначает границы слова. Под словом понимается последовательность символов из класса \w. Граница слова определяется как точка между символами из класса \w и символами из класса \W
\B Обозначает не-границы слова
\cn Управляющий символ, который генерируется при нажатии комбинации клавиш Ctrl+n
\d Любой цифровой символ, то же, что и
\D Любой нецифровой символ, то же, что и [^0-9]
Символ Esc, ASCII 27
\E Ограничитель последовательностей \L, \U, \Q
\f Символ перевода страницы, ASCII 12
\G Обозначает точку, в которой закончился предыдущий поиск m//g
\l Преобразует следующий символ регулярного выражения в нижний регистр
\L Преобразует все последующие символы в регулярном выражении в нижний регистр до тех пор, пока не встретится последовательность \Е
\n Символ новой строки, ASCII 10
\Q Эквивалентно экранированию всех последующих метасимволов в регулярном выражении при помощи символа \ до тех пор, пока не встретится последовательность \Е
\r Символ «возврат каретки», ASCII 13
\s Класс пробельных символов: пробел (space), символ табуляции (tab), возврат каретки (carriage return), символ перевода строки (line feed) и символ перевода страницы (form feed); эквивалентно [\t,\r,\n,\f]
\S Класс непробельных символов
\t Символ табуляции, ASCII 9
\u Преобразует следующий символ в верхний регистр
\U Преобразует все последующие символы в регулярном выражении в верхний регистр до тех пор, пока не встретится последовательность \Е
\v Символ вертикальной табуляции, ASCII 11
\w Любая буква, цифра или символ подчеркивания
\W Любой символ, не являющийся буквой, цифрой или символом подчеркивания
\xnn Символ, шестнадцатеричный код которого равен nn
\Z Обозначает конец строки

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

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

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

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

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

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

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

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

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

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

$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("Vasya\t\tpupkin");

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

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

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