Spec-Zone.ru › Perl 5.38

perlretut

СОДЕРЖАНИЕ

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

ИМЯ

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

ОПИСАНИЕ

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

Perl широко известен своим мастерством в обработке текста, и регулярные выражения являются одним из основных факторов этой славы. Регулярные выражения 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 — более естественное сокращение, чем regex, но произнести его сложнее. В документации 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 paired '{}'
"/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

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

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

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

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

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

Этот список не является таким исчерпывающим, как может показаться (или как утверждается в других документах). Например, "#" является метасимволом только при использовании модификатора шаблона /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

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

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

Символ обратной косой черты '\' сам является метасимволом и также требует обратной косой черты:

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

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

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

"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, все эти разговоры о последовательностях escape могут показаться знакомыми. Аналогичные последовательности escape используются в строках с двойными кавычками, и на самом деле регулярные выражения в 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' означает сопоставление без учета регистра и является примером модификатора операции сопоставления. Мы встретим другие модификаторы позже в учебном пособии.

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

/[\]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, если модификатор /a не активен, эти классы символов соответствуют большему числу символов, чем только несколько символов в диапазоне ASCII.

  • \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)/;
  1. Начните с первой буквы в строке 'a'.

  2. Попробуйте первую альтернативу в первой группе 'abd'.

  3. Сопоставьте 'a' с последующим 'b'. Всё хорошо.

  4. 'd' в регулярном выражении не соответствует 'c' в строке — тупик. Поэтому вернитесь назад на две позиции и выберите вторую альтернативу в первой группе 'abc'.

  5. Сопоставьте 'a' с последующим 'b' с последующим 'c'. У нас всё получается, и первая группа удовлетворена. Установите $1 в 'abc'.

  6. Перейдите ко второй группе и выберите первую альтернативу 'df'.

  7. Сопоставьте 'd'.

  8. 'f' в регулярном выражении не соответствует 'e' в строке, поэтому тупик. Вернитесь на одну позицию назад и выберите вторую альтернативу во второй группе 'd'.

  9. 'd' соответствует. Вторая группа удовлетворена, поэтому установите $2 в 'd'.

  10. Мы достигли конца регулярного выражения, значит всё готово! Мы сопоставили '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)/ возвращает значение true или false. Однако в списочном контексте он возвращает список сопоставленных значений ($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{-1}, на предпоследнюю — \g-2 или \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..$#-) {
    no strict 'refs';
    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+))?)/;

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

$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{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{ 2, 4 }$/;    # Same; for those who like wide open
                            # spaces.
$year =~ /^\d{2, 4}$/;      # Same.
$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 раз, но как можно меньше раз.

  • 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)
  1. Начните с первой буквы в строке 't'.

  2. Первый квантификатор '.*' начинает с соответствия всей строке "the cat in the hat".

  3. 'a' в элементе регулярного выражения 'at' не соответствует концу строки. Вернитесь назад на один символ.

  4. 'a' в элементе регулярного выражения 'at' всё ещё не соответствует последней букве строки 't', поэтому вернитесь назад ещё на один символ.

  5. Теперь мы можем сопоставить 'a' и 't'.

  6. Перейдите к третьему элементу '.*'. Так как мы находимся в конце строки и '.*' может соответствовать 0 раз, назначьте ему пустую строку.

  7. Всё готово!

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

/(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{1,}+.

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

  • 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 версии 5.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. В кодирующей области кодоны являются трехбуквенными последовательностями, поэтому мы можем рассматривать фрагмент ДНК как последовательность трехбуквенных записей. Простое регулярное выражение

# 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 регулярных выражений. Во второй части мы предполагаем, что вы знакомы с основами и сосредоточимся на расширенных функциях.

Более подробно о символах, строках и классах символов

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

Существуют несколько управляющих последовательностей, которые преобразуют символы или строки между прописными и строчными буквами, и они также доступны в шаблонах. \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 в верхний регистр, а остальные символы — в нижний. (Для символов, выходящих за рамки ASCII, всё несколько сложнее; \u фактически выполняет преобразование в прописной регистр, которое для большинства символов такое же, как верхний регистр, но не для всех; см. https://unicode.org/faq/casemap_charprop.html#4.)

Управляющие символы могут быть экранированы с помощью \c, так что символ управления Z будет сопоставлен с \cZ. Управляющая последовательность \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 :)
/\x{ 263a }/; # Same

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

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

$x = "abc\N{MERCURY}def";
$x =~ /\N{MERCURY}/;   # matches
$x =~ /\N{ MERCURY }/; # Also 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, https://www.unicode.org/charts/charindex.html; справочный материал с ссылками на другие ресурсы по адресу https://www.unicode.org/standard/where.

Начиная с Perl v5.32, есть альтернатива \N{...}, позволяющая использовать полные имена, — это

/\p{Name=greek small letter sigma}/

Регистр имени символа не имеет значения при использовании в \p{}, как и большинство пробелов, нижних подчеркиваний и тире. (Некоторые исключительные символы вызывают проблемы при игнорировании всех из них всегда. Подробности (которые вы можете найти, когда станете более опытным и если вам это понадобится) находятся по адресу https://www.unicode.org/reports/tr44/tr44-24.html#UAX44-LM2).

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

Теперь давайте обсудим классы символов Unicode, обычно называемые «свойствами символов». Они представлены с помощью управляющей последовательности \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 — более старая, менее совершенная форма Script_Extensions, сохраняемая только для обратной совместимости.) Можно проверить, принадлежит ли символ конкретному шрифту, например \p{Latin}, \p{Greek}, или \p{Katakana}. Чтобы проверить, не принадлежит ли он балийскому шрифту, используйте \P{Balinese}. (Все эти вещи используют Script_Extensions под капотом, поскольку это даёт лучшие результаты.)

Рассмотренные до этого формы символьных классов \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 — это сокращение для символьного класса, который включает в себя расширенный кластер графем Юникода. Это представляет собой «логический символ»: то, что выглядит как один символ, но может представляться внутри более чем одним. Например, используя полные имена Юникода, например, "A + COMBINING RING" — это кластер графем с базовым символом «A» и комбинирующим символом "COMBINING RING, что на датском языке переводится как «A» с кружком сверху, как в слове Ångstrom.

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

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

Уф! Это всё, что осталось из символов и символьных классов.

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

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

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

Затем $reg можно использовать как регулярное выражение:

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

$reg также можно включить в более крупное регулярное выражение:

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

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

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

% 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;

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

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

Поиск с возвратом более эффективен, чем многократные попытки с различными регулярными выражениями. Если есть несколько регулярных выражений, и соответствие с любым из них приемлемо, то их можно объединить в набор альтернатив. Если отдельные выражения являются входными данными, это можно сделать, запрограммировав операцию объединения. Мы воспользуемся этой идеей в улучшенной версии программы 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,}?, и {,n}?. Большинство расширений ниже имеют вид (?char...), где char — это символ, определяющий тип расширения.

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

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

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

Большинство модификаторов, таких как /i, /m, /s и /x (или любое их сочетание) также могут быть встроены в регулярное выражение с помощью (?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;

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

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

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

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

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

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

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

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

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

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

Утверждения предварительного и последующего просмотра — это обобщения концепции якоря. Предварительный и последующий просмотр — это утверждения нулевой ширины, которые позволяют указать, какие символы мы хотим проверить. Утверждение предварительного просмотра обозначается (?=regexp) или (начиная с версии 5.32, экспериментально в 5.28) (*pla:regexp) или (*positive_lookahead:regexp); а утверждение последующего просмотра обозначается (?<=fixed-regexp) или (начиная с версии 5.32, экспериментально в 5.28) (*plb:fixed-regexp) или (*positive_lookbehind:fixed-regexp). Примеры:

$x = "I catch the housecat 'Tom-cat' with catnip";
$x =~ /cat(*pla:\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

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

Отрицательные версии утверждений предварительного и последующего просмотра обозначаются (?!regexp) и (?<!fixed-regexp) соответственно. Или, начиная с версии 5.32 (экспериментально в 5.28), (*nla:regexp), (*negative_lookahead:regexp), (*nlb:regexp), или (*negative_lookbehind: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)

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

Независимые подвыражения (или атомарные подвыражения) — это регулярные выражения, в контексте более крупного регулярного выражения, которые функционируют независимо от более крупного регулярного выражения. То есть они потребляют столько или так мало строки, сколько им нужно, не обращая внимания на возможность совпадения с более крупным регулярным выражением. Независимые подвыражения представляются (?>regexp) или (начиная с 5.32, экспериментально в 5.28) (*atomic: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. Если условие истинно, будет сопоставлено regexp-да. Если условие ложно, regexp-да будет пропущено, и Perl перейдёт к следующему элементу регулярного выражения. Вторая форма подобна оператору 'if () {} else {}' в Perl. Если условие истинно, будет сопоставлено regexp-да, в противном случае будет сопоставлено regexp-нет.

Условие может иметь несколько форм. Первая форма — просто целое число в скобках (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-кода в регулярном выражении

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

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

$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' в класс символов не изменит того, что он сопоставляет. Так почему же первый не печатает, а второй — да?

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

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

Больше веселья с ?{}:

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

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

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

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

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

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

Ха! Попробуйте это с вашим обычным пакетом регулярных выражений...

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

Это регулярное выражение без модификатора /x:

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

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

Управляющие глаголы возврата назад

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

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

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

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

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

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

Предикаты и отладка

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

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

Предикат taint заставляет любые подстроки из совпадения с загрязнённой переменной также быть загрязнёнными, если ваш Perl поддерживает загрязнение (см. perlsec). Обычно это не так, так как регулярные выражения часто используются для извлечения чистых частей из загрязнённой переменной. Используйте taint в том случае, если вы не извлекаете чистые части, а выполняете некоторую другую обработку. Предикаты taint и eval имеют лексический диапазон, что означает, что они действуют только до конца блока, содержащего предикаты.

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

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

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

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

Глобальные предикаты debug и debugcolor позволяют получить подробную информацию об отладке компиляции и выполнения регулярных выражений. 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 в списке компиляции выше. Более подробную информацию см. в "Отладка регулярных выражений" в perldebguts.

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

"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/// см. "Операторы регулярных выражений, похожих на операторы цитирования" в perlop. Информацию об операции split см. в "split" в perlfunc.

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

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

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

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

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

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

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

© 1993–2023 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.38.0/perlretut

Spec-Zone.ru

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