Spec-Zone.ru › Perl 5.28

perlretut

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ОПИСАНИЕ
  • Часть 1: Основы
    • Простой поиск слов
    • Использование классов символов
    • Сопоставление того или иного
    • Группирование и иерархическое сопоставление
    • Извлечение совпадений
    • Обратные ссылки
    • Относительные обратные ссылки
    • Именованные обратные ссылки
    • Альтернативная нумерация групп захвата
    • Информация о позиции
    • Незахватывающие группировки
    • Сопоставление повторений
    • Позитивные квантификаторы
    • Создание regexp
    • Использование регулярных выражений в Perl
      • Запрет подстановки
      • Глобальное сопоставление
      • Поиск и замена
      • Функция split
  • Часть 2: Расширенные инструменты
    • Подробнее о символах, строках и классах символов
    • Компиляция и сохранение регулярных выражений
    • Компоновка регулярных выражений во время выполнения
    • Встраивание комментариев и модификаторов в регулярное выражение
    • Просмотр вперед и назад
    • Использование независимых подвыражений для предотвращения обратного отслеживания
    • Условные выражения
    • Определение именованных шаблонов
    • Рекурсивные шаблоны
    • Немного магии: выполнение кода Perl в регулярном выражении
    • Глаголы управления обратным отслеживанием
    • Предикаты и отладка
  • СМОТРИТЕ ТАКЖЕ
  • АВТОР И АВТОРСКИЕ ПРАВА
    • Благодарности

НАЗВАНИЕ

perlretut - Руководство по регулярным выражениям Perl

ОПИСАНИЕ

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

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

Что такое регулярное выражение? В самом простом виде регулярное выражение — это шаблон, используемый для определения наличия определённых характеристик в строке. Строка чаще всего представляет собой текст, например, строку, предложение, веб-страницу или даже целую книгу, но реже — двоичные данные. Предположим, мы хотим определить, содержит ли текст в переменной $var последовательность символов m u s h r o o m (пробелы добавлены для наглядности). Мы можем написать на Perl

$var =~ m/mushroom/

Значение этого выражения будет TRUE, если $var содержит эту последовательность символов, и FALSE в противном случае. Часть, заключённая в '/' символы, обозначает характеристику, которую мы ищем. Мы используем термин шаблон для неё. Процесс проверки наличия шаблона в строке называется сопоставлением, а "=~" оператор вместе с m// сообщает Perl попробовать сопоставить шаблон со строкой. Обратите внимание, что шаблон также является строкой, но очень специального типа, как мы увидим. Шаблоны широко используются в наши дни; примерами являются шаблоны, вводимые в поисковую систему для поиска веб-страниц и шаблоны для перечисления файлов в каталоге, например, "ls *.txt" или "dir *.*". В Perl описанные регулярными выражениями шаблоны используются не только для поиска строк, но и для извлечения необходимых частей строк и для выполнения операций поиска и замены.

Регулярные выражения имеют незаслуженную репутацию абстрактных и сложных для понимания. Это действительно просто связано с тем, что обозначение, используемое для их выражения, склонно быть кратким и плотным, а не с внутренней сложностью. Мы рекомендуем использовать /x модификатор регулярных выражений (описанный ниже) вместе с достаточным количеством пробелов, чтобы сделать их менее плотными и более читабельными. Регулярные выражения строятся с помощью простых понятий, таких как условные операторы и циклы, и не сложнее для понимания, чем соответствующие if условные операторы и while циклы языка Perl.

Это руководство сглаживает кривую обучения, обсуждая понятия регулярных выражений вместе с их обозначениями по одному за раз и со многими примерами. Первая часть руководства будет проходить от простых поисков слов к основным понятиям регулярных выражений. Если вы освоите первую часть, у вас будут все инструменты, необходимые для решения около 98% ваших задач. Вторая часть руководства предназначена для тех, кто знаком с основами и жаждет большего. Она обсуждает более продвинутые операторы регулярных выражений и знакомит с последними инновациями.

Примечание: для экономии времени «регулярное выражение» часто сокращают до regexp или regex. Regexp — более естественное сокращение, но произносить его сложнее. Документация Perl pod разделена поровну между regexp и regex; в Perl существует не один способ сократить его. В этом руководстве мы будем использовать regexp.

В версии 5.22, use re 'strict' применяет более строгие правила, чем обычно, при компиляции шаблонов регулярных выражений. Он может находить вещи, которые, хотя и законны, могут не соответствовать вашим намерениям.

Часть 1: Основы

Простой поиск слов

Самый простой regexp — это просто слово или, более обобщенно, строка символов. Regexp, состоящий только из слова, соответствует любой строке, содержащей это слово:

"Hello World" =~ /World/;  # matches

О чём говорит это утверждение Perl? "Hello World" — это простая строка в двойных кавычках. World — это регулярное выражение, а // окружающие /World/ говорят Perl искать совпадение в строке. Оператор =~ связывает строку с результатом сопоставления regexp и возвращает истинное значение, если regexp соответствует, или ложное, если regexp не соответствует. В нашем случае, World соответствует второму слову в "Hello World", поэтому выражение истинно. Такие выражения полезны в условных операторах:

if ("Hello World" =~ /World/) {
    print "It matches\n";
}
else {
    print "It doesn't match\n";
}

Существуют полезные вариации на эту тему. Значение сопоставления можно изменить, используя оператор !~:

if ("Hello World" !~ /World/) {
    print "It doesn't match\n";
}
else {
    print "It matches\n";
}

Литеральную строку в regexp можно заменить переменной:

my $greeting = "World";
if ("Hello World" =~ /$greeting/) {
    print "It matches\n";
}
else {
    print "It doesn't match\n";
}

Если вы сопоставляете со специальной переменной по умолчанию $_, часть $_ =~ можно опустить:

$_ = "Hello World";
if (/World/) {
    print "It matches\n";
}
else {
    print "It doesn't match\n";
}

И, наконец, // стандартные разделители для сопоставления могут быть изменены на произвольные разделители, поместив 'm' перед ними:

"Hello World" =~ m!World!;   # matches, delimited by '!'
"Hello World" =~ m{World};   # matches, note the matching '{}'
"/usr/bin/perl" =~ m"/perl"; # matches after '/usr/bin',
                             # '/' becomes an ordinary char

/World/, m!World!, и m{World} всё означают то же самое. Когда, например, используется кавычка ('"') в качестве разделителя, прямой слэш '/' становится обычным символом и может быть использован в этом regexp без проблем.

Давайте рассмотрим, как разные regexp будут сопоставляться со строкой "Hello World":

"Hello World" =~ /world/;  # doesn't match
"Hello World" =~ /o W/;    # matches
"Hello World" =~ /oW/;     # doesn't match
"Hello World" =~ /World /; # doesn't match

Первый regexp world не соответствует, потому что регулярные выражения чувствительны к регистру. Второй regexp соответствует, потому что подстрока 'o W' встречается в строке "Hello World". Пробельный символ ' ' обрабатывается как любой другой символ в regexp и необходим в этом случае. Отсутствие пробельного символа — причина, по которой третий regexp 'oW' не соответствует. Четвёртый regexp "World " не соответствует, потому что в regexp есть пробел в конце, но не в конце строки. Урок здесь состоит в том, что regexp должны точно соответствовать части строки, чтобы утверждение было истинным.

Если regexp соответствует в более чем одном месте в строке, Perl всегда будет соответствовать в самом раннем возможном месте в строке:

"Hello World" =~ /o/;       # matches 'o' in 'Hello'
"That hat is red" =~ /hat/; # matches 'hat' in 'That'

Что касается сопоставления символов, есть ещё несколько моментов, которые вам нужно знать. Во-первых, не все символы могут быть использованы «как есть» в сопоставлении. Некоторые символы, называемые метасимволами, обычно зарезервированы для использования в обозначениях regexp. Метасимволы:

{}[]()^$.|*+?-#\

Этот список не так определён, как может показаться (или как утверждается в другой документации). Например, "#" является метасимволом только тогда, когда используется модификатор шаблона /x (описанный ниже), а оба "}" и "]" являются метасимволами только в паре с открывающим "{" или "[" соответственно; существуют и другие особенности.

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

"2+2=4" =~ /2+2/;    # doesn't match, + is a metacharacter
"2+2=4" =~ /2\+2/;   # matches, \+ is treated like an ordinary +
"The interval is [0,1)." =~ /[0,1)./     # is a syntax error!
"The interval is [0,1)." =~ /\[0,1\)\./  # matches
"#!/usr/bin/perl" =~ /#!\/usr\/bin\/perl/;  # matches

В последнем регулярном выражении обратный слэш '/' также экранирован, потому что он используется для разграничения регулярного выражения. Однако это может привести к синдрому «наклоненной зубочистки» (leaning toothpick syndrome), и часто более читаемо изменить разделители.

"#!/usr/bin/perl" =~ m!#\!/usr/bin/perl!;  # easier to read

Символ обратного слэша '\' сам является метасимволом и нуждается в экранировании:

'C:\WIN32' =~ /C:\\WIN/;   # matches

В ситуациях, когда для определенного метасимвола не имеет смысла то, что он обычно обозначает, он автоматически теряет свою метасимвольность и становится обычным символом, который должен быть сопоставлен буквально. Например, '}' является метасимволом только тогда, когда он является парой метасимвола '{'. В противном случае он обрабатывается как буквальный ПРАВЫЙ ФИГУРНЫЙ СКОБКА. Это может привести к непредвиденным результатам. use re 'strict' может поймать некоторые из них.

В дополнение к метасимволам существуют некоторые символы ASCII, у которых нет эквивалентов печатных символов, и они представлены последовательностями экранирования. Общие примеры: \t для табуляции, \n для новой строки, \r для возврата каретки и \a для звонка (или сигнала). Если ваша строка лучше рассматривается как последовательность произвольных байтов, то восьмеричная последовательность экранирования, например \033, или шестнадцатеричная последовательность экранирования, например \x1B, может быть более естественным представлением для ваших байтов. Вот несколько примеров экранирования:

"1000\t2000" =~ m(0\t2)   # matches
"1000\n2000" =~ /0\n20/   # matches
"1000\t2000" =~ /\000\t2/ # doesn't match, "0" ne "\000"
"cat"   =~ /\o{143}\x61\x74/ # matches in ASCII, but a weird way
                             # to spell cat

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

$foo = 'house';
'housecat' =~ /$foo/;      # matches
'cathouse' =~ /cat$foo/;   # matches
'housecat' =~ /${foo}cat/; # matches

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

% cat > simple_grep
#!/usr/bin/perl
$regexp = shift;
while (<>) {
    print if /$regexp/;
}
^D

% chmod +x simple_grep

% simple_grep abba /usr/dict/words
Babbage
cabbage
cabbages
sabbath
Sabbathize
Sabbathizes
sabbatical
scabbard
scabbards

Эта программа легко понимается. #!/usr/bin/perl — это стандартный способ вызова программы perl из оболочки. $regexp = shift; сохраняет первый аргумент командной строки как регулярное выражение, которое будет использоваться, оставляя остальные аргументы командной строки для обработки как файлы. while (<>) перебирает все строки во всех файлах. Для каждой строки print if /$regexp/; печатает строку, если регулярное выражение совпадает со строкой. В этой строке оба print и /$regexp/ используют по умолчанию переменную $_ неявно.

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

"housekeeper" =~ /keeper/;    # matches
"housekeeper" =~ /^keeper/;   # doesn't match
"housekeeper" =~ /keeper$/;   # matches
"housekeeper\n" =~ /keeper$/; # matches

Второе регулярное выражение не совпадает, потому что '^' ограничивает keeper соответствием только в начале строки, но "housekeeper" имеет начало посредине. Третье регулярное выражение совпадает, так как '$' ограничивает keeper соответствием только в конце строки.

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

"keeper" =~ /^keep$/;      # doesn't match
"keeper" =~ /^keeper$/;    # matches
""       =~ /^$/;          # ^$ matches an empty string

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

"dogbert" =~ /bert/;   # matches, but not what you want

"dilbert" =~ /^bert/;  # doesn't match, but ..
"bertram" =~ /^bert/;  # matches, so still not good enough

"bertram" =~ /^bert$/; # doesn't match, good
"dilbert" =~ /^bert$/; # doesn't match, good
"bert"    =~ /^bert$/; # matches, perfect

Конечно, в случае буквальной строки можно было бы просто использовать сравнение строк $string eq 'bert', и это было бы более эффективным. Регулярное выражение ^...$ действительно становится полезным, когда мы добавляем более мощные инструменты регулярных выражений ниже.

Использование классов символов

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

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

/cat/;       # matches 'cat'
/[bcr]at/;   # matches 'bat, 'cat', or 'rat'
/item[0123456789]/;  # matches 'item0' or ... or 'item9'
"abc" =~ /[cab]/;    # matches 'a'

В последнем утверждении, даже если 'c' является первым символом в классе, 'a' соответствует, так как первая позиция символа в строке является самой ранней точкой, в которой регулярное выражение может сопоставиться.

/[yY][eE][sS]/;      # match 'yes' in a case-insensitive way
                     # 'yes', 'Yes', 'YES', etc.

Это регулярное выражение демонстрирует распространенную задачу: выполнить сопоставление без учета регистра. Perl предлагает способ избежать всех этих скобок, просто добавив 'i' в конец соответствия. Тогда /[yY][eE][sS]/; можно переписать как /yes/i;. 'i' обозначает сопоставление без учета регистра и является примером модификатора операции сопоставления. Мы встретимся с другими модификаторами позже в учебнике.

Мы видели в предыдущем разделе, что были обычные символы, которые представляли сами себя, и специальные символы, которые нуждались в обратном слэше '\' для представления самих себя. То же самое верно и в классе символов, но наборы обычных и специальных символов внутри класса символов отличаются от тех, что вне класса символов. Специальные символы для класса символов — это -]\^$ (и разделитель шаблона, какой бы он ни был). ']' является специальным, потому что обозначает конец класса символов. '$' является специальным, потому что обозначает скалярную переменную. '\' является специальным, потому что используется в последовательностях экранирования, как и выше. Вот как обрабатываются специальные символы ]$\:

/[\]c]def/; # matches ']def' or 'cdef'
$x = 'bcr';
/[$x]at/;   # matches 'bat', 'cat', or 'rat'
/[\$x]at/;  # matches '$at' or 'xat'
/[\\$x]at/; # matches '\at', 'bat, 'cat', or 'rat'

Последние два немного сложны. В [\$x], обратный слэш защищает знак доллара, поэтому в классе символов есть два члена '$' и 'x'. В [\\$x], обратный слэш защищен, поэтому $x обрабатывается как переменная и подставляется по принципу двойных кавычек.

Специальный символ '-' действует как оператор диапазона внутри классов символов, так что непрерывный набор символов может быть записан как диапазон. С диапазонами громоздкие [0123456789] и [abc...xyz] превращаются в изящные [0-9] и [a-z]. Некоторые примеры:

/item[0-9]/;  # matches 'item0' or ... or 'item9'
/[0-9bx-z]aa/;  # matches '0aa', ..., '9aa',
                # 'baa', 'xaa', 'yaa', or 'zaa'
/[0-9a-fA-F]/;  # matches a hexadecimal digit
/[0-9a-zA-Z_]/; # matches a "word" character,
                # like those in a Perl variable name

Если '-' является первым или последним символом в классе символов, он обрабатывается как обычный символ; [-ab], [ab-] и [a\-b] все эквивалентны.

Специальный символ '^' в первой позиции класса символов обозначает отрицательный класс символов, который соответствует любому символу, кроме тех, что находятся в скобках. Оба [...] и [^...] должны соответствовать символу, иначе соответствие не выполняется. Тогда

/[^a]at/;  # doesn't match 'aat' or 'at', but matches
           # all other 'bat', 'cat, '0at', '%at', etc.
/[^0-9]/;  # matches a non-numeric character
/[a^]at/;  # matches 'aat' or '^at'; here '^' is ordinary

Теперь даже [0-9] может утомить при многократном написании, поэтому в интересах экономии нажатий клавиш и повышения читабельности регулярных выражений Perl имеет несколько сокращений для общих классов символов, как показано ниже. После введения Unicode эти классы символов соответствуют большему количеству символов, чем только символы диапазона ASCII, если модификатор /a не активен.

  • \d соответствует цифре, не только [0-9], но и цифрам из нелатинских письменностей

  • \s соответствует символу пробела, набору [\ \t\r\n\f] и другим

  • \w соответствует символу слова (буквенно-цифровому или '_'), не только [0-9a-zA-Z_], но и цифрам и символам из нелатинских письменностей

  • \D — отрицаемый \d; он представляет любой символ, кроме цифры или [^\d]

  • \S — отрицаемый \s; он представляет любой символ, не являющийся пробелом [^\s]

  • \W — отрицаемый \w; он представляет любой небуквенно-цифровой символ [^\w]

  • Точка '.' соответствует любому символу, кроме "\n" (если модификатор /s не активен, как объяснено ниже).

  • \N, как и точка, соответствует любому символу, кроме "\n", но делает это независимо от того, активен ли модификатор /s.

Модификатор /a, доступный начиная с Perl 5.14, используется для ограничения соответствий \d, \s и \w только символами в диапазоне ASCII. Это полезно, чтобы программа не была излишне подвержена влиянию Unicode (и связанным с ним соображениям безопасности), когда все, что вам нужно, — это обработка текста, похожего на английский. (Буква "а" может быть удвоена, /aa, чтобы обеспечить еще больше ограничений, предотвращая регистронезависимое сопоставление ASCII с не-ASCII символами; в противном случае символ Unicode «знак Кельвина» регистронезависимо сопоставлялся бы с "k" или "K".)

Сокращения \d\s\w\D\S\W можно использовать как внутри, так и вне квадратных скобок в классах символов. Вот несколько примеров их использования:

/\d\d:\d\d:\d\d/; # matches a hh:mm:ss time format
/[\d\s]/;         # matches any digit or whitespace character
/\w\W\w/;         # matches a word char, followed by a
                  # non-word char, followed by a word char
/..rt/;           # matches any two chars, followed by 'rt'
/end\./;          # matches 'end.'
/end[.]/;         # same thing, matches 'end.'

Так как точка является метасимволом, для сопоставления её как обычной точки необходимо экранировать её. Например, поскольку \d и \w являются наборами символов, неправильно считать [^\d\w] как [\D\W]; на самом деле [^\d\w] равно [^\w], что равно [\W]. Подумайте о законах де Моргана.

Фактически, точка и сокращения \d\s\w\D\S\W сами по себе являются типами классов символов, поэтому те, которые окружены квадратными скобками, представляют собой всего лишь один тип класса символов. Когда нам нужно сделать различие, мы называем их «классами символов в квадратных скобках».

Якорная позиция, полезная в базовых регулярных выражениях, — это якорная позиция слова \b. Она соответствует границе между символом слова и несимволом слова \w\W или \W\w:

$x = "Housecat catenates house and cat";
$x =~ /cat/;    # matches cat in 'housecat'
$x =~ /\bcat/;  # matches cat in 'catenates'
$x =~ /cat\b/;  # matches cat in 'housecat'
$x =~ /\bcat\b/;  # matches 'cat' at end of string

Обратите внимание, что в последнем примере конец строки считается границей слова.

Для обработки естественного языка (чтобы, например, апострофы включались в слова), используйте вместо этого \b{wb}

"don't" =~ / .+? \b{wb} /x;  # matches the whole string

Возможно, вы спросите, почему '.' соответствует всем символам, кроме "\n" — почему не каждому символу? Причина в том, что часто происходит сопоставление со строками, и хотелось бы игнорировать символы новой строки. Например, хотя строка "\n" представляет одну строку, мы хотели бы рассматривать её как пустую. Тогда

""   =~ /^$/;    # matches
"\n" =~ /^$/;    # matches, $ anchors before "\n"

""   =~ /./;      # doesn't match; it needs a char
""   =~ /^.$/;    # doesn't match; it needs a char
"\n" =~ /^.$/;    # doesn't match; it needs a char other than "\n"
"a"  =~ /^.$/;    # matches
"a\n"  =~ /^.$/;  # matches, $ anchors before "\n"

Это поведение удобно, потому что мы обычно хотим игнорировать символы новой строки при подсчёте и сопоставлении символов в строке. Однако иногда мы хотим отслеживать символы новой строки. Мы даже можем '^' и '$' установить якоря в начале и конце строк внутри строки, а не только в начале и конце всей строки. Perl позволяет нам выбирать между игнорированием и учётом символов новой строки, используя модификаторы /s и /m . /s и /m обозначают «одна строка» и «много строк», и они определяют, будет ли строка обрабатываться как одна непрерывная строка или как набор строк. Два модификатора влияют на два аспекта интерпретации регулярного выражения: 1) определение класса символов '.', и 2) места, где якоря '^' и '$' могут быть сопоставлены. Вот четыре возможных сочетания:

  • без модификаторов: По умолчанию. '.' соответствует любому символу, кроме "\n". '^' соответствует только началу строки, а '$' соответствует только концу строки или перед символом новой строки в конце.

  • модификатор s (/s): Рассматривать строку как одну длинную строку. '.' соответствует любому символу, даже "\n". '^' соответствует только началу строки, а '$' соответствует только концу строки или перед символом новой строки в конце.

  • модификатор m (/m): Рассматривать строку как набор нескольких строк. '.' соответствует любому символу, кроме "\n". '^' и '$' могут соответствовать началу или концу любой строки внутри строки.

  • оба модификатора s и m (/sm): Рассматривать строку как одну длинную строку, но обнаруживать несколько строк. '.' соответствует любому символу, даже "\n". '^' и '$', однако, могут соответствовать началу или концу любой строки внутри строки.

Вот примеры использования модификаторов /s и /m:

$x = "There once was a girl\nWho programmed in Perl\n";

$x =~ /^Who/;   # doesn't match, "Who" not at start of string
$x =~ /^Who/s;  # doesn't match, "Who" not at start of string
$x =~ /^Who/m;  # matches, "Who" at start of second line
$x =~ /^Who/sm; # matches, "Who" at start of second line

$x =~ /girl.Who/;   # doesn't match, "." doesn't match "\n"
$x =~ /girl.Who/s;  # matches, "." matches "\n"
$x =~ /girl.Who/m;  # doesn't match, "." doesn't match "\n"
$x =~ /girl.Who/sm; # matches, "." matches "\n"

В большинстве случаев поведение по умолчанию является желательным, но /s и /m иногда очень полезны. Если используется /m, начало строки всё ещё можно сопоставить с \A, а конец строки — с якорями \Z (сопоставляется как с концом, так и с символом новой строки перед ним, как '$' ), и \z (сопоставляется только с концом):

$x =~ /^Who/m;   # matches, "Who" at start of second line
$x =~ /\AWho/m;  # doesn't match, "Who" is not at start of string

$x =~ /girl$/m;  # matches, "girl" at end of first line
$x =~ /girl\Z/m; # doesn't match, "girl" is not at end of string

$x =~ /Perl\Z/m; # matches, "Perl" is at newline before end
$x =~ /Perl\z/m; # doesn't match, "Perl" is not at end of string

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

Сопоставление этого или того

Иногда мы хотим, чтобы наше регулярное выражение могло сопоставлять различные возможные слова или строки символов. Это достигается с помощью метасимвола альтернации '|'. Чтобы сопоставить dog или cat, мы формируем регулярное выражение dog|cat. Как и раньше, Perl попытается сопоставить регулярное выражение в самой ранней возможной точке в строке. В каждой позиции символа Perl сначала попытается сопоставить первый вариант dog. Если dog не сопоставляется, Perl попытается сопоставить следующий вариант cat. Если cat также не сопоставляется, сопоставление не выполняется, и Perl переходит к следующей позиции в строке. Некоторые примеры:

"cats and dogs" =~ /cat|dog|bird/;  # matches "cat"
"cats and dogs" =~ /dog|cat|bird/;  # matches "cat"

Несмотря на то, что dog является первым вариантом во втором регулярном выражении, cat может сопоставляться раньше в строке.

"cats"          =~ /c|ca|cat|cats/; # matches "c"
"cats"          =~ /cats|cat|ca|c/; # matches "cats"

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

"cab" =~ /a|b|c/ # matches "c"
                 # /a|b|c/ == /[abc]/

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

Группирование элементов и иерархическое сопоставление

Альтернация позволяет регулярному выражению выбирать между вариантами, но сама по себе она неудовлетворительна. Причина в том, что каждый вариант представляет собой всё регулярное выражение, но иногда нам нужны варианты только для части регулярного выражения. Например, предположим, что мы хотим найти слова «кошки» или «домовые». Регулярное выражение housecat|housekeeper подходит, но неэффективно, потому что мы дважды ввели house. Было бы неплохо иметь постоянные части регулярного выражения, такие как house, а некоторые части имели бы варианты, такие как cat|keeper.

Метасимволы группирования () решают эту проблему. Группирование позволяет рассматривать части регулярного выражения как единый блок. Части регулярного выражения группируются путём помещения их в скобки. Таким образом, мы можем решить задачу housecat|housekeeper путём создания регулярного выражения house(cat|keeper). Регулярное выражение house(cat|keeper) означает сопоставление house с последующим либо cat, либо keeper. Некоторые дополнительные примеры:

/(a|b)b/;    # matches 'ab' or 'bb'
/(ac|b)b/;   # matches 'acb' or 'bb'
/(^a|b)c/;   # matches 'ac' at start of string or 'bc' anywhere
/(a|[bc])d/; # matches 'ad', 'bd', or 'cd'

/house(cat|)/;  # matches either 'housecat' or 'house'
/house(cat(s|)|)/;  # matches either 'housecats' or 'housecat' or
                    # 'house'.  Note groups can be nested.

/(19|20|)\d\d/;  # match years 19xx, 20xx, or the Y2K problem, xx
"20" =~ /(19|20|)\d\d/;  # matches the null alternative '()\d\d',
                         # because '20\d\d' can't match

Альтернативы работают внутри групп так же, как и вне их: в данной позиции строки выбирается та левая альтернатива, которая позволяет выполнить сопоставление регулярного выражения. Таким образом, в последнем примере в первой позиции строки "20" сопоставляется второй вариант, но ничего не остаётся для сопоставления следующих двух цифр \d\d. Поэтому Perl переходит к следующему варианту, который является нулевым вариантом, и это работает, поскольку "20" состоит из двух цифр.

Процесс попытки сопоставления одного варианта, проверки, соответствует ли он, и перехода к следующему варианту, возвращаясь в строке к тому месту, где был пробован предыдущий вариант, если он не соответствует, называется обратным отслеживанием. Термин «обратное отслеживание» происходит от идеи, что сопоставление регулярного выражения подобно прогулке в лесу. Успешное сопоставление регулярного выражения подобно прибытию в пункт назначения. Существуют многие возможные точки начала пути, одна для каждой позиции в строке, и каждая из них пробывается в порядке слева направо. Из каждой точки начала пути могут быть многочисленные пути, некоторые из которых ведут к цели, а некоторые — тупиковые. Когда вы идете по пути и встречаете тупик, вам нужно вернуться назад по пути к более ранней точке, чтобы попробовать другой путь. Если вы достигли пункта назначения, вы сразу же останавливаетесь и забываете о попытке всех остальных путей. Вы упорны, и только если вы перепробовали все пути со всех точек начала пути и не достигли пункта назначения, вы объявляете об ошибке. Для наглядности, вот пошаговый анализ того, что делает Perl при попытке сопоставления регулярного выражения

"abcde" =~ /(abd|abc)(df|d|de)/;
0. Начинаем с первой буквы в строке 'a'.
1. Пробуем первый вариант в первой группе 'abd'.
2. Сопоставляем 'a' и последующую 'b'. Пока всё хорошо.
3. 'd' в регулярном выражении не соответствует 'c' в строке — тупик. Поэтому возвращаемся на две позиции назад и выбираем второй вариант в первой группе 'abc'.
4. Сопоставляем 'a', затем 'b' и затем 'c'. Мы на верном пути и удовлетворили первую группу. Задаём $1 значение 'abc'.
5. Переходим к второй группе и выбираем первый вариант 'df'.
6 Сопоставляем 'd'.
7. 'f' в регулярном выражении не соответствует 'e' в строке, так что тупик. Возвращаемся на одну позицию назад и выбираем второй вариант во второй группе 'd'.
8. 'd' соответствует. Вторая группа удовлетворена, поэтому задаём $2 значение 'd'.
9. Мы в конце регулярного выражения, значит, всё готово! Мы сопоставили 'abcd' из строки "abcde".

Несколько моментов следует учесть при анализе. Во-первых, третий вариант во второй группе 'de' также допускает соответствие, но мы остановились до него — в заданной позиции символа левее соответствует. Во-вторых, нам удалось получить соответствие в первой позиции символа строки 'a'. Если совпадений не было в первой позиции, Perl переходил ко второй позиции символа 'b' и снова пытался найти соответствие. Только когда все возможные пути во всех возможных позициях символов были исчерпаны, Perl отказывается и объявляет $string =~ /(abd|abc)(df|d|de)/; ложным.

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

Извлечение совпадений

Метасимволы группирования () также выполняют другую совершенно отличную функцию: они позволяют извлекать части строки, которые соответствуют. Это очень полезно, чтобы узнать, что совпало, и для обработки текста в целом. Для каждой группы часть, которая совпала внутри, помещается в специальные переменные $1, $2, и т.д. Их можно использовать как обычные переменные:

# extract hours, minutes, seconds
if ($time =~ /(\d\d):(\d\d):(\d\d)/) {    # match hh:mm:ss format
    $hours = $1;
    $minutes = $2;
    $seconds = $3;
}

Теперь мы знаем, что в скалярном контексте $time =~ /(\d\d):(\d\d):(\d\d)/ возвращает значение истинно или ложно. Однако в списочном контексте он возвращает список сопоставленных значений ($1,$2,$3). Таким образом, мы могли бы записать код более компактно, как

# extract hours, minutes, seconds
($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/);

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

/(ab(cd|ef)((gi)|j))/;
 1  2      34

Если это регулярное выражение совпадает, $1 содержит строку, начинающуюся с 'ab', $2 устанавливается либо на 'cd', либо на 'ef', $3 равно либо 'gi', либо 'j', а $4 устанавливается либо на 'gi', точно так же, как $3, или остается неопределённым.

Для удобства Perl устанавливает $+ в строку, хранящуюся в самой правой $1, $2,... которая была назначена (и, в некоторой степени связанная, $^N в значение $1, $2,... которое было назначено в последний раз; т.е. $1, $2,... связанное с правой закрывающей скобкой, используемой в соответствии).

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

Тесно связанные с переменными соответствия $1, $2, ... являются обратными ссылками \g1, \g2,... Обратные ссылки — это просто переменные соответствия, которые можно использовать внутри регулярного выражения. Это действительно хорошая функция; то, что соответствует позже в регулярном выражении, зависит от того, что соответствовало ранее в регулярном выражении. Предположим, мы хотим найти удвоенные слова в тексте, например, «the the». Следующее регулярное выражение находит все удвоенные слова из 3 букв со пробелом между ними:

/\b(\w\w\w)\s\g1\b/;

Группирование присваивает значение \g1, так что одна и та же последовательность из 3 букв используется для обеих частей.

Аналогичная задача заключается в поиске слов, состоящих из двух одинаковых частей:

% simple_grep '^(\w\w\w\w|\w\w\w|\w\w|\w)\g1$' /usr/dict/words
beriberi
booboo
coco
mama
murmur
papa

Регулярное выражение имеет одну группировку, которая рассматривает 4-буквенные сочетания, затем 3-буквенные сочетания, и т.д., и использует \g1 для поиска повторения. Хотя $1 и \g1 представляют одно и то же, следует проявлять осторожность, используя сопоставленные переменные $1, $2,... только вне регулярного выражения и обратные ссылки \g1, \g2,... только внутри регулярного выражения; если этого не делать, могут возникнуть неожиданные и неудовлетворительные результаты.

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

Подсчет открывающихся скобок для получения правильного номера обратной ссылки уязвим к ошибкам, как только количество захватывающих групп превышает одно. Более удобный метод стал доступен с Perl 5.10: относительные обратные ссылки. Для ссылки на непосредственно предшествующую захватывающую группу теперь можно написать \g{-1}, следующая, но предпоследняя, доступна через \g{-2}, и так далее.

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

$a99a = '([a-z])(\d)\g2\g1';   # matches a11a, g22g, x33x, etc.

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

$line = "code=e99e";
if ($line =~ /^(\w+)=$a99a$/){   # unexpected behavior!
    print "$1 is valid\n";
} else {
    print "bad line: '$line'\n";
}

Но это не совпадает, по крайней мере, не так, как можно ожидать. Только после вставки интерполированного $a99a и просмотра полученного полного текста регулярного выражения становится очевидно, что обратные ссылки не сработали. Подвыражение (\w+) отобрало номер 1 и понизило группы в $a99a на одну позицию. Этому можно избежать, используя относительные обратные ссылки:

$a99a = '([a-z])(\d)\g{-1}\g{-2}';  # safe for being interpolated

Именованные обратные ссылки

Perl 5.10 также представил именованные захватывающие группы и именованные обратные ссылки. Чтобы добавить имя к захватывающей группе, запишите либо (?<name>...) или (?'name'...). Обратную ссылку затем можно записать как \g{name}. Разрешается присваивать одно и то же имя более чем одной группе, но затем только самая левая из одноимённых групп может быть упомянута. Вне шаблона именованная захватывающая группа доступна через хеш %+.

Предположим, что нам нужно сопоставить даты календаря, которые могут быть заданы в одном из трёх форматов: yyyy-mm-dd, mm/dd/yyyy или dd.mm.yyyy, мы можем написать три подходящих шаблона, где 'd', 'm' и 'y' соответственно являются именами групп, которые захватывают соответствующие компоненты даты. Операция сопоставления объединяет три шаблона как альтернативы:

$fmt1 = '(?<y>\d\d\d\d)-(?<m>\d\d)-(?<d>\d\d)';
$fmt2 = '(?<m>\d\d)/(?<d>\d\d)/(?<y>\d\d\d\d)';
$fmt3 = '(?<d>\d\d)\.(?<m>\d\d)\.(?<y>\d\d\d\d)';
for my $d (qw(2006-10-21 15.01.2007 10/31/2005)) {
    if ( $d =~ m{$fmt1|$fmt2|$fmt3} ){
        print "day=$+{d} month=$+{m} year=$+{y}\n";
    }
}

Если любая из альтернатив совпадает, хеш %+ содержит три пары ключ-значение.

Альтернативное нумерация захватывающих групп

Еще один метод нумерации захватывающих групп (также с Perl 5.10) решает проблему ссылки на группы внутри набора альтернатив. Рассмотрим шаблон для сопоставления времени суток, в гражданском или военном стиле:

if ( $time =~ /(\d\d|\d):(\d\d)|(\d\d)(\d\d)/ ){
    # process hour and minute
}

Обработка результатов требует дополнительного оператора if для определения, содержат ли $1 и $2 или $3 и $4 нужные значения. Было бы проще, если бы мы могли использовать номера групп 1 и 2 во второй альтернативе тоже, и это именно то, что родительская скобка (?|...), установленная вокруг альтернативы, достигает. Вот расширенная версия предыдущего шаблона:

if($time =~ /(?|(\d\d|\d):(\d\d)|(\d\d)(\d\d))\s+([A-Z][A-Z][A-Z])/){
    print "hour=$1 minute=$2 zone=$3\n";
}

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

Информация о позиции

В дополнение к тому, что было сопоставлено, Perl также предоставляет позиции того, что было сопоставлено, как содержимое массивов @- и @+. $-[0] — позиция начала всего сопоставления, а $+[0] — позиция конца. Аналогично, $-[n] — позиция начала сопоставления $n и $+[n] — позиция конца. Если $n не определено, то и $-[n] и $+[n] тоже. Тогда этот код

$x = "Mmm...donut, thought Homer";
$x =~ /^(Mmm|Yech)\.\.\.(donut|peas)/; # matches
foreach $exp (1..$#-) {
    print "Match $exp: '${$exp}' at position ($-[$exp],$+[$exp])\n";
}

выводит

Match 1: 'Mmm' at position (0,3)
Match 2: 'donut' at position (6,11)

Даже если в регулярном выражении нет группировок, всё ещё возможно узнать, что именно совпало в строке. Если вы используете их, Perl установит $` в часть строки перед соответствием, установит $& в часть строки, которая соответствовала, и установит '$' в часть строки после соответствия. Пример:

$x = "the cat caught the mouse";
$x =~ /cat/;  # $` = 'the ', $& = 'cat', $' = ' caught the mouse'
$x =~ /the/;  # $` = '', $& = 'the', $' = ' cat caught the mouse'

Во втором соответствии $` равно '', потому что регулярное выражение соответствовало первой позиции символа в строке и остановилось; оно никогда не видело второе «the».

Если ваш код должен работать на версиях Perl, предшествующих 5.20, стоит отметить, что использование $` и '$' значительно замедляет сопоставление по регулярным выражениям, в то время как $& замедляет его в меньшей степени, потому что если они используются в одном регулярном выражении в программе, они генерируются для всех регулярных выражений в программе. Поэтому, если исходная производительность является целью вашего приложения, их следует избегать. Если вам нужно извлечь соответствующие подстроки, используйте @- и @+ вместо этого:

$` is the same as substr( $x, 0, $-[0] )
$& is the same as substr( $x, $-[0], $+[0]-$-[0] )
$' is the same as substr( $x, $+[0] )

Начиная с Perl 5.10, переменные ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH} могут быть использованы. Они устанавливаются только если присутствует модификатор /p. Следовательно, они не наказывают остальную часть программы. В Perl 5.20 ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH} доступны, независимо от того, использовался ли /p (модификатор игнорируется), а $`, '$' и $& не вызывают никакой разницы в скорости.

Незахватные группирования

Группа, которая необходима для объединения набора альтернатив, может быть или не быть полезна как захватывающая группа. Если она не полезна, она просто создает излишнее добавление к набору доступных значений захватывающих групп, как внутри, так и вне регулярного выражения. Незахватные группирования, обозначаемые (?:regexp), по-прежнему позволяют рассматривать регулярное выражение как единый блок, но не создают захватывающую группу в то же время. Как захватывающие, так и незахватывающие группирования могут сосуществовать в одном регулярном выражении. Поскольку извлечения нет, незахватывающие группирования быстрее, чем захватывающие. Незахватные группирования также удобны для выбора именно тех частей регулярного выражения, которые должны быть извлечены в переменные соответствия:

# match a number, $1-$4 are set, but we only want $1
/([+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)/;

# match a number faster , only $1 is set
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;

# match a number, get $1 = whole number, $2 = exponent
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]([+-]?\d+))?)/;

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

$x = '12aba34ba5';
@num = split /(a|b)+/, $x;    # @num = ('12','a','34','a','5')
@num = split /(?:a|b)+/, $x;  # @num = ('12','34','5')

В Perl 5.22 и более поздних версиях все группы в регулярном выражении могут быть установлены как незахватывающие, используя новый флаг /n.

"hello" =~ /(hi|hello)/n; # $1 is not set!

См. "n" в perlre для получения дополнительной информации.

Сопоставление повторений

Примеры в предыдущем разделе демонстрируют неприятный недостаток. Мы сопоставляли только слова из 3 букв или фрагменты слов из 4 букв или меньше. Нам хотелось бы иметь возможность сопоставлять слова или, в более общем случае, строки любой длины, не записывая утомительные альтернативы, такие как \w\w\w\w|\w\w\w|\w\w|\w.

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

  • a? означает: сопоставить 'a' 1 или 0 раз

  • a* означает: сопоставить 'a' 0 или более раз, т.е., любое количество раз

  • a+ означает: сопоставить 'a' 1 или более раз, т.е., по крайней мере один раз

  • a{n,m} означает: сопоставить по крайней мере n раз, но не более m раз.

  • a{n,} означает: сопоставить по крайней мере n или более раз

  • a{n} означает: сопоставить ровно n раз

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

/[a-z]+\s+\d*/;  # match a lowercase word, at least one space, and
                 # any number of digits
/(\w+)\s+\g1/;    # match doubled words of arbitrary length
/y(es)?/i;       # matches 'y', 'Y', or a case-insensitive 'yes'
$year =~ /^\d{2,4}$/;  # make sure year is at least 2 but not more
                       # than 4 digits
$year =~ /^\d{4}$|^\d{2}$/; # better match; throw out 3-digit dates
$year =~ /^\d{2}(\d{2})?$/; # same thing written differently.
                            # However, this captures the last two
                            # digits in $1 and the other does not.

% simple_grep '^(\w+)\g1$' /usr/dict/words   # isn't this easier?
beriberi
booboo
coco
mama
murmur
papa

Для всех этих квантификаторов Perl будет пытаться сопоставить как можно больше строки, всё ещё позволяя регулярному выражению успешно завершиться. Таким образом, с /a?.../, Perl сначала попытается сопоставить регулярное выражение с 'a' присутствующим; если это не удастся, Perl попытается сопоставить регулярное выражение без 'a' присутствующего. Для квантификатора '*', мы получим следующее:

$x = "the cat in the hat";
$x =~ /^(.*)(cat)(.*)$/; # matches,
                         # $1 = 'the '
                         # $2 = 'cat'
                         # $3 = ' in the hat'

Что, можно ожидать, совпадение находит единственное cat в строке и фиксируется на нём. Однако рассмотрим это регулярное выражение:

$x =~ /^(.*)(at)(.*)$/; # matches,
                        # $1 = 'the cat in the h'
                        # $2 = 'at'
                        # $3 = ''   (0 characters match)

Можно было бы предположить, что Perl найдёт at в cat и остановится на этом, но это не даст самой длинной возможной строки первому квантификатору .*. Вместо этого первый квантификатор .* захватывает как можно больше строки, всё ещё позволяя регулярному выражению совпасть. В этом примере это означает наличие последовательности at с конечным at в строке. Другим важным принципом, проиллюстрированным здесь, является то, что, когда в регулярном выражении есть два или более элементов, самый левый квантификатор, если он есть, захватывает как можно больше строки, оставляя остаток регулярного выражения бороться за обрезки. Таким образом, в нашем примере, первый квантификатор .* захватывает большую часть строки, а второй квантификатор .* получает пустую строку. Квантификаторы, которые захватывают как можно больше строки, называются максимальными совпадениями или жадными квантификаторами.

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

  • Принцип 0: В целом любое регулярное выражение будет сопоставлено в самом раннем возможном положении в строке.

  • Принцип 1: В альтернации a|b|c..., используемая альтернатива слева, которая позволяет совпадение для всего регулярного выражения.

  • Принцип 2: Максимальные квантификаторы совпадений '?', '*', '+' и {n,m} обычно будут сопоставлять как можно больше строки, всё ещё позволяя всему регулярному выражению совпасть.

  • Принцип 3: Если в регулярном выражении есть два или более элементов, самый левый жадный квантификатор, если есть, будет сопоставлять как можно больше строки, всё ещё позволяя всему регулярному выражению совпасть. Следующий самый левый жадный квантификатор, если есть, будет пытаться сопоставить как можно больше оставшейся доступной для него строки, всё ещё позволяя всему регулярному выражению совпасть. И так далее, пока не будут удовлетворены все элементы регулярного выражения.

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

Вот пример этих принципов в действии:

$x = "The programming republic of Perl";
$x =~ /^(.+)(e|r)(.*)$/;  # matches,
                          # $1 = 'The programming republic of Pe'
                          # $2 = 'r'
                          # $3 = 'l'

Это регулярное выражение сопоставляется в самой ранней позиции строки, 'T'. Можно подумать, что 'e', будучи самым левым в альтернации, будет сопоставлено, но 'r' производит самую длинную строку в первом квантификаторе.

$x =~ /(m{1,2})(.*)$/;  # matches,
                        # $1 = 'mm'
                        # $2 = 'ing republic of Perl'

Здесь, самое раннее возможное соответствие находится в первом 'm' в programming. m{1,2} — это первый квантификатор, поэтому он получает право сопоставить максимальное mm.

$x =~ /.*(m{1,2})(.*)$/;  # matches,
                          # $1 = 'm'
                          # $2 = 'ing republic of Perl'

Здесь регулярное выражение сопоставляется в начале строки. Первый квантификатор .* захватывает как можно больше, оставляя только один 'm' для второго квантификатора m{1,2}.

$x =~ /(.?)(m{1,2})(.*)$/;  # matches,
                            # $1 = 'a'
                            # $2 = 'mm'
                            # $3 = 'ing republic of Perl'

Здесь .? поедает свое максимальное односимвольное соответствие в самой ранней позиции в строке, 'a' в programming, предоставляя m{1,2} возможность сопоставить оба 'm''а. Наконец,

"aXXXb" =~ /(X*)/; # matches with $1 = ''

потому что он может сопоставить нулевые копии 'X' в начале строки. Если вы определенно хотите сопоставить по крайней мере один 'X', используйте X+, а не X*.

Иногда жадность — это не хорошо. Иногда мы хотим, чтобы квантификаторы сопоставляли минимальную часть строки, а не максимальную. Для этой цели Ларри Уолл создал минимальные совпадения или нежадные квантификаторы ??, *?, +?, и {}?. Это обычные квантификаторы с добавленным '?'. Они имеют следующие значения:

  • a?? означает: сопоставить 'a' 0 или 1 раз. Сначала попробуйте 0, затем 1.

  • a*? означает: сопоставить 'a' 0 или более раз, т.е., любое количество раз, но как можно меньше раз

  • a+? означает: сопоставить 'a' 1 или более раз, т.е., по крайней мере один раз, но как можно меньше раз

  • a{n,m}? означает: сопоставить по крайней мере n раз, не более m раз, как можно меньше раз

  • a{n,}? означает: сопоставить по крайней мере n раз, но как можно меньше раз

  • a{n}? означает: сопоставить ровно n раз. Поскольку мы сопоставляем ровно n раз, a{n}? эквивалентно a{n} и просто существует для единообразия обозначений.

Давайте посмотрим на пример выше, но с минимальными квантификаторами:

$x = "The programming republic of Perl";
$x =~ /^(.+?)(e|r)(.*)$/; # matches,
                          # $1 = 'Th'
                          # $2 = 'e'
                          # $3 = ' programming republic of Perl'

Минимальная строка, которая позволит как началу строки '^', так и совпадению с альтернацией, равна Th, при этом альтернация e|r сопоставляет 'e' . Второй квантификатор .* свободен поглотить остаток строки.

$x =~ /(m{1,2}?)(.*?)$/;  # matches,
                          # $1 = 'm'
                          # $2 = 'ming republic of Perl'

Первая позиция строки, которую это регулярное выражение может сопоставить, находится в первом 'm' в programming. В этой позиции минимальный m{1,2}? сопоставляет только один 'm' . Хотя второй квантификатор .*? предпочел бы не сопоставлять никаких символов, он ограничен якорем конца строки '$' для сопоставления остальной части строки.

$x =~ /(.*?)(m{1,2}?)(.*)$/;  # matches,
                              # $1 = 'The progra'
                              # $2 = 'm'
                              # $3 = 'ming republic of Perl'

В этом регулярном выражении можно было бы ожидать, что первый минимальный квантификатор .*? сопоставит пустую строку, так как он не ограничен якорем '^' начала слова. Принцип 0 здесь применяется. Так как существует возможность, что все регулярное выражение может сопоставиться в начале строки, оно будет сопоставляться в начале строки. Следовательно, первый квантификатор должен сопоставить всё до первого 'm'. Второй минимальный квантификатор сопоставляет только один 'm', а третий квантификатор сопоставляет остаток строки.

$x =~ /(.??)(m{1,2})(.*)$/;  # matches,
                             # $1 = 'a'
                             # $2 = 'mm'
                             # $3 = 'ing republic of Perl'

Точно так же, как и в предыдущем регулярном выражении, первый квантификатор .?? может сопоставиться раньше всего в позиции 'a', поэтому он и делает это. Второй квантификатор жадный, поэтому он сопоставляет mm, а третий сопоставляет остаток строки.

Мы можем изменить принцип 3 выше, чтобы учесть нежадные квантификаторы:

  • Принцип 3: Если в регулярном выражении есть два или более элементов, самый левый жадный (нежадный) квантификатор, если есть, будет сопоставлять как можно больше (меньше) строки, всё ещё позволяя всему регулярному выражению совпасть. Следующий самый левый жадный (нежадный) квантификатор, если есть, будет пытаться сопоставить как можно больше (меньше) оставшейся доступной для него строки, всё ещё позволяя всему регулярному выражению совпасть. И так далее, пока не будут удовлетворены все элементы регулярного выражения.

Точно так же, как и альтернация, квантификаторы также подвержены возврату. Вот пошаговый анализ примера

$x = "the cat in the hat";
$x =~ /^(.*)(at)(.*)$/; # matches,
                        # $1 = 'the cat in the h'
                        # $2 = 'at'
                        # $3 = ''   (0 matches)
0. Начнём с первой буквы в строке 't'.
1. Первый квантификатор '.*' начинает с сопоставления всей строки "the cat in the hat".
2. 'a' в элементе регулярного выражения 'at' не сопоставляется с концом строки. Вернуться на один символ назад.
3. 'a' в элементе регулярного выражения 'at' всё ещё не сопоставляется с последней буквой строки 't', поэтому вернуться на один символ назад.
4. Теперь мы можем сопоставить 'a' и 't'.
5. Переходим к третьему элементу '.*'. Поскольку мы находимся в конце строки и '.*' может сопоставиться 0 раз, назначьте ему пустую строку.
6. Закончено!

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

/(a|b+)*/;

Проблема заключается в вложенных неопределенных квантификаторах. Существует множество способов разбить строку длиной n между '+' и '*': одно повторение с b+ длиной n, два повторения с первым b+ длиной k и вторым длиной n-k, m повторений, биты которых суммируются в длину n, и т.д.. Фактически, существует экспоненциальное количество способов разбить строку в зависимости от ее длины. Регулярное выражение может «повезти» и совпасть на ранней стадии процесса, но если совпадения нет, Perl попробует каждый вариант, прежде чем отказаться. Поэтому будьте осторожны с вложенными '*', {n,m} и '+'. В книге «Мастерство регулярных выражений» Джеффри Фридла дается замечательное обсуждение этой и других проблем эффективности.

Позитивные квантификаторы

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

/^\w+\s+\w+$/; # a word, spaces, a word

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

С введением позитивных квантификаторов в Perl 5.10 у нас есть способ указать движку регулярных выражений не возвращаться назад, используя обычные квантификаторы с добавленным к ним '+'. Это делает их жадными и одновременно скупыми; однажды добившись успеха, они ничего не отдадут, чтобы позволить другое решение. Они имеют следующие значения:

  • a{n,m}+ означает: соответствовать как минимум n раз, не более m раз, как можно больше раз, и не отдавать ничего. a?+ — это сокращение от a{0,1}+

  • a{n,}+ означает: соответствовать как минимум n раз, но как можно больше раз, и не отдавать ничего. a*+ — это сокращение от a{0,}+, а a++ — от a{1,}+.

  • a{n}+ означает: соответствовать ровно n раз. Это необходимо только для согласованности обозначений.

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

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

/"(?:[^"\\]++|\\.)*+"/;

Построение регулярного выражения

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

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

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

Простейший случай — целые числа. Они состоят из последовательности цифр с необязательным знаком перед ними. Цифры можно представить с помощью \d+, а знак можно сопоставить с помощью [+-] . Таким образом, регулярное выражение для целого числа имеет вид

/[+-]?\d+/;  # matches integers

Вещественное число потенциально имеет знак, целую часть, десятичную точку, дробную часть и показатель степени. Одна или несколько из этих частей являются необязательными, поэтому нам нужно проверить различные возможности. Вещественные числа в правильном формате включают 123., 0.345, .34, -1e6 и 25.4E-72. Как и в случае с целыми числами, знак впереди полностью необязателен и может быть сопоставлен с помощью [+-]? . Мы видим, что если нет показателя степени, вещественные числа должны иметь десятичную точку, в противном случае они являются целыми числами. Мы могли бы попытаться смоделировать это с помощью \d*\.\d*, но это также будет соответствовать только одной десятичной точке, что не является числом. Итак, три случая вещественного числа без показателя степени:

/[+-]?\d+\./;  # 1., 321., etc.
/[+-]?\.\d+/;  # .1, .234, etc.
/[+-]?\d+\.\d+/;  # 1.0, 30.56, etc.

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

/[+-]?(\d+\.\d+|\d+\.|\.\d+)/;  # floating point, no exponent

В этом выборе важно поместить '\d+\.\d+' перед '\d+\.'. Если бы '\d+\.' стояло первым, регулярное выражение счастливо сопоставило бы это и проигнорировало бы дробную часть числа.

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

/^(optional sign)(integer | f.p. mantissa)(optional exponent)$/;

Показатель степени — это 'e' или 'E', за которым следует целое число. Поэтому регулярное выражение для показателя степени имеет вид

/[eE][+-]?\d+/;  # exponent

Объединив все части, получаем регулярное выражение, которое соответствует числам:

/^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/;  # Ta da!

Длинные регулярные выражения, подобные этому, могут впечатлить ваших друзей, но их трудно расшифровать. В сложных ситуациях, подобных этому, модификатор /x для соответствия имеет неоценимую ценность. Он позволяет помещать почти любые пробелы и комментарии в регулярное выражение без изменения их смысла. Используя его, мы можем переписать наше «расширенное» регулярное выражение в более удобочитаемой форме

/^
   [+-]?         # first, match an optional sign
   (             # then match integers or f.p. mantissas:
       \d+\.\d+  # mantissa of the form a.b
      |\d+\.     # mantissa of the form a.
      |\.\d+     # mantissa of the form .b
      |\d+       # integer of the form a
   )
   ( [eE] [+-]? \d+ )?  # finally, optionally match an exponent
$/x;

Если пробелы в основном неважны, как включить пробелы в расширенном регулярном выражении? Ответ — экранировать их '\ ' или поместить их в класс символов [ ]. То же самое относится и к знакам фунта: используйте \# или [#] . Например, Perl допускает пробел между знаком и мантиссой или целым числом, и мы могли бы добавить это в наше регулярное выражение следующим образом:

/^
   [+-]?\ *      # first, match an optional sign *and space*
   (             # then match integers or f.p. mantissas:
       \d+\.\d+  # mantissa of the form a.b
      |\d+\.     # mantissa of the form a.
      |\.\d+     # mantissa of the form .b
      |\d+       # integer of the form a
   )
   ( [eE] [+-]? \d+ )?  # finally, optionally match an exponent
$/x;

В этой форме легче увидеть способ упрощения выбора. Альтернативы 1, 2 и 4 все начинаются с \d+, поэтому его можно вынести за скобки:

/^
   [+-]?\ *      # first, match an optional sign
   (             # then match integers or f.p. mantissas:
       \d+       # start out with a ...
       (
           \.\d* # mantissa of the form a.b or a.
       )?        # ? takes care of integers of the form a
      |\.\d+     # mantissa of the form .b
   )
   ( [eE] [+-]? \d+ )?  # finally, optionally match an exponent
$/x;

Начиная с Perl v5.26, указание /xx изменяет квадратные скобки части шаблона, чтобы игнорировать табуляции и пробелы, если они не экранированы предшествующим обратным слэшем. Таким образом, мы могли бы написать

/^
   [ + - ]?\ *   # first, match an optional sign
   (             # then match integers or f.p. mantissas:
       \d+       # start out with a ...
       (
           \.\d* # mantissa of the form a.b or a.
       )?        # ? takes care of integers of the form a
      |\.\d+     # mantissa of the form .b
   )
   ( [ e E ] [ + - ]? \d+ )?  # finally, optionally match an exponent
$/xx;

Это не улучшает удобочитаемость этого примера, но оно доступно, если вам это нужно. Сжав шаблон до компактной формы, мы имеем

/^[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;

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

  • подробно определив задачу,

  • разбив задачу на более мелкие части,

  • переведя мелкие части в регулярные выражения,

  • объединив регулярные выражения,

  • и оптимизировав полученное комбинированное регулярное выражение.

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

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

Последняя тема части 1 кратко описывает, как регулярные выражения используются в программах Perl. Где они помещаются в синтаксис Perl?

Мы уже представили оператор сопоставления в его стандартном /regexp/ и произвольном разделительном m!regexp! форматах. Мы использовали оператор привязки =~ и его отрицание !~ для проверки соответствия строк. В связи с оператором сопоставления мы обсудили модификаторы одиночной строки /s, многострочного /m, регистронезависимого /i и расширенного /x.

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

Если вы измените $pattern после первой подстановки, Perl проигнорирует её. Если вы вообще не хотите никаких подстановок, используйте специальный разделитель m'':

@pattern = ('Seuss');
while (<>) {
    print if m'@pattern';  # matches literal '@pattern', not 'Seuss'
}

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

"dog" =~ /d/;  # 'd' matches
"dogbert =~ //;  # this matches the 'd' regexp used before

Глобальное сопоставление

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

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

$x = "cat dog house"; # 3 words
$x =~ /^\s*(\w+)\s+(\w+)\s+(\w+)\s*$/; # matches,
                                       # $1 = 'cat'
                                       # $2 = 'dog'
                                       # $3 = 'house'

Но что если у нас неопределенное количество слов? Это тот случай, для которого /g предназначен. Для извлечения всех слов составьте простое регулярное выражение (\w+) и перебирайте все совпадения с помощью /(\w+)/g:

while ($x =~ /(\w+)/g) {
    print "Word is $1, ends at position ", pos $x, "\n";
}

выводит

Word is cat, ends at position 3
Word is dog, ends at position 7
Word is house, ends at position 13

Неудачное совпадение или изменение целевой строки сбрасывает позицию. Если вы не хотите, чтобы позиция сбрасывалась после неудачи, добавьте /c, как в /regexp/gc. Текущая позиция в строке связана со строкой, а не с регулярным выражением. Это означает, что различные строки имеют различные позиции, и их соответствующие позиции могут устанавливаться или считываться независимо.

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

@words = ($x =~ /(\w+)/g);  # matches,
                            # $words[0] = 'cat'
                            # $words[1] = 'dog'
                            # $words[2] = 'house'

Тесно связанный с модификатором /g якорь \G. Якорь \G соответствует той точке, где предыдущее совпадение /g завершилось. \G позволяет нам легко выполнять контекстно-зависимое сопоставление:

$metric = 1;  # use metric units
...
$x = <FILE>;  # read in measurement
$x =~ /^([+-]?\d+)\s*/g;  # get magnitude
$weight = $1;
if ($metric) { # error checking
    print "Units error!" unless $x =~ /\Gkg\./g;
}
else {
    print "Units error!" unless $x =~ /\Glbs\./g;
}
$x =~ /\G\s+(widget|sprocket)/g;  # continue processing

Сочетание /g и \G позволяет нам обрабатывать строку по частям и использовать произвольную логику Perl, чтобы определить, что делать дальше. В настоящее время якорь \G полностью поддерживается только при использовании для привязки к началу шаблона.

\G также незаменимо при обработке записей фиксированной длины с регулярными выражениями. Предположим, у нас есть фрагмент кодирующей области ДНК, закодированный буквами пар оснований ATCGTTGAAT..., и мы хотим найти все стоп-кодоны TGA. В кодирующей области кодоны представляют собой последовательности из 3 букв, поэтому мы можем рассматривать фрагмент ДНК как последовательность записей из 3 букв. Примитивное регулярное выражение

# expanded, this is "ATC GTT GAA TGC AAA TGA CAT GAC"
$dna = "ATCGTTGAATGCAAATGACATGAC";
$dna =~ /TGA/;

не работает; оно может соответствовать TGA, но нет гарантии, что соответствие выровнено с границами кодонов, например, подстрока GTT GAA дает соответствие. Лучшее решение —

while ($dna =~ /(\w\w\w)*?TGA/g) {  # note the minimal *?
    print "Got a TGA stop codon at position ", pos $dna, "\n";
}

что выводит

Got a TGA stop codon at position 18
Got a TGA stop codon at position 23

Позиция 18 — хорошая, а позиция 23 — ложная. Что случилось?

Ответ в том, что наше регулярное выражение работает хорошо, пока мы не пройдем за последним реальным соответствием. Затем регулярное выражение не сможет найти синхронизированное TGA и начнет сдвигать позицию на один символ за раз, что нам не нужно. Решение — использовать \G для привязки соответствия к выравниванию кодонов:

while ($dna =~ /\G(\w\w\w)*?TGA/g) {
    print "Got a TGA stop codon at position ", pos $dna, "\n";
}

Это выводит

Got a TGA stop codon at position 18

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

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

Поиск и замена

Регулярные выражения также играют большую роль в операциях поиска и замены в Perl. Поиск и замена выполняются с помощью оператора s///. Общий вид — s/regexp/replacement/modifiers, при этом все, что мы знаем о регулярных выражениях и модификаторах, применяется и в этом случае. Замена — это строка Perl в двойных кавычках, которая заменяет в строке все, что совпадает с regexp. Оператор =~ также используется здесь для связывания строки с s///. Если соответствие производится с $_, то $_ =~ можно опустить. Если есть соответствие, s/// возвращает количество выполненных замен; в противном случае возвращает false. Вот несколько примеров:

$x = "Time to feed the cat!";
$x =~ s/cat/hacker/;   # $x contains "Time to feed the hacker!"
if ($x =~ s/^(Time.*hacker)!$/$1 now!/) {
    $more_insistent = 1;
}
$y = "'quoted words'";
$y =~ s/^'(.*)'$/$1/;  # strip single quotes,
                       # $y contains "quoted words"

В последнем примере вся строка совпадала, но только часть внутри одинарных кавычек была сгруппирована. С оператором s///, сопоставленные переменные $1, $2, и т.д., немедленно становятся доступными для использования в выражении замены, поэтому мы используем $1 для замены строки в кавычках только на то, что было в кавычках. С глобальным модификатором s///g будет выполняться поиск и замена всех вхождений регулярного выражения в строке:

$x = "I batted 4 for 4";
$x =~ s/4/four/;   # doesn't do it all:
                   # $x contains "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g;  # does it all:
                   # $x contains "I batted four for four"

Если в этом учебнике вы предпочитаете «regex» вместо «regexp», вы можете использовать следующую программу для замены:

% cat > simple_replace
#!/usr/bin/perl
$regexp = shift;
$replacement = shift;
while (<>) {
    s/$regexp/$replacement/g;
    print;
}
^D

% simple_replace regexp regex perlretut.pod

В simple_replace мы использовали модификатор s///g для замены всех вхождений регулярного выражения в каждой строке. (Даже если регулярное выражение появляется в цикле, Perl достаточно умён, чтобы скомпилировать его только один раз.) Как и с simple_grep, как print, так и s/$regexp/$replacement/g неявно используют $_.

Если вы не хотите, чтобы s/// изменял вашу исходную переменную, вы можете использовать модификатор неразрушающей замены s///r. Это изменяет поведение таким образом, что s///r возвращает окончательно заменённую строку (а не количество замен):

$x = "I like dogs.";
$y = $x =~ s/dogs/cats/r;
print "$x $y\n";

Этот пример выведет "I like dogs. I like cats". Обратите внимание, что исходная $x переменная не была затронута. Вместо этого общий результат замены сохраняется в $y. Если замена ничего не меняет, возвращается исходная строка:

$x = "I like dogs.";
$y = $x =~ s/elephants/cougars/r;
print "$x $y\n"; # prints "I like dogs. I like dogs."

Ещё одна интересная возможность флага s///r — цепные замены:

$x = "Cats are great.";
print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~
    s/Frogs/Hedgehogs/r, "\n";
# prints "Hedgehogs are great."

Модификатор, доступный специально для поиска и замены, — это модификатор вычисления s///e. s///e обрабатывает текст замены как код Perl, а не строку в двойных кавычках. Значение, возвращаемое кодом, подставляется вместо сопоставленной подстроки. s///e полезна, если вам нужно выполнить немного вычислений в процессе замены текста. В этом примере подсчитываются частоты символов в строке:

$x = "Bill the cat";
$x =~ s/(.)/$chars{$1}++;$1/eg; # final $1 replaces char with itself
print "frequency of '$_' is $chars{$_}\n"
    foreach (sort {$chars{$b} <=> $chars{$a}} keys %chars);

Это выводит

frequency of ' ' is 2
frequency of 't' is 2
frequency of 'l' is 2
frequency of 'B' is 1
frequency of 'c' is 1
frequency of 'e' is 1
frequency of 'h' is 1
frequency of 'i' is 1
frequency of 'a' is 1

Как и оператор совпадения m//, s/// может использовать другие разделители, такие как s!!! и s{}{}, а также s{}//. Если используются одинарные кавычки s''', то регулярное выражение и замена рассматриваются как строки в одинарных кавычках, и подстановок переменных не происходит. s/// в контексте списка возвращает то же, что и в скалярном контексте, то есть количество совпадений.

Функция split

Функция split() — ещё одно место, где используется регулярное выражение. split /regexp/, string, limit разделяет операнд string на список подстрок и возвращает этот список. Регулярное выражение должно быть сконструировано таким образом, чтобы соответствовать тому, что представляет собой разделитель для требуемых подстрок. limit, если он присутствует, ограничивает разделение не более чем limit числом строк. Например, для разделения строки на слова используйте

$x = "Calvin and Hobbes";
@words = split /\s+/, $x;  # $word[0] = 'Calvin'
                           # $word[1] = 'and'
                           # $word[2] = 'Hobbes'

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

$x = "/usr/bin/perl";
@dirs = split m!/!, $x;  # $dirs[0] = ''
                         # $dirs[1] = 'usr'
                         # $dirs[2] = 'bin'
                         # $dirs[3] = 'perl'
@parts = split m!(/)!, $x;  # $parts[0] = ''
                            # $parts[1] = '/'
                            # $parts[2] = 'usr'
                            # $parts[3] = '/'
                            # $parts[4] = 'bin'
                            # $parts[5] = '/'
                            # $parts[6] = 'perl'

Поскольку первый символ $x соответствовал регулярному выражению, split добавило пустой начальный элемент в список.

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

Часть 2: Расширенные инструменты

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

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

Больше о символах, строках и классах символов

Есть ряд последовательностей с escape-символами и классов символов, которые мы ещё не рассмотрели.

Есть несколько последовательностей с escape-символами, которые преобразуют символы или строки между верхним и нижним регистром, и они также доступны внутри шаблонов. \l и \u соответственно преобразуют следующий символ в нижний или верхний регистр:

$x = "perl";
$string =~ /\u$x/;  # matches 'Perl' in $string
$x = "M(rs?|s)\\."; # note the double backslash
$string =~ /\l$x/;  # matches 'mr.', 'mrs.', and 'ms.',

\L или \U указывают на длительную конвертацию регистра до тех пор, пока она не будет завершена \E или не будет отменена другим \U или \L.

$x = "This word is in lower case:\L SHOUT\E";
$x =~ /shout/;       # matches
$x = "I STILL KEYPUNCH CARDS FOR MY 360"
$x =~ /\Ukeypunch/;  # matches punch card string

Если \E отсутствует, регистр преобразуется до конца строки. Регулярные выражения \L\u$word или \u\L$word преобразуют первый символ $word в верхний регистр, а остальные — в нижний.

Управляющие символы можно экранировать с помощью \c, так что символ управления Z будет сопоставляться с \cZ. Последовательность escape-символов \Q...\E заключает в кавычки или защищает большинство неалфавитных символов. Например,

$x = "\QThat !^*&%~& cat!";
$x =~ /\Q!^*&%~&\E/;  # check for rough language

Он не защищает '$' или '@', так что переменные все ещё могут быть заменены.

\Q, \L, \l, \U, \u и \E фактически являются частью синтаксиса двойных кавычек, а не частью синтаксиса регулярных выражений. Они будут работать, если они появятся в регулярном выражении, встроенном непосредственно в программу, но не когда они содержатся в строке, которая интерполируется в шаблон.

Регулярные выражения Perl могут обрабатывать больше, чем стандартный набор символов ASCII. Perl поддерживает Unicode, стандарт для представления алфавитов практически всех письменных языков мира, и множество символов. Строки Perl — это строки Unicode, поэтому они могут содержать символы со значением (кодовым точкой или номером символа) больше 255.

Что это значит для регулярных выражений? Ну, пользователям регулярных выражений не нужно знать много об внутренней реализации строк в Perl. Но им нужно знать 1) как представлять символы Unicode в регулярном выражении и 2) что операция сопоставления будет обрабатывать строку для поиска как последовательность символов, а не байтов. Ответ на 1) заключается в том, что символы Unicode, большие чем chr(255), представляются с помощью обозначения \x{hex}, потому что \xXY (без фигурных скобок, а XY — две шестнадцатеричные цифры) не идёт дальше 255. (Начиная с Perl 5.14, если вы любитель восьмеричной системы счисления, вы также можете использовать \o{oct}.)

/\x{263a}/;  # match a Unicode smiley face :)

ПРИМЕЧАНИЕ: В Perl 5.6.0 требовалось использовать use utf8 для использования любых функций Unicode. Это больше не так: для почти всех операций с Unicode явное использование директивы utf8 не требуется. (Единственный случай, когда это имеет значение, если ваш скрипт Perl находится в Unicode и закодирован в UTF-8, тогда необходимо явное использование директивы use utf8.)

Определение шестнадцатеричной последовательности символа Unicode, который вам нужен, или расшифровка шестнадцатеричного Unicode-регулярного выражения чужого авторства — занятие примерно такое же интересное, как программирование на машинном коде. Поэтому другой способ указания символов Unicode — использование последовательности escape-символов с именем символа \N{name}. Имя — это имя символа Unicode, как указано в стандарте Unicode. Например, если мы хотели бы представить или сопоставить астрологический знак для планеты Меркурий, мы могли бы использовать

$x = "abc\N{MERCURY}def";
$x =~ /\N{MERCURY}/;   # matches

Можно использовать и "короткое" имя:

print "\N{GREEK SMALL LETTER SIGMA} is called sigma.\n";
print "\N{greek:Sigma} is an upper-case sigma.\n";

Вы также можете ограничить имена определённым алфавитом, указав директиву charnames:

use charnames qw(greek);
print "\N{sigma} is Greek sigma\n";

Индекс имен символов доступен онлайн в консорциуме Unicode, http://www.unicode.org/charts/charindex.html; справочный материал со ссылками на другие ресурсы по адресу http://www.unicode.org/standard/where.

Ответ на требование 2) заключается в том, что regexp (в основном) использует символы Unicode. «В основном» — из-за проблем обратной совместимости, но начиная с Perl 5.14, любой regexp, скомпилированный в области действия use feature 'unicode_strings' (который автоматически включается в области действия use 5.012 или выше), превратит это «в основном» в «всегда». Если вы хотите правильно обрабатывать Unicode, вы должны убедиться, что 'unicode_strings' включен. Внутренне он кодируется в байты с использованием UTF-8 или родного 8-битного кодирования, в зависимости от истории строки, но концептуально это последовательность символов, а не байтов. См. perlunitut для получения руководства по этому вопросу.

Теперь давайте обсудим классы символов Unicode, обычно называемые «свойствами символов». Они представлены escape-последовательностью \p{name}. Отрицание этого — \P{name}. Например, чтобы сопоставить строчные и прописные символы,

$x = "BOB";
$x =~ /^\p{IsUpper}/;   # matches, uppercase char class
$x =~ /^\P{IsUpper}/;   # doesn't match, char class sans uppercase
$x =~ /^\p{IsLower}/;   # doesn't match, lowercase char class
$x =~ /^\P{IsLower}/;   # matches, char class sans lowercase

(«Is» необязательно.)

Существует множество свойств символов Unicode. Полный список см. в perluniprops. У большинства из них есть синонимы с более короткими именами, также перечисленными там. Некоторые синонимы представляют собой один символ. В таких случаях вы можете опустить фигурные скобки. Например, \pM — это то же самое, что и \p{Mark}, что означает такие вещи, как диакритические знаки.

Свойства Unicode \p{Script} и \p{Script_Extensions} используются для категоризации каждого символа Unicode по письменности языка, на котором он написан. (Script_Extensions — улучшенная версия Script, которая сохраняется для обратной совместимости, поэтому в общем случае следует использовать Script_Extensions.) Например, английский, французский и множество других европейских языков написаны латинским шрифтом. Но также есть греческий шрифт, тайский шрифт, катакана, и т.д.. Вы можете проверить, принадлежит ли символ определённому шрифту (на основе Script_Extensions) с помощью, например \p{Latin}, \p{Greek}, или \p{Katakana}. Чтобы проверить, не относится ли он к балийскому письму, используйте \P{Balinese}.

Всё описанное до сих пор относится к одиночной форме классов символов \p{...}. Есть также составная форма, с которой вы можете столкнуться. Они выглядят как \p{name=value} или \p{name:value} (знаки равенства и двоеточия можно использовать взаимозаменяемо). Они более общие, чем одиночная форма, и фактически большинство одиночных форм просто определённые Perl-сокращения для общих составных форм. Например, примеры шрифтов в предыдущем абзаце можно написать эквивалентно как \p{Script_Extensions=Latin}, \p{Script_Extensions:Greek}, \p{script_extensions=katakana}, и \P{script_extensions=balinese} (регистр не имеет значения внутри {} скобок). Вам, возможно, никогда не придётся использовать составные формы, но иногда это необходимо, и их использование может сделать ваш код более понятным.

\X — сокращение для класса символов, который включает в себя расширенное кластер символов Unicode. Это представляет собой «логический символ»: то, что выглядит как один символ, но может быть представлено внутри более чем одним. В качестве примера, используя полные имена Unicode, например, "A + COMBINING RING" — это кластер символов с базовым символом «A» и комбинирующим символом "COMBINING RING, что переводится на датский как «A» с кружком сверху, как в слове Ångstrom.

Для получения полной и актуальной информации об Unicode см. последнюю версию стандарта Unicode или веб-сайт консорциума Unicode http://www.unicode.org

Как будто этих классов было недостаточно, Perl также определяет классы символов в стиле POSIX. Они имеют вид [:name:], где name — имя класса POSIX. Классы POSIX — это alpha, alnum, ascii, cntrl, digit, graph, lower, print, punct, space, upper, и xdigit, и два расширения, word (расширение Perl для соответствия \w), и blank (расширение GNU). Модификатор /a ограничивает их соответствием только в диапазоне ASCII; в противном случае они могут совпадать с соответствующими классами Unicode Perl: [:upper:] эквивалентно \p{IsUpper}, и т.д. (Существуют некоторые исключения и нюансы; см. perlrecharclass для подробного обсуждения.) [:digit:], [:word:], и [:space:] соответствуют знакомым классам символов \d, \w, и \s соответственно. Для отрицания класса POSIX поставьте '^' перед именем, так что, например, [:^digit:] соответствует \D и, в Unicode, \P{IsDigit}. Классы символов Unicode и POSIX могут использоваться так же, как \d, за исключением того, что классы символов POSIX могут использоваться только внутри класса символов:

/\s+[abc[:digit:]xyz]\s*/;  # match a,b,c,x,y,z, or a digit
/^=item\s[[:digit:]]/;      # match '=item',
                            # followed by a space and a digit
/\s+[abc\p{IsDigit}xyz]\s+/;  # match a,b,c,x,y,z, or a digit
/^=item\s\p{IsDigit}/;        # match '=item',
                              # followed by a space and a digit

Вот и все остальные символы и классы символов.

Компиляция и сохранение регулярных выражений

В первой части мы упомянули, что Perl компилирует regexp в компактную последовательность операторов. Таким образом, скомпилированное regexp — это структура данных, которая может храниться один раз и использоваться снова и снова. Квотирование регулярных выражений qr// делает именно это: qr/string/ компилирует string как regexp и преобразует результат в форму, которую можно присвоить переменной:

$reg = qr/foo+bar?/;  # reg contains a compiled regexp

Затем $reg можно использовать как regexp:

$x = "fooooba";
$x =~ $reg;     # matches, just like /foo+bar?/
$x =~ /$reg/;   # same thing, alternate form

$reg также можно интерполировать в более крупное regexp:

$x =~ /(abc)?$reg/;  # still matches

Как и с оператором соответствия, квотирование регулярного выражения может использовать разные разделители, например, qr!!, qr{} или qr~~. Апострофы в качестве разделителей (qr'') предотвращают любую интерполяцию.

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

% cat > grep_step
#!/usr/bin/perl
# grep_step - match <number> regexps, one after the other
# usage: multi_grep <number> regexp1 regexp2 ... file1 file2 ...

$number = shift;
$regexp[$_] = shift foreach (0..$number-1);
@compiled = map qr/$_/, @regexp;
while ($line = <>) {
    if ($line =~ /$compiled[0]/) {
        print $line;
        shift @compiled;
        last unless @compiled;
    }
}
^D

% grep_step 3 shift print last grep_step
$number = shift;
        print $line;
        last unless @compiled;

Хранение предварительно скомпилированных regexp в массиве @compiled позволяет нам просто перебирать regexp без перекомпиляции, тем самым обеспечивая гибкость без ущерба для скорости.

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

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

% cat > multi_grep
#!/usr/bin/perl
# multi_grep - match any of <number> regexps
# usage: multi_grep <number> regexp1 regexp2 ... file1 file2 ...

$number = shift;
$regexp[$_] = shift foreach (0..$number-1);
$pattern = join '|', @regexp;

while ($line = <>) {
    print $line if $line =~ /$pattern/;
}
^D

% multi_grep 2 shift for multi_grep
$number = shift;
$regexp[$_] = shift foreach (0..$number-1);

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

% cat > keymatch
#!/usr/bin/perl
$kwds = 'copy compare list print';
while( $cmd = <> ){
    $cmd =~ s/^\s+|\s+$//g;  # trim leading and trailing spaces
    if( ( @matches = $kwds =~ /\b$cmd\w*/g ) == 1 ){
        print "command: '@matches'\n";
    } elsif( @matches == 0 ){
        print "no such command: '$cmd'\n";
    } else {
        print "not unique: '$cmd' (could be one of: @matches)\n";
    }
}
^D

% keymatch
li
command: 'list'
co
not unique: 'co' (could be one of: copy compare)
printer
no such command: 'printer'

Вместо того, чтобы пытаться сопоставить ввод с ключевыми словами, мы сопоставляем объединённый набор ключевых слов с вводом. Операция сопоставления шаблонов $kwds =~ /\b($cmd\w*)/g делает несколько вещей одновременно. Она гарантирует, что заданная команда начинается там, где начинается ключевое слово (\b). Она допускает сокращения благодаря добавлению \w*. Она сообщает нам количество совпадений (scalar @matches) и все ключевые слова, которые фактически совпали. Вам вряд ли захочется чего-то большего.

Встраивание комментариев и модификаторов в регулярное выражение

Начиная с этого раздела, мы будем обсуждать набор расширенных шаблонов Perl. Это расширения традиционной синтаксической конструкции регулярных выражений, которые предоставляют мощные новые инструменты для сопоставления шаблонов. Мы уже видели расширения в виде минимальных конструкций сопоставления ??, *?, +?, {n,m}?, и {n,}?. Большинство расширений ниже имеют вид (?char...), где char — символ, определяющий тип расширения.

Первое расширение — встраиваемый комментарий (?#text). Это встраивает комментарий в регулярное выражение без изменения его значения. Комментарий не должен содержать закрывающих скобок в тексте. Пример:

/(?# Match an integer:)[+-]?\d+/;

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

Большинство модификаторов, таких как /i, /m, /s и /x (или любая их комбинация) также могут быть встроены в regexp с помощью (?i), (?m), (?s), и (?x). Например,

/(?i)yes/;  # match 'yes' case insensitively
/yes/i;     # same thing
/(?x)(          # freeform version of an integer regexp
         [+-]?  # match an optional sign
         \d+    # match a sequence of digits
     )
/x;

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

$pattern[0] = '(?i)doctor';
$pattern[1] = 'Johnson';
...
while (<>) {
    foreach $patt (@pattern) {
        print if /$patt/;
    }
}

Второе преимущество заключается в том, что встроенные модификаторы (за исключением /p, который изменяет всё регулярное выражение) влияют только на regexp внутри группы, в которой находится встроенный модификатор. Таким образом, группирование можно использовать для локализации эффектов модификатора:

/Answer: ((?i)yes)/;  # matches 'Answer: yes', 'Answer: YES', etc.

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

Встроенные модификаторы также могут быть добавлены к необязательной группировке. (?i-m:regexp) — необязательная группировка, которая соответствует regexp регистронезависимо и отключает многострочный режим.

Просмотр вперед и просмотр назад

Этот раздел посвящен утверждениям с опережением и отставанием. Сначала немного предыстории.

В регулярных выражениях Perl большинство элементов регулярных выражений «съедают» определенное количество символов строки при сопоставлении. Например, элемент регулярного выражения [abc] съедает один символ строки при сопоставлении в том смысле, что Perl переходит к следующей позиции символа в строке после сопоставления. Однако существуют некоторые элементы, которые не съедают символы (не продвигают позицию символа), если они совпадают. Примеры, которые мы видели до сих пор, — это якоря. Якорь '^' соответствует началу строки, но не съедает никаких символов. Аналогично, якорь границы слова \b соответствует тому месту, где символ, соответствующий \w, находится рядом с символом, который этому не соответствует, но сам не съедает никаких символов. Якоря являются примерами утверждений с нулевой шириной: нулевой ширины, потому что они не потребляют символы, и утверждений, потому что они проверяют некоторое свойство строки. В контексте нашей аналогии с походом в лес при сопоставлении регулярных выражений большинство элементов регулярных выражений перемещают нас вдоль тропы, но якоря заставляют нас на мгновение остановиться и осмотреться. Если местная среда соответствует нашим требованиям, мы можем продолжить движение вперед. Но если местная среда не удовлетворяет нас, мы должны вернуться назад.

Проверка среды подразумевает либо поиск вперед по тропе, либо поиск назад, либо и то, и другое. '^' ищет назад, чтобы убедиться, что перед ним нет символов. '$' ищет вперед, чтобы убедиться, что после него нет символов. \b ищет как вперед, так и назад, чтобы убедиться, что символы с обеих сторон отличаются по своей «словесности».

Утверждения с опережением и отставанием являются обобщением концепции якоря. Утверждения с опережением и отставанием — это утверждения с нулевой шириной, которые позволяют нам указать, какие символы мы хотим проверить. Утверждение с опережением обозначается (?=regexp), а утверждение с отставанием — (?<=fixed-regexp). Вот несколько примеров:

$x = "I catch the housecat 'Tom-cat' with catnip";
$x =~ /cat(?=\s)/;   # matches 'cat' in 'housecat'
@catwords = ($x =~ /(?<=\s)cat\w+/g);  # matches,
                                       # $catwords[0] = 'catch'
                                       # $catwords[1] = 'catnip'
$x =~ /\bcat\b/;  # matches 'cat' in 'Tom-cat'
$x =~ /(?<=\s)cat(?=\s)/; # doesn't match; no isolated 'cat' in
                          # middle of $x

Обратите внимание, что скобки в (?=regexp) и (?<=regexp) не являются захватывающими, так как это утверждения с нулевой шириной. Таким образом, во втором регулярном выражении подстроки, которые захватываются, — это подстроки всего самого регулярного выражения. Утверждение с опережением (?=regexp) может соответствовать произвольным регулярным выражениям, но утверждение с отставанием (?<=fixed-regexp) работает только для регулярных выражений с фиксированной шириной, т. е., имеющих фиксированное количество символов. Таким образом, (?<=(ab|bc)) подходит, но (?<=(ab)*) — нет. Отрицательные версии утверждений с опережением и отставанием обозначаются (?!regexp) и (?<!fixed-regexp) соответственно. Они оцениваются как истинные, если регулярные выражения не соответствуют:

$x = "foobar";
$x =~ /foo(?!bar)/;  # doesn't match, 'bar' follows 'foo'
$x =~ /foo(?!baz)/;  # matches, 'baz' doesn't follow 'foo'
$x =~ /(?<!\s)foo/;  # matches, there is no \s before 'foo'

Вот пример, где строка, содержащая слова, разделенные пробелами, числа и одиночные дефисы, должна быть разделена на свои составляющие. Использование только /\s+/ не сработает, потому что пробелы не требуются между дефисами или словом или дефисом. Дополнительные места для разделения устанавливаются с помощью поиска вперед и назад:

$str = "one two - --6-8";
@toks = split / \s+              # a run of spaces
              | (?<=\S) (?=-)    # any non-space followed by '-'
              | (?<=-)  (?=\S)   # a '-' followed by any non-space
              /x, $str;          # @toks = qw(one two - - - 6 - 8)

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

(?=...)        (*pla:...) or (*positive_lookahead:...)
(?!...)        (*nla:...) or (*negative_lookahead:...)
(?<=...)       (*plb:...) or (*positive_lookbehind:...)
(?<!...)       (*nlb:...) or (*negative_lookbehind:...)
(?>...)        (*atomic:...)

Использование любого из них вызовет (если не отключено) предупреждение в категории experimental::alpha_assertions.

Использование независимых подвыражений для предотвращения обратного поиска

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

$x = "ab";
$x =~ /a*ab/;  # matches

Это, очевидно, соответствует, но в процессе сопоставления подвыражение a* сначала захватило 'a'. Однако это не позволит всему регулярному выражению соответствовать, поэтому после обратного поиска a* в конечном итоге вернет 'a' и сопоставит пустую строку. Здесь то, что a* сопоставило, было зависимо от того, что сопоставило остальная часть регулярного выражения.

В отличие от этого, рассмотрим независимое подвыражение:

$x =~ /(?>a*)ab/;  # doesn't match!

Независимое подвыражение (?>a*) не заботится об остальной части регулярного выражения, поэтому оно видит 'a' и захватывает его. Затем остальная часть регулярного выражения ab не может соответствовать. Поскольку (?>a*) независимо, обратного поиска не происходит, и независимое подвыражение не отказывается от своего 'a'. Таким образом, совпадение всего регулярного выражения в целом терпит неудачу. Аналогичное поведение наблюдается с полностью независимыми регулярными выражениями:

$x = "ab";
$x =~ /a*/g;   # matches, eats an 'a'
$x =~ /\Gab/g; # doesn't match, no 'a' available

Здесь /g и \G создают «командную передачу» передачи строки от одного регулярного выражения к другому. Регулярные выражения с независимым подвыражением очень похожи на это, с передачей строки независимому подвыражению и передачей строки обратно окружающему регулярному выражению.

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

$x = "abc(de(fg)h";  # unbalanced parentheses
$x =~ /\( ( [ ^ () ]+ | \( [ ^ () ]* \) )+ \)/xx;

Регулярное выражение соответствует открывающей скобке, одному или нескольким экземплярам альтернативы и закрывающей скобке. Альтернатива двусторонняя, при этом первая альтернатива [^()]+ соответствует подстроке без скобок, а вторая альтернатива \([^()]*\) соответствует подстроке, ограниченной скобками. Проблема с этим регулярным выражением в том, что оно патологическое: оно имеет вложенные неопределенные квантификаторы формы (a+|b)+. Мы обсуждали в Части 1, как вложенные квантификаторы такого рода могут занимать экспоненциально большое время для выполнения, если соответствия нет. Для предотвращения экспоненциального взрыва нам нужно предотвратить бесполезный обратный поиск в какой-то момент. Это можно сделать, заключив внутренний квантификатор в качестве независимого подвыражения:

$x =~ /\( ( (?> [ ^ () ]+ ) | \([ ^ () ]* \) )+ \)/xx;

Здесь (?>[^()]+) нарушает вырожденность разбиения строки, захватывая как можно больше строки и сохраняя ее. Затем сбои в соответствии с подвыражениями происходят намного быстрее.

Условные выражения

Условное выражение — это форма оператора if-then-else, которая позволяет выбирать, какие шаблоны следует сопоставлять, исходя из определенного условия. Есть два типа условных выражений: (?(condition)yes-regexp) и (?(condition)yes-regexp|no-regexp). (?(condition)yes-regexp) подобно оператору 'if () {}' в Perl. Если условие истинно, будет сопоставлено регулярное выражение yes. Если условие ложно, регулярное выражение yes будет пропущено, и Perl перейдет к следующему элементу регулярного выражения. Второй вид похож на оператор 'if () {} else {}' в Perl. Если условие истинно, будет сопоставлено регулярное выражение yes, в противном случае будет сопоставлено регулярное выражение no.

Условие может иметь несколько форм. Первая форма — это просто целое число в скобках (integer). Оно истинно, если соответствующая ссылка на обратную ссылку \integer ранее сопоставлялась в регулярном выражении. То же самое можно сделать с именем, связанным с группой захвата, написанным как (<name>) или ('name'). Вторая форма — это чистое утверждение с нулевой шириной (?...), либо утверждение с опережением, либо утверждение с отставанием, либо утверждение кода (обсуждаемое в следующем разделе). Третий набор форм предоставляет тесты, которые возвращают true, если выражение выполняется внутри рекурсии ((R)) или вызывается из какой-либо группы захвата, указанной либо по номеру ((R1), (R2),...), либо по имени ((R&name)).

Целая форма или форма с именем condition позволяют нам более гибко выбирать, что сопоставлять, на основе того, что ранее сопоставлялось в регулярном выражении. Это ищет слова формы "$x$x" или "$x$y$y$x":

% simple_grep '^(\w+)(\w+)?(?(2)\g2\g1|\g1)$' /usr/dict/words
beriberi
coco
couscous
deed
...
toot
toto
tutu

Утверждение с отставанием condition позволяет, наряду со ссылками на обратные ссылки, ранней части соответствия влиять на более позднюю часть соответствия. Например,

/[ATGC]+(?(?<=AA)G|C)$/;

сопоставляет последовательность ДНК, такая, что она либо заканчивается на AAG, либо на некоторой другой комбинации пар оснований и 'C'. Обратите внимание, что форма — (?(?<=AA)G|C), а не (?((?<=AA))G|C); для утверждений с опережением, отставанием или кодом скобки вокруг условного выражения не нужны.

Определение именных шаблонов

Некоторые регулярные выражения используют одинаковые подшаблоны в нескольких местах. Начиная с Perl 5.10, можно определять именные подшаблоны в части шаблона, чтобы их можно было вызывать по имени в любой части шаблона. Этот синтаксический шаблон для этой группы определений — (?(DEFINE)(?<name>pattern)...). Вставка именного шаблона записывается как (?&name).

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

/^ (?&osg)\ * ( (?&int)(?&dec)? | (?&dec) )
   (?: [eE](?&osg)(?&int) )?
 $
 (?(DEFINE)
   (?<osg>[-+]?)         # optional sign
   (?<int>\d++)          # integer
   (?<dec>\.(?&int))     # decimal fraction
 )/x

Рекурсивные шаблоны

Эта функция (введена в Perl 5.10) существенно расширяет возможности сопоставления шаблонов Perl. Ссылаясь на какую-либо другую группу захвата в любом месте шаблона с конструкцией (?group-ref), шаблон внутри указанной группы используется как независимый подшаблон вместо ссылки на группу. Поскольку ссылка на группу может находиться внутри группы, на которую она ссылается, теперь можно применять сопоставление шаблонов к задачам, которые до сих пор требовали рекурсивного анализатора.

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

/(?: (\w) (?...Here be a palindrome...) \g{-1} | \w? )/x

Добавив \W* с любой стороны, чтобы исключить то, что должно быть проигнорировано, у нас уже есть полный шаблон:

my $pp = qr/^(\W* (?: (\w) (?1) \g{-1} | \w? ) \W*)$/ix;
for $s ( "saippuakauppias", "A man, a plan, a canal: Panama!" ){
    print "'$s' is a palindrome\n" if $s =~ /$pp/;
}

В (?...) можно использовать абсолютные и относительные обратные ссылки. Весь шаблон можно повторно вставить с помощью (?R) или (?0). Если вы предпочитаете именовать свои группы, вы можете использовать (?&name) для рекурсии в эту группу.

Немного магии: выполнение Perl-кода в регулярном выражении

Обычно выражения с регулярными выражениями (regexp) являются частью выражений Perl. Выражения оценки кода переворачивают это, позволяя произвольному Perl-коду быть частью regexp. Выражение оценки кода обозначается (?{code}), где код — строка Perl-команд.

Выражения кода представляют собой утверждения нулевой ширины, а значение, которое они возвращают, зависит от их окружения. Существуют два варианта: либо выражение кода используется как условие в условном выражении (?(condition)...), либо нет. Если выражение кода является условным, код оценивается, и результат (т.е., результат последней инструкции) используется для определения истинности или ложности. Если выражение кода не используется как условное, утверждение всегда оценивается как истинное, а результат помещается в специальную переменную $^R. Переменная $^R может затем использоваться в выражениях кода в дальнейшем в regexp. Вот некоторые глупые примеры:

$x = "abcdef";
$x =~ /abc(?{print "Hi Mom!";})def/; # matches,
                                     # prints 'Hi Mom!'
$x =~ /aaa(?{print "Hi Mom!";})def/; # doesn't match,
                                     # no 'Hi Mom!'

Обратите особое внимание на следующий пример:

$x =~ /abc(?{print "Hi Mom!";})ddd/; # doesn't match,
                                     # no 'Hi Mom!'
                                     # but why not?

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

$x =~ /abc(?{print "Hi Mom!";})[dD]dd/; # doesn't match,
                                        # but _does_ print

Хмм. Что здесь произошло? Если вы следили за этим, вы знаете, что вышеприведенный шаблон должен быть в значительной степени (почти) таким же, как последний; включение 'd' в класс символов не изменит того, что он сопоставляет. Так почему же первый не печатает, а второй печатает?

Ответ кроется в оптимизациях, которые производит движок regexp. В первом случае движок видит только обычные символы (кроме конструкции ?{}). Он достаточно умен, чтобы понять, что строка 'ddd' не встречается в нашей целевой строке, прежде чем фактически запускать шаблон. Но во втором случае мы обманули его, заставив его думать, что наш шаблон более сложный. Он смотрит, видит наш класс символов и решает, что ему придется фактически запустить шаблон, чтобы определить, соответствует ли он, и в процессе его выполнения выполняется оператор print, прежде чем он обнаружит, что у нас нет совпадения.

Чтобы более подробно рассмотреть, как движок выполняет оптимизации, см. раздел "Прагмы и отладка" ниже.

Еще больше веселья с ?{}:

$x =~ /(?{print "Hi Mom!";})/;       # matches,
                                     # prints 'Hi Mom!'
$x =~ /(?{$c = 1;})(?{print "$c";})/;  # matches,
                                       # prints '1'
$x =~ /(?{$c = 1;})(?{print "$^R";})/; # matches,
                                       # prints '1'

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

$x = "aaaa";
$count = 0;  # initialize 'a' count
$c = "bob";  # test if $c gets clobbered
$x =~ /(?{local $c = 0;})         # initialize count
       ( a                        # match 'a'
         (?{local $c = $c + 1;})  # increment count
       )*                         # do this any number of times,
       aa                         # but match 'aa' at the end
       (?{$count = $c;})          # copy local $c var into $count
      /x;
print "'a' count is $count, \$c variable is '$c'\n";

Это печатает

'a' count is 2, $c variable is 'bob'

Если мы заменим (?{local $c = $c + 1;}) на (?{$c = $c + 1;}), изменения в переменных не отменяются во время отслеживания обратного пути, и мы получаем

'a' count is 4, $c variable is 'bob'

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

$x = "aaaa";
$x =~ /(a(?{print "Yow\n";}))*aa/;

выводит

Yow
Yow
Yow
Yow

Результат $^R автоматически локализуется, чтобы он корректно работал при наличии обратного отслеживания.

В этом примере используется выражение кода в условном операторе для сопоставления определенного артикля, либо 'the' на английском языке, либо 'der|die|das' на немецком языке:

$lang = 'DE';  # use German
...
$text = "das";
print "matched\n"
    if $text =~ /(?(?{
                      $lang eq 'EN'; # is the language English?
                     })
                   the |             # if so, then match 'the'
                   (der|die|das)     # else, match 'der|die|das'
                 )
                /xi;

Обратите внимание, что синтаксис здесь (?(?{...})yes-regexp|no-regexp), а не (?((?{...}))yes-regexp|no-regexp). Другими словами, в случае выражения кода нам не нужны дополнительные скобки вокруг условного оператора.

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

$bar = 5;
$pat = '(?{ 1 })';
/foo(?{ $bar })bar/; # compiles ok, $bar not interpolated
/foo(?{ 1 })$bar/;   # compiles ok, $bar interpolated
/foo${pat}bar/;      # compile error!

$pat = qr/(?{ $foo = 1 })/;  # precompile code regexp
/foo${pat}bar/;      # compiles ok

Если у regexp есть переменная, интерполирующая выражение кода, Perl обрабатывает regexp как ошибку. Однако, если выражение кода предварительно скомпилировано в переменную, интерполяция разрешена. Вопрос в том, почему это ошибка?

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

$regexp = <>;       # read user-supplied regexp
$chomp $regexp;     # get rid of possible newline
$text =~ /$regexp/; # search $text for the $regexp

Если переменная $regexp содержит выражение кода, пользователь может затем выполнить произвольный Perl-код. Например, какой-то шутник может искать system('rm -rf *'); для удаления ваших файлов. В этом смысле сочетание интерполяции и выражений кода делает ваш regexp загрязненным. Поэтому по умолчанию использование интерполяции и выражений кода в одном regexp запрещено. Если вас не беспокоят злонамеренные пользователи, вы можете обойти эту проверку безопасности, вызвав use re 'eval':

use re 'eval';       # throw caution out the door
$bar = 5;
$pat = '(?{ 1 })';
/foo${pat}bar/;      # compiles ok

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

$length = 5;
$char = 'a';
$x = 'aaaaabb';
$x =~ /(??{$char x $length})/x; # matches, there are 5 of 'a'

Этот заключительный пример содержит как обычные, так и выражения кода шаблона. Он определяет, имеет ли двоичная строка 1101010010001... интервалы Фибоначчи 0,1,1,2,3,5,... '1':

$x = "1101010010001000001";
$z0 = ''; $z1 = '0';   # initial conditions
print "It is a Fibonacci sequence\n"
    if $x =~ /^1         # match an initial '1'
                (?:
                   ((??{ $z0 })) # match some '0'
                   1             # and then a '1'
                   (?{ $z0 = $z1; $z1 .= $^N; })
                )+   # repeat as needed
              $      # that is all there is
             /x;
printf "Largest sequence matched was %d\n", length($z1)-length($z0);

Помните, что $^N устанавливается в соответствии с тем, что было сопоставлено последней завершенной группой захвата. Это печатает

It is a Fibonacci sequence
Largest sequence matched was 5

Ха! Попробуйте это с вашим обычным пакетом regexp…

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

Этот regexp без модификатора /x

/^1(?:((??{ $z0 }))1(?{ $z0 = $z1; $z1 .= $^N; }))+$/

показывает, что пробелы всё еще возможны в частях кода. Тем не менее, при работе с выражениями кода и условными операторами расширенная форма regexp почти необходима при создании и отладке regexp.

Управляющие глаголы обратного отслеживания

Perl 5.10 представил ряд управляющих глаголов, предназначенных для предоставления детального управления процессом обратного отслеживания, путем непосредственного влияния на движок regexp и путем предоставления методов мониторинга. См. "Special Backtracking Control Verbs" в perlre для подробного описания.

Ниже приведен всего один пример, иллюстрирующий управляющий глагол (*FAIL), который можно сократить до (*F). Если его вставить в regexp, он приведет к его отказу, точно так же, как это произошло бы при несовпадении шаблона и строки. Обработка regexp продолжается так, как это происходило бы после любого "нормального" отказа, так что, например, будет опробована следующая позиция в строке или другая альтернатива. Поскольку неудача в совпадении не сохраняет группы захвата или не производит результатов, может потребоваться использовать это в сочетании со встроенным кодом.

%count = ();
"supercalifragilisticexpialidocious" =~
    /([aeiou])(?{ $count{$1}++; })(*FAIL)/i;
printf "%3d '%s'\n", $count{$_}, $_ for (sort keys %count);

Шаблон начинается с класса, сопоставляющего подмножество букв. Всякий раз, когда это происходит, выполняется инструкция, подобная $count{'a'}++;, увеличивающая счетчик буквы. Затем (*FAIL) делает то, что сказано, и движок regexp продолжает действовать по правилам: пока не будет достигнут конец строки, позиция продвинется вперед, прежде чем искать другую гласную. Таким образом, совпадение или его отсутствие не имеет значения, и движок regexp продолжает работу, пока не будет проверен весь текст. (Примечательно, что альтернативное решение, использующее что-то вроде

$count{lc($_)}++ for split('', "supercalifragilisticexpialidocious");
printf "%3d '%s'\n", $count2{$_}, $_ for ( qw{ a e i o u } );

значительно медленнее.)

Прагмы и отладка

Говоря об отладке, существуют несколько прагм, доступных для управления и отладки regexp в Perl. Мы уже встречали одну прагму в предыдущем разделе, use re 'eval';, которая позволяет переменной интерполяции и выражениям кода сосуществовать в regexp. Другие прагмы —

use re 'taint';
$tainted = <>;
@parts = ($tainted =~ /(\w+)\s+(\w+)/; # @parts is now tainted

Прагма taint заставляет любые подстроки из совпадения с зараженной переменной стать зараженными также. Это обычно не так, поскольку regexp часто используются для извлечения безопасных частей из зараженной переменной. Используйте taint в тех случаях, когда вы не извлекаете безопасные части, а выполняете некоторую другую обработку. Обе прагмы taint и eval обладают лексическим объемом действия, что означает, что они действуют только до конца блока, включающего прагмы.

use re '/m';  # or any other flags
$multiline_string =~ /^foo/; # /m is implied

Прагма re '/flags' (введена в Perl 5.14) включает указанные флаги регулярных выражений до конца лексического объема. См. "'/flags' mode" в re для получения дополнительной информации.

use re 'debug';
/^(.*)$/s;       # output debugging info

use re 'debugcolor';
/^(.*)$/s;       # output debugging info in living color

Глобальные прагмы debug и debugcolor позволяют получить подробную отладочную информацию о компиляции и выполнении regexp. debugcolor аналогична debug, за исключением того, что отладочная информация отображается в цвете на терминалах, которые могут отображать цветовые последовательности termcap. Вот пример вывода:

% perl -e 'use re "debug"; "abc" =~ /a*b+c/;'
Compiling REx 'a*b+c'
size 9 first at 1
   1: STAR(4)
   2:   EXACT <a>(0)
   4: PLUS(7)
   5:   EXACT <b>(0)
   7: EXACT <c>(9)
   9: END(0)
floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0
Matching REx 'a*b+c' against 'abc'
  Setting an EVAL scope, savestack=3
   0 <> <abc>           |  1:  STAR
                         EXACT <a> can match 1 times out of 32767...
  Setting an EVAL scope, savestack=3
   1 <a> <bc>           |  4:    PLUS
                         EXACT <b> can match 1 times out of 32767...
  Setting an EVAL scope, savestack=3
   2 <ab> <c>           |  7:      EXACT <c>
   3 <abc> <>           |  9:      END
Match successful!
Freeing REx: 'a*b+c'

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

Compiling REx 'a*b+c'
size 9 first at 1
   1: STAR(4)
   2:   EXACT <a>(0)
   4: PLUS(7)
   5:   EXACT <b>(0)
   7: EXACT <c>(9)
   9: END(0)

описывает этап компиляции. STAR(4) означает, что есть звездочный объект, в данном случае 'a', и если он соответствует, перейти к строке 4, т.е., PLUS(7). Средние строки описывают некоторые эвристики и оптимизации, выполненные до совпадения:

floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0

Затем выполняется сопоставление, а оставшиеся строки описывают процесс:

Matching REx 'a*b+c' against 'abc'
  Setting an EVAL scope, savestack=3
   0 <> <abc>           |  1:  STAR
                         EXACT <a> can match 1 times out of 32767...
  Setting an EVAL scope, savestack=3
   1 <a> <bc>           |  4:    PLUS
                         EXACT <b> can match 1 times out of 32767...
  Setting an EVAL scope, savestack=3
   2 <ab> <c>           |  7:      EXACT <c>
   3 <abc> <>           |  9:      END
Match successful!
Freeing REx: 'a*b+c'

Каждый шаг имеет вид n <x> <y>, где <x> — часть строки, которая соответствует, а <y> — часть, которая еще не соответствует. | 1: STAR говорит, что Perl находится на строке 1 в списке компиляции выше. См. "Debugging Regular Expressions" в perldebguts для получения более подробной информации.

Альтернативный метод отладки regexp — вставка инструкций print в regexp. Это обеспечивает подробный отчет об отслеживании обратного пути в альтернации:

"that this" =~ m@(?{print "Start at position ", pos, "\n";})
                 t(?{print "t1\n";})
                 h(?{print "h1\n";})
                 i(?{print "i1\n";})
                 s(?{print "s1\n";})
                     |
                 t(?{print "t2\n";})
                 h(?{print "h2\n";})
                 a(?{print "a2\n";})
                 t(?{print "t2\n";})
                 (?{print "Done at position ", pos, "\n";})
                @x;

печатает

Start at position 0
t1
h1
t2
h2
a2
t2
Done at position 4

СМОТРИТЕ ТАКЖЕ

Это всего лишь учебник. Для получения полной информации о регулярных выражениях Perl см. страницу справки по регулярным выражениям perlre.

Дополнительную информацию о операторах сопоставления m// и подстановки s/// см. в "Regexp Quote-Like Operators" в perlop. Информацию об операции split см. в "split" в perlfunc.

Для получения исчерпывающей информации о работе с регулярными выражениями, обратитесь к книге Джеффри Фридла «Мастерство регулярных выражений» (издательство O'Reilly, ISBN 1556592-257-3).

АВТОР И АВТОРСКИЕ ПРАВА

Copyright (c) 2000 Марк Квале. Все права защищены. Сейчас поддерживается Perl-разработчиками.

Этот документ можно распространять на тех же условиях, что и Perl сам по себе.

Благодарности

Вдохновением для примера кодона терминации ДНК послужил пример индекса ZIP в главе 7 книги «Мастерство регулярных выражений».

Автор выражает благодарность Джеффу Пиньяну, Эндрю Джонсону, Питеру Хоуорту, Рональду Дж. Кимбалла и Джо Смиту за все их ценные комментарии.

© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.28.3/perlretut

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API