Spec-Zone.ru › Perl 5.34

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/

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

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

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

Примечание: для экономии времени «регулярное выражение» часто сокращается до regexp или regex. Regexp — более естественное сокращение, но его сложнее произносить. В документации Perl под документации поровну разделены 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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

% chmod +x simple_grep

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Теперь даже [0-9] может быть проблематично писать несколько раз, поэтому в целях экономии нажатий клавиш и повышения читабельности регулярных выражений Perl имеет несколько сокращений для общих классов символов, как показано ниже. С появлением Unicode, если модификатор /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 (и связанных с ним проблем безопасности), когда всё, что вам нужно, это обработка текста, подобного английскому. (Буква «a» может быть удвоена, /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]/

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

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

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

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

Ещё несколько примеров

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Подсчёт открывающих скобок для получения правильного номера обратной ссылки становится сложным, как только появляется более одной группы захвата. В Perl 5.10 стала доступна более удобная техника: относительные обратные ссылки. Чтобы сослаться на непосредственно предыдущую группу захвата, теперь можно написать \g-1 или \g{-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+))?)/;

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

$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)
0. Начинаем с первой буквы строки 't'.
1. Первый квантификатор '.*' пытается сопоставить всю строку "the cat in the hat".
2. 'a' в элементе регулярного выражения 'at' не сопоставляется с концом строки. Откатываемся назад на один символ.
3. 'a' в элементе регулярного выражения 'at' все еще не сопоставляется с последней буквой строки 't', поэтому откатываемся назад еще на один символ.
4. Теперь мы можем сопоставить 'a' и 't'.
5. Переходим к третьему элементу '.*'. Так как мы в конце строки и '.*' может сопоставить 0 раз, присваиваем ему пустую строку.
6. Завершено!

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

/(a|b+)*/;

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

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

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

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

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

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

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

  • a{n,}+ означает: сопоставить как минимум n раз, но как можно больше раз, и не отказываться от результатов. a++ является сокращением от a{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 v5.26, указание /xx изменяет квадратные скобки в шаблоне так, что табуляции и пробелы игнорируются, если они не экранированы с помощью предшествующей обратной косой черты. Поэтому мы можем написать

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

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

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

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

  • подробное описание задачи,

  • разбиение проблемы на более мелкие части,

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

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

  • и оптимизацию объединенного регулярного выражения.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

выводит

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

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

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

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

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

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

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

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

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

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

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

что выводит

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

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

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

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

Это выводит

Got a TGA stop codon at position 18

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

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

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

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

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

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

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

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

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

% simple_replace regexp regex perlretut.pod

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

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

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

Этот пример выведет «Мне нравятся собаки. Мне нравятся кошки». Обратите внимание, что исходная переменная $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 в верхний регистр, а остальные символы — в нижний.

Управляющие символы можно экранировать с помощью \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 требовалось сказать use utf8, чтобы использовать любые функции Unicode. Сейчас это больше не так: для почти всех операций с Unicode явное использование utf8 псевдонима не требуется. (Единственный случай, когда это важно, — если ваш сценарий Perl находится в Unicode и закодирован в UTF-8, тогда необходимо явное использование use utf8.)

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

$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";

Онлайн-индекс имён символов доступен от Консорциума Юникода по адресу 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) состоит в том, что регулярное выражение (в основном) использует символы Юникода. «В основном» — это из-за проблем обратной совместимости, но начиная с Perl 5.14, любое регулярное выражение, скомпилированное в рамках use feature 'unicode_strings' (которое автоматически включается в рамках use 5.012 или выше), превратит это «в основном» в «всегда». Если вы хотите правильно обработать Юникод, вы должны убедиться, что 'unicode_strings' включено. Внутренне это кодируется в байты с помощью UTF-8 или родного кодирования с 8 битами, в зависимости от истории строки, но концептуально это последовательность символов, а не байтов. См. perlunitut для руководства по этому вопросу.

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

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

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

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

Свойства Юникода \p{Script} и \p{Script_Extensions} используются для категоризации каждого символа Юникода по письменному языку сценария. Например, английский, французский и множество других европейских языков написаны латинским шрифтом. Но также есть греческий шрифт, тайский шрифт, катакана, и т. д. (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, которая выполняет поиск по последовательности шаблонов, переходя к следующему шаблону, как только один из них будет удовлетворён.

% 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. Если условие истинно, будет сопоставлено выражение-да. Если условие ложно, выражение-да будет пропущено, и Perl перейдёт к следующему элементу регулярного выражения. Второй вариант похож на оператор 'if () {} else {}' в Perl. Если условие истинно, будет сопоставлено выражение-да, в противном случае будет сопоставлено выражение-нет.

Условие может иметь несколько форм. Первая форма — просто целое число в скобках (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. Выражения вычисления кода переворачивают это, разрешая произвольный код 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 одновременно с компиляцией 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 заставляет любые подстроки из совпадения с загрязнённой переменной быть также загрязнёнными. Обычно это не так, так как регулярные выражения часто используются для извлечения безопасных частей из загрязнённой переменной. Используйте 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–2021 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.34.0/perlretut

Spec-Zone.ru

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