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


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

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

Операции сопоставления могут иметь различные модификаторы, в том числе, связанные с интерпретацией используемых регулярных выражений. Вот эти модификаторы:
i Игнорировать регистр символов при сопоставлении с образцом.
m Обрабатывать буфер как несколько строк.
s Обрабатывать буфер как одну строку.
x Повысить удобочитаемость шаблона путем включения пробелов и комментариев.

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

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

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

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

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

\ Маскирует следующий метасимвол
^ Соответствует началу строки
. Соответствует любому символу (кроме символа перевода строки)
$ Соответствует концу строки (перед символом перевода строки в конце)
| Дизъюнкция
() Группировка
[] Класс символов

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

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

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

* Соответствует 0 или более вхождений
+ Соответствует 1 или более вхождений
? Соответствует 1 или 0 вхождений
{n} Соответствует ровно n вхождений
{n,} Соответствует не менее n вхождений
{n,m} Соответствует не менее n и не более m вхождений

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

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

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

*? Соответствует 0 или более вхождений
+? Соответствует 1 или более вхождений
?? Соответствует 1 или 0 вхождений
{n}? Соответствует ровно n вхождений
{n,}? Соответствует не менее n вхождений
{n,m}? Соответствует не менее n и не более m вхождений

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

\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 Соответствует символу, не являющемуся "символом слова"
\s Соответствует пробельному символу
\S Соответствует не пробельному символу
\d Соответствует цифре
\D Соответствует не цифре

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

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

\b Соответствует границе слова
\B Соответствует позиции, не являющейся границей слова
\A Соответствует только позиции в начале строки
\Z Соответствует только позиции в конце строки (или перед последним символом перевода строки)
\G Соответствует только позиции, в которой остановился предыдущий оператор m//g

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

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

Можно использовать любое количество скобок. Если имеется более 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 <d is under the bar in the >

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

if ( /foo(.*?)bar/ ) { 
  print "got <$1>\n"
}
got <d is under the >

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

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

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

Beginning is <I have 2: 53147>, 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*)     <I have 2 numbers: 53147> <>
(.*)(\d+)     <I have 2 numbers: 5314> <7>
(.*?)(\d*)     <> <>
(.*?)(\d+)     <I have > <2>
(.*)(\d+)$     <I have 2 numbers: 5314> <7>
(.*?)(\d+)$     <I have 2 numbers: > <53147>
(.*)\b(\d+)$     <I have 2 numbers: > <53147>
(.*\D)(\d+)$     <I have 2 numbers: > <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). Учтите, что первая альтернатива включает все от последнего разделителя шаблона ("(", "[" или от начала шаблона) до первого символа "|", а последняя альтернатива включает все от последнего символа "|" до следующего разделителя шаблона. Поэтому альтернативы обычно берут в круглые скобки, чтобы не сомневаться, где они начинаются и заканчиваются. Учтите, однако, что в квадратных скобках "|" интерпретируется как литерал, поэтому если вы напишите [fee|fie|foe], сопоставление произойдет только с [feio|].

В шаблоне можно выделять подшаблоны (путем взятия их в круглые скобки) для дальнейших ссылок и можно ссылаться обратно на 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///.
 



Оценить Статью:  
1   2   3   4   5   6   7   8   9   10    

« Назад
SAPE все усложнил?

MainLink - простая и прибыльная продажа ссылок!

Последние поступления:

Стишки пирожки про Олега⁠⁠

Размещена 20 июня 2024 года

Олег купил презервативы
Проник в семидесятый год
И подарил их папе с мамой
Такой нелепый суицид

читать далее…

Размещена 10 августа 2020 года

Я по ТВ видел, что через 10 лет мы будем жить лучше, чем в Германии...
Я не понял, что это они с Германией сделать хотят?!

читать далее…

ТехЗадание на Землю

Размещена 14 марта 2018 года

Пpоект Genesis (из коpпоpативной пеpеписки)

читать далее…

Шпаргалка по работе с Vim

Размещена 05 декабря 2017 года

Vim довольно мощный редактор, но работа с ним не всегда наглядна.
Например если нужно отредактировать какой-то файл например при помощи crontab, без знания специфики работы с viv никак.

читать далее…

Ошибка: Error: Cannot find a valid baseurl for repo

Размещена 13 сентабря 2017 года

Если возникает ошибка на centos 5 вида
YumRepo Error: All mirror URLs are not using ftp, http[s] or file.
Eg. Invalid release/

читать далее…