perlretut
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ОПИСАНИЕ
- Часть 1: Основы
- Простое сопоставление слов
- Использование классов символов
- Сопоставление этого или того
- Группировка элементов и иерархическое сопоставление
- Извлечение совпадений
- Обратные ссылки
- Относительные обратные ссылки
- Именованные обратные ссылки
- Альтернативная нумерация групп захвата
- Информация о позиции
- Незахватывающие группировки
- Сопоставление повторений
- Позиционные квантификаторы
- Создание regexp
- Использование регулярных выражений в Perl
- Часть 2: Расширенные инструменты
- Подробнее о символах, строках и классах символов
- Компиляция и сохранение регулярных выражений
- Компоновка регулярных выражений во время выполнения
- Встраивание комментариев и модификаторов в регулярное выражение
- Просмотр вперед и назад
- Использование независимых подвыражений для предотвращения обратного поиска
- Условные выражения
- Определение именованных шаблонов
- Рекурсивные шаблоны
- Немного магии: выполнение Perl-кода в регулярном выражении
- Глаголы управления обратным поиском
- Предикаты и отладка
- СМОТРИТЕ ТАКЖЕ
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlretut - Руководство по регулярным выражениям Perl
ОПИСАНИЕ
Эта страница предоставляет базовое руководство по пониманию, созданию и использованию регулярных выражений в Perl. Она служит дополнением к справочной странице по регулярным выражениям perlre. Регулярные выражения являются неотъемлемой частью операторов m//, s///, qr// и split, поэтому это руководство также перекликается с "Операторами-квалификаторами регулярных выражений" в perlop и "split" в perlfunc.
Perl широко известен своим превосходством в обработке текста, и регулярные выражения являются одной из основных причин этой славы. Регулярные выражения Perl демонстрируют эффективность и гибкость, неизвестные большинству других компьютерных языков. Освоение даже основ регулярных выражений позволит вам манипулировать текстом с поразительной легкостью.
Что такое регулярное выражение? В самом простом случае, регулярное выражение — это шаблон, используемый для определения наличия определенных характеристик в строке. Строка чаще всего представляет собой текст, такой как строка, предложение, веб-страница или даже целая книга, но не обязательно. Это может быть и двоичные данные, например. Биологи часто используют Perl для поиска закономерностей в длинных последовательностях ДНК.
Предположим, мы хотим определить, содержит ли текст в переменной $var последовательность символов m u s h r o o m (пробелы добавлены для удобочитаемости). Мы можем написать на Perl
$var =~ m/mushroom/ Значение этого выражения будет TRUE, если $var содержит эту последовательность символов где-либо внутри неё, и FALSE в противном случае. Часть, заключённая в '/' символы, обозначает искомую характеристику. Мы используем термин шаблон для этого. Процесс поиска наличия шаблона в строке называется сопоставлением, а оператор "=~" вместе с m// говорят Perl о попытке сопоставить шаблон со строкой. Обратите внимание, что шаблон также является строкой, но очень особым видом, как мы увидим. Шаблоны широко используются в наши дни; примерами являются шаблоны, набираемые в поисковой системе для поиска веб-страниц, и шаблоны, используемые для перечисления файлов в каталоге, например, "ls *.txt" или "dir *.*". В Perl шаблоны, описываемые регулярными выражениями, используются не только для поиска строк, но и для извлечения необходимых частей строк, а также для поиска и замены.
Регулярные выражения имеют незаслуженную репутацию абстрактных и сложных для понимания. Это происходит просто потому, что обозначения, используемые для их выражения, склонны к краткости и плотности, а не из-за внутренней сложности. Мы рекомендуем использовать модификатор регулярных выражений /x (описанный ниже) вместе с достаточным количеством пробелов, чтобы сделать их менее плотно расположенными и легче читаемыми. Регулярные выражения строятся с использованием простых понятий, таких как условные операторы и циклы, и не сложнее для понимания, чем соответствующие if условные операторы и while циклы в языке Perl.
Это руководство упрощает процесс обучения, рассматривая понятия регулярных выражений вместе с их обозначениями по одному за раз и с множеством примеров. Первая часть руководства будет продвигаться от простейших поисков слов к основным концепциям регулярных выражений. Если вы освоите первую часть, у вас будут все инструменты, необходимые для решения примерно 98% ваших задач. Вторая часть руководства предназначена для тех, кто знаком с основами и жаждет более мощных инструментов. В ней рассматриваются более сложные операторы регулярных выражений и вводятся новейшие передовые разработки.
Примечание: для экономии времени "регулярное выражение" часто сокращается до regexp или regex. Regexp — более естественное сокращение, но труднее произносить. Документация Perl pod равномерно разделена на regexp против regex; в Perl существует более одного способа его сокращения. В этом руководстве мы будем использовать regexp.
В версии 5.22, use re 'strict' применяет более строгие правила, чем обычно, при компиляции шаблонов регулярных выражений. Он может находить то, что, хотя и законно, может не соответствовать вашим намерениям.
Часть 1: Основы
Простое сопоставление слов
Самое простое регулярное выражение — это просто слово, или, более общо, строка символов. Регулярное выражение, состоящее только из слова, соответствует любой строке, содержащей это слово:
"Hello World" =~ /World/; # matches О чём говорит это утверждение Perl? "Hello World" — это простая строка с двойными кавычками. World — это регулярное выражение, а окружающие /World/ скобки говорят Perl о поиске совпадения в строке. Оператор =~ связывает строку с совпадением по регулярному выражению и возвращает истинное значение, если совпадение найдено, или ложное, если совпадение не найдено. В нашем случае 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";
} Буквальная строка в регулярном выражении может быть заменена переменной:
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} — всё это одно и то же. Например, если в качестве разделителя используется кавычка ('"'), обратный слэш '/' становится обычным символом и может использоваться в этом регулярном выражении без проблем.
Давайте рассмотрим, как разные регулярные выражения соответствовали бы строке "Hello World":
"Hello World" =~ /world/; # doesn't match
"Hello World" =~ /o W/; # matches
"Hello World" =~ /oW/; # doesn't match
"Hello World" =~ /World /; # doesn't match Первое регулярное выражение world не совпадает, потому что регулярные выражения по умолчанию чувствительны к регистру. Второе регулярное выражение совпадает, потому что подстрока 'o W' встречается в строке "Hello World". Пробел ' ' обрабатывается как любой другой символ в регулярном выражении и нужен в данном случае. Отсутствие пробела — причина, по которой третье регулярное выражение 'oW' не совпадает. Четвёртое регулярное выражение "World " не совпадает, потому что в конце регулярного выражения есть пробел, но не в конце строки. Урок здесь заключается в том, что регулярное выражение должно точно совпадать с частью строки, чтобы утверждение было истинным.
Если регулярное выражение соответствует строке более чем в одном месте, Perl всегда будет сопоставлять его с самым ранним возможным местом в строке:
"Hello World" =~ /o/; # matches 'o' in 'Hello'
"That hat is red" =~ /hat/; # matches 'hat' in 'That' Что касается сопоставления символов, есть несколько дополнительных моментов, которые вам нужно знать. Во-первых, не все символы могут использоваться "как есть" в сопоставлении. Некоторые символы, называемые метасимволами, обычно зарезервированы для использования в обозначениях регулярных выражений. Метасимволами являются
{}[]()^$.|*+?-#\ Этот список не так определён, как может показаться (или как заявлено в другой документации). Например, "#" является метасимволом только тогда, когда используется модификатор шаблона /x (описанный ниже), и оба "}" и "]" являются метасимволами только при объединении с открывающим "{" или "[" соответственно; существуют и другие нюансы.
Значение каждого из них будет объяснено в остальной части учебника, но пока важно только знать, что метасимвол можно сопоставить как есть, поместив перед ним обратную косую черту:
"2+2=4" =~ /2+2/; # doesn't match, + is a metacharacter
"2+2=4" =~ /2\+2/; # matches, \+ is treated like an ordinary +
"The interval is [0,1)." =~ /[0,1)./ # is a syntax error!
"The interval is [0,1)." =~ /\[0,1\)\./ # matches
"#!/usr/bin/perl" =~ /#!\/usr\/bin\/perl/; # matches В последнем регулярном выражении обратный слэш '/' также экранируется, потому что он используется для разграничения регулярного выражения. Однако это может привести к синдрому LTS (синдром наклонённой зубочистки), и часто более читабельно изменить разделители.
"#!/usr/bin/perl" =~ m!#\!/usr/bin/perl!; # easier to read Символ обратной косой черты '\' сам по себе является метасимволом и должен экранироваться:
'C:\WIN32' =~ /C:\\WIN/; # matches В ситуациях, когда для конкретного метасимвола бессмысленно иметь то же значение, что и обычно, он автоматически теряет свою метасимвольность и становится обычным символом, который должен сопоставляться буквально. Например, '}' является метасимволом только тогда, когда он является парой метасимвола '{'. В противном случае он обрабатывается как буквальная ПРАВАЯ КРУГЛЫЕ СКОБКА. Это может привести к неожиданным результатам. use re 'strict' может поймать некоторые из них.
Помимо метасимволов, есть некоторые ASCII-символы, у которых нет эквивалентов печатных символов, и они представлены последовательностями экранирования. Типичные примеры: \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. Поскольку второе регулярное выражение точно соответствует строке, оно соответствует. Использование и '^', и '$' в регулярном выражении принуждает к соответствию всей строки, что даёт вам полный контроль над тем, какие строки соответствуют, а какие нет. Предположим, вы ищете человека по имени bert, в строке отдельно:
"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]/ Последний пример указывает на то, что классы символов похожи на альтернации символов. В данной позиции символа первым вариантом, позволяющим успешно сопоставить регулярное выражение, будет тот, который соответствует.
Группирование и иерархическое сопоставление
Альтернация позволяет регулярному выражению выбирать между вариантами, но сама по себе она неудовлетворительна. Причина в том, что каждый вариант — это целое регулярное выражение, но иногда мы хотим альтернативы только для части регулярного выражения. Например, предположим, что мы хотим найти «кошек» или «хозяек домов». Регулярное выражение 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)/ возвращает значение true или false. Однако в контексте списка он возвращает список сопоставленных значений ($1,$2,$3). Поэтому мы можем записать код более компактно как
# extract hours, minutes, seconds
($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/); Если группировки в регулярном выражении вложены, $1 получает группу с левой скобкой, $2 следующую открывающую скобку, и т. д. Вот регулярное выражение со вложенными группами:
/(ab(cd|ef)((gi)|j))/;
1 2 34 Если это регулярное выражение соответствует, то $1 содержит строку, начинающуюся с 'ab', $2 установлено либо в 'cd', либо в 'ef', $3 равно либо 'gi', либо 'j', и $4 установлено либо в 'gi', так же, как $3, либо остается неопределённым.
Для удобства Perl устанавливает $+ в строку, содержащуюся в группе с наибольшим номером $1, $2,... которая была назначена (и, отчасти связанное с этим, $^N в значение группы $1, $2,... которая была назначена последней; т. е. в значение группы $1, $2,... соответствующей правой закрывающей скобке, используемой в сопоставлении).
Обратные ссылки
Тесно связанные с переменными сопоставления $1, $2, ... являются обратные ссылки \g1, \g2, ... Обратные ссылки — это просто переменные сопоставления, которые могут использоваться внутри регулярного выражения. Это очень полезная функция; то, что сопоставляется позже в регулярном выражении, зависит от того, что было сопоставлено ранее в регулярном выражении. Предположим, мы хотели найти удвоенные слова в тексте, такие как «the the». Следующее регулярное выражение находит все 3-буквенные дубли с пробелом между ними:
/\b(\w\w\w)\s\g1\b/; Группировка присваивает значение \g1, так что одна и та же последовательность из 3 букв используется для обеих частей.
Аналогичная задача — найти слова, состоящие из двух одинаковых частей:
% simple_grep '^(\w\w\w\w|\w\w\w|\w\w|\w)\g1$' /usr/dict/words
beriberi
booboo
coco
mama
murmur
papa Регулярное выражение имеет одну группу, которая рассматривает 4-буквенные комбинации, затем 3-буквенные комбинации и т. д. и использует \g1, чтобы найти повторение. Хотя $1 и \g1 представляют одно и то же, следует быть осторожным, используя переменные сопоставления $1, $2, ... только вне регулярного выражения и обратные ссылки \g1, \g2, ... только внутри регулярного выражения; в противном случае могут возникнуть неожиданные и неудовлетворительные результаты.
Относительные обратные ссылки
Подсчет открывающих скобок для получения правильного номера обратной ссылки становится громоздким, как только появляется более одной группы захвата. С Perl 5.10 стала доступна более удобная техника: относительные обратные ссылки. Чтобы сослаться на непосредственно предыдущую группу захвата, можно использовать \g-1 или \g{-1}, предыдущую-перед-последнюю — через \g-2 или \g{-2}, и так далее.
Еще одна причина, помимо удобочитаемости и поддерживаемости, для использования относительных обратных ссылок показана на следующем примере, где используется простой шаблон для сопоставления особых строк:
$a99a = '([a-z])(\d)\g2\g1'; # matches a11a, g22g, x33x, etc. Теперь, когда у нас есть этот шаблон, сохраненный в виде удобной строки, мы можем захотеть использовать его как часть другого шаблона:
$line = "code=e99e";
if ($line =~ /^(\w+)=$a99a$/){ # unexpected behavior!
print "$1 is valid\n";
} else {
print "bad line: '$line'\n";
} Но это не соответствует, по крайней мере, не так, как можно было бы ожидать. Только после вставки интерполированного $a99a и просмотра результирующего полного текста регулярного выражения становится очевидным, что обратные ссылки не сработали. Подвыражение (\w+) захватило номер 1 и понизило группы в $a99a на один ранг. Этого можно избежать, используя относительные обратные ссылки:
$a99a = '([a-z])(\d)\g{-1}\g{-2}'; # safe for being interpolated Именованные обратные ссылки
Perl 5.10 также представил именованные группы захвата и именованные обратные ссылки. Чтобы прикрепить имя к группе захвата, вы можете написать либо (?<name>...), либо (?'name'...). Обратная ссылка затем может быть записана как \g{name}. Разрешено прикреплять одно и то же имя к нескольким группам, но тогда можно сослаться только на самую левую из одноимённых групп. Вне шаблона именованная группа захвата доступна через хеш %+.
Предположим, нам нужно сопоставить даты календаря, которые могут быть заданы в одном из трёх форматов: yyyy-mm-dd, mm/dd/yyyy или dd.mm.yyyy, мы можем написать три подходящих шаблона, где мы используем 'd', 'm' и 'y' соответственно в качестве имён групп, захватывающих соответствующие компоненты даты. Операция сопоставления объединяет три шаблона как альтернативы:
$fmt1 = '(?<y>\d\d\d\d)-(?<m>\d\d)-(?<d>\d\d)';
$fmt2 = '(?<m>\d\d)/(?<d>\d\d)/(?<y>\d\d\d\d)';
$fmt3 = '(?<d>\d\d)\.(?<m>\d\d)\.(?<y>\d\d\d\d)';
for my $d (qw(2006-10-21 15.01.2007 10/31/2005)) {
if ( $d =~ m{$fmt1|$fmt2|$fmt3} ){
print "day=$+{d} month=$+{m} year=$+{y}\n";
}
} Если любой из вариантов соответствует, хеш %+ будет содержать три пары ключ-значение.
Альтернативная нумерация групп захвата
Ещё один метод нумерации групп захвата (также начиная с Perl 5.10) решает проблему ссылки на группы в наборе альтернатив. Рассмотрим шаблон для сопоставления времени суток, в гражданском или военном стиле:
if ( $time =~ /(\d\d|\d):(\d\d)|(\d\d)(\d\d)/ ){
# process hour and minute
} Обработка результатов требует дополнительного оператора if для определения, содержат ли $1 и $2 или $3 и $4 нужные значения. Было бы проще, если бы мы могли использовать номера групп 1 и 2 во втором варианте тоже, и это именно то, что достигается с помощью скобок (?|...), помещенных вокруг альтернативы. Вот расширенная версия предыдущего шаблона:
if($time =~ /(?|(\d\d|\d):(\d\d)|(\d\d)(\d\d))\s+([A-Z][A-Z][A-Z])/){
print "hour=$1 minute=$2 zone=$3\n";
} Внутри группы нумерации альтернатив, номера групп начинаются в той же позиции для каждой альтернативы. После группы нумерация продолжается с номером, на один большим, чем максимальный достигнут по всем альтернативам.
Информация о позиции
В дополнение к сопоставленным значениям Perl также предоставляет позиции сопоставленных значений в массивах @- и @+. $-[0] — это позиция начала всего совпадения, а $+[0] — позиция конца. Аналогично, $-[n] — это позиция начала совпадения $n, а $+[n] — позиция конца. Если $n не определено, то и $-[n] и $+[n] тоже не определены. Тогда этот код
$x = "Mmm...donut, thought Homer";
$x =~ /^(Mmm|Yech)\.\.\.(donut|peas)/; # matches
foreach $exp (1..$#-) {
no strict 'refs';
print "Match $exp: '$$exp' at position ($-[$exp],$+[$exp])\n";
} выводит
Match 1: 'Mmm' at position (0,3)
Match 2: 'donut' at position (6,11) Даже если в регулярном выражении нет группировок, всё ещё можно узнать, что именно было сопоставлено в строке. Если вы их используете, Perl установит $` в часть строки перед совпадением, установит $& в часть строки, которая совпала, и установит $' в часть строки после совпадения. Пример:
$x = "the cat caught the mouse";
$x =~ /cat/; # $` = 'the ', $& = 'cat', $' = ' caught the mouse'
$x =~ /the/; # $` = '', $& = 'the', $' = ' cat caught the mouse' Во втором совпадении $` равно '', потому что регулярное выражение сопоставляется с первой позицией символа в строке и останавливается; оно никогда не видело второе «the».
Если ваш код должен работать на версиях Perl раньше 5.20, стоит отметить, что использование $` и $' значительно замедляет сопоставление регулярных выражений, в то время как $& замедляет его в меньшей степени, потому что если они используются в одном регулярном выражении в программе, они генерируются для всех регулярных выражений в программе. Таким образом, если исходная производительность является целью вашей программы, их следует избегать. Если вам нужно извлечь соответствующие подстроки, используйте @- и @+ вместо этого:
$` is the same as substr( $x, 0, $-[0] )
$& is the same as substr( $x, $-[0], $+[0]-$-[0] )
$' is the same as substr( $x, $+[0] ) Начиная с Perl 5.10, можно использовать переменные ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH}. Они устанавливаются только при наличии модификатора /p. Следовательно, они не накладывают дополнительные расходы на остальную часть программы. В Perl 5.20 переменные ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH} доступны независимо от использования модификатора /p (модификатор игнорируется), а $`, $' и $& не влияют на скорость.
Незахватывающие группировки
Группа, необходимая для объединения набора альтернатив, может или не может быть полезна как группа захвата. Если нет, она просто создает излишнее добавление к набору доступных значений групп захвата, как внутри, так и вне регулярного выражения. Незахватывающие группировки, обозначаемые (?:regexp), по-прежнему позволяют рассматривать регулярное выражение как единое целое, но не создают группу захвата. В одном регулярном выражении могут сосуществовать как захватывающие, так и незахватывающие группировки. Поскольку нет извлечения, незахватывающие группировки быстрее, чем захватывающие. Незахватывающие группировки также полезны для выбора точных частей регулярного выражения, которые нужно извлечь в переменные сопоставления:
# match a number, $1-$4 are set, but we only want $1
/([+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)/;
# match a number faster , only $1 is set
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;
# match a number, get $1 = whole number, $2 = exponent
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]([+-]?\d+))?)/; Незахватывающие группировки также полезны для удаления ненужных элементов, полученных из операции разделения, где скобки необходимы по каким-либо причинам:
$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 версии 5.26, указание /xx изменяет квадратные скобки в шаблоне, чтобы игнорировать табуляции и пробелы, если они не экранированы предшествующим обратным слэшем. Таким образом, мы можем написать
/^
[ + - ]?\ * # first, match an optional sign
( # then match integers or f.p. mantissas:
\d+ # start out with a ...
(
\.\d* # mantissa of the form a.b or a.
)? # ? takes care of integers of the form a
|\.\d+ # mantissa of the form .b
)
( [ e E ] [ + - ]? \d+ )? # finally, optionally match an exponent
$/xx; Это не очень улучшает удобочитаемость этого примера, но оно доступно, если вам это нужно. Сжимая шаблон до компактной формы, получаем
/^[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/; Это наше окончательное выражение регулярного поиска. Вкратце, мы создали выражение регулярного поиска, выполнив
-
детализацию задачи,
-
разбиение проблемы на более мелкие части,
-
перевод мелких частей в выражения регулярного поиска,
-
объединение выражений регулярного поиска,
-
и оптимизацию комбинированного выражения регулярного поиска.
Это также типичные этапы написания компьютерной программы. Это имеет смысл, потому что выражения регулярного поиска — это по существу программы, написанные на небольшом компьютерном языке, который определяет шаблоны.
Использование выражений регулярного поиска в Perl
Последняя тема части 1 кратко описывает, как выражения регулярного поиска используются в программах Perl. Где они вписываются в синтаксис Perl?
Мы уже представили оператор сопоставления в его стандартной /regexp/ и произвольной форме разделителя m!regexp!. Мы использовали оператор привязки =~ и его отрицание !~ для проверки соответствия строк. В связи с оператором сопоставления мы обсудили однострочный /s, многострочный /m, регистронезависимый /i и расширенный /x модификаторы. Есть еще несколько вещей, которые вам может понадобиться узнать об операторах сопоставления.
Запрещение подстановки
Если вы измените $pattern после первой подстановки, Perl проигнорирует её. Если вы вообще не хотите никаких подстановок, используйте специальный разделитель m'':
@pattern = ('Seuss');
while (<>) {
print if m'@pattern'; # matches literal '@pattern', not 'Seuss'
} Аналогично строкам, m'' действует как апострофы в выражении регулярного поиска; все остальные 'm' разделители действуют как кавычки. Если выражение регулярного поиска вычисляется как пустая строка, вместо него используется выражение регулярного поиска в последнем успешно выполненном сопоставлении. Поэтому у нас есть
"dog" =~ /d/; # 'd' matches
"dogbert" =~ //; # this matches the 'd' regexp used before Глобальное сопоставление
Последние два модификатора, которые мы здесь обсудим, /g и /c, относятся к множественным сопоставлениям. Модификатор /g означает глобальное сопоставление и позволяет оператору сопоставления сопоставлять в строке как можно больше раз. В скалярном контексте последовательные вызовы к строке будут иметь /g переходить от совпадения к совпадению, отслеживая позицию в строке по мере продвижения. Вы можете получить или установить позицию с помощью функции pos().
Использование /g показано в следующем примере. Предположим, у нас есть строка, состоящая из слов, разделенных пробелами. Если мы знаем, сколько слов заранее, мы можем извлечь слова с помощью группировки:
$x = "cat dog house"; # 3 words
$x =~ /^\s*(\w+)\s+(\w+)\s+(\w+)\s*$/; # matches,
# $1 = 'cat'
# $2 = 'dog'
# $3 = 'house' Но что, если у нас неопределенное количество слов? Это тот случай, для которого /g было создано. Чтобы извлечь все слова, составьте простое выражение регулярного поиска (\w+) и перебирайте все совпадения с /(\w+)/g:
while ($x =~ /(\w+)/g) {
print "Word is $1, ends at position ", pos $x, "\n";
} печатает
Word is cat, ends at position 3
Word is dog, ends at position 7
Word is house, ends at position 13 Неудачное сопоставление или изменение целевой строки сбрасывает позицию. Если вы не хотите, чтобы позиция сбрасывалась после неудачного сопоставления, добавьте /c, как в /regexp/gc. Текущая позиция в строке связана со строкой, а не с выражением регулярного поиска. Это означает, что у разных строк разные позиции, и их соответствующие позиции могут устанавливаться или считываться независимо.
В списочном контексте /g возвращает список сопоставленных групп или, если нет групп, список совпадений со всем выражением регулярного поиска. Таким образом, если нам нужны только слова, мы могли бы использовать
@words = ($x =~ /(\w+)/g); # matches,
# $words[0] = 'cat'
# $words[1] = 'dog'
# $words[2] = 'house' Тесно связанный с модификатором /g якорь \G. Якорь \G соответствует точке, где предыдущее сопоставление /g оставляет позицию. \G позволяет легко выполнять контекстно-зависимое сопоставление:
$metric = 1; # use metric units
...
$x = <FILE>; # read in measurement
$x =~ /^([+-]?\d+)\s*/g; # get magnitude
$weight = $1;
if ($metric) { # error checking
print "Units error!" unless $x =~ /\Gkg\./g;
}
else {
print "Units error!" unless $x =~ /\Glbs\./g;
}
$x =~ /\G\s+(widget|sprocket)/g; # continue processing Сочетание /g и \G позволяет обрабатывать строку по частям и использовать произвольную логику Perl, чтобы решить, что делать дальше. В настоящее время якорь \G полностью поддерживается только при использовании для привязки к началу шаблона.
\G также имеет важное значение при обработке записей фиксированной длины с помощью выражений регулярного поиска. Предположим, у нас есть фрагмент кодирующей области ДНК, закодированный как буквы пар оснований ATCGTTGAAT..., и мы хотим найти все стоп-кодоны TGA. В кодирующей области кодоны являются 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 для замены строки в кавычках только на то, что было в кавычках. С модификатором global, s///g будет искать и заменять все вхождения регулярного выражения в строке:
$x = "I batted 4 for 4";
$x =~ s/4/four/; # doesn't do it all:
# $x contains "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g; # does it all:
# $x contains "I batted four for four" Если вы предпочитаете "regex" вместо "regexp" в этом руководстве, вы можете использовать следующую программу для замены:
% cat > simple_replace
#!/usr/bin/perl
$regexp = shift;
$replacement = shift;
while (<>) {
s/$regexp/$replacement/g;
print;
}
^D
% simple_replace regexp regex perlretut.pod В simple_replace мы использовали модификатор s///g для замены всех вхождений регулярного выражения в каждой строке. (Несмотря на то, что регулярное выражение появляется в цикле, Perl достаточно умен, чтобы скомпилировать его только один раз.) Как и в случае с simple_grep, и print, и s/$regexp/$replacement/g неявно используют $_.
Если вы не хотите, чтобы s/// изменяло вашу исходную переменную, вы можете использовать модификатор неразрушающей замены s///r. Это меняет поведение так, что s///r возвращает окончательно заменённую строку (вместо количества замен):
$x = "I like dogs.";
$y = $x =~ s/dogs/cats/r;
print "$x $y\n"; Этот пример напечатает «I like dogs. I like cats». Обратите внимание, что исходная переменная $x не была затронута. Вместо этого общий результат замены хранится в $y. Если замена ничего не изменила, то возвращается исходная строка:
$x = "I like dogs.";
$y = $x =~ s/elephants/cougars/r;
print "$x $y\n"; # prints "I like dogs. I like dogs." Ещё одна интересная возможность, предоставляемая флагом s///r, — цепочка замен:
$x = "Cats are great.";
print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~
s/Frogs/Hedgehogs/r, "\n";
# prints "Hedgehogs are great." Модификатор, доступный специально для поиска и замены, — это модификатор вычисления s///e. s///e обрабатывает текст замены как код Perl, а не строку в двойных кавычках. Возвращаемое значение кода подставляется вместо совпавшей подстроки. s///e полезно, если нужно выполнить немного вычислений в процессе замены текста. В этом примере подсчитываются частоты символов в строке:
$x = "Bill the cat";
$x =~ s/(.)/$chars{$1}++;$1/eg; # final $1 replaces char with itself
print "frequency of '$_' is $chars{$_}\n"
foreach (sort {$chars{$b} <=> $chars{$a}} keys %chars); Это выведет:
frequency of ' ' is 2
frequency of 't' is 2
frequency of 'l' is 2
frequency of 'B' is 1
frequency of 'c' is 1
frequency of 'e' is 1
frequency of 'h' is 1
frequency of 'i' is 1
frequency of 'a' is 1 Как и в случае с оператором сопоставления m//, s/// может использовать другие разделители, такие как s!!! и s{}{}, и даже s{}//. Если используются одинарные кавычки s''', то регулярное выражение и замена обрабатываются как строки в одинарных кавычках, и подстановок переменных не происходит. s/// в контексте списка возвращает то же, что и в скалярном контексте, т.е., количество совпадений.
Функция split
Функция split() — ещё одно место, где используется регулярное выражение. split /regexp/, string, limit разделяет операнд string на список подстрок и возвращает этот список. Регулярное выражение должно быть разработано так, чтобы соответствовать тому, что образует разделители для желаемых подстрок. limit, если присутствует, ограничивает разделение не более чем limit количеством строк. Например, для разделения строки на слова используйте
$x = "Calvin and Hobbes";
@words = split /\s+/, $x; # $word[0] = 'Calvin'
# $word[1] = 'and'
# $word[2] = 'Hobbes' Если используется пустое регулярное выражение //, регулярное выражение всегда совпадает, и строка разделяется на отдельные символы. Если регулярное выражение содержит группировки, то результирующий список содержит сопоставленные подстроки из группировок. Например,
$x = "/usr/bin/perl";
@dirs = split m!/!, $x; # $dirs[0] = ''
# $dirs[1] = 'usr'
# $dirs[2] = 'bin'
# $dirs[3] = 'perl'
@parts = split m!(/)!, $x; # $parts[0] = ''
# $parts[1] = '/'
# $parts[2] = 'usr'
# $parts[3] = '/'
# $parts[4] = 'bin'
# $parts[5] = '/'
# $parts[6] = 'perl' Поскольку первый символ $x соответствовал регулярному выражению, split добавил в начало списка пустой элемент.
Если вы дочитали до этого места, поздравляем! Теперь у вас есть все основные инструменты, необходимые для использования регулярных выражений для решения широкого спектра задач обработки текста. Если это ваш первый раз в руководстве, почему бы не остановиться здесь и немного поэкспериментировать с регулярными выражениями... Часть 2 касается более эзотерических аспектов регулярных выражений, и эти концепции определённо не нужны сразу.
Часть 2: Расширенные инструменты
Хорошо, вы знаете основы регулярных выражений и хотите узнать больше. Если сопоставление регулярных выражений аналогично прогулке по лесу, то инструменты, обсуждавшиеся в части 1, аналогичны топографическим картам и компасу, базовым инструментам, которые мы используем постоянно. Большинство инструментов во второй части аналогичны сигнальным ракетам и спутниковым телефонам. Ими не так часто пользуются в походе, но когда мы застряли, они могут быть бесценными.
В дальнейшем будут рассмотрены более сложные, менее используемые или иногда эзотерические возможности регулярных выражений Perl. Во второй части мы будем исходить из того, что вы знакомы с основами, и сосредоточимся на расширенных функциях.
Подробнее о символах, строках и классах символов
Существует ряд управляющих последовательностей и классов символов, которые мы ещё не рассматривали.
Есть несколько управляющих последовательностей, которые преобразуют символы или строки между верхним и нижним регистром, и они также доступны в шаблонах. \l и \u преобразуют следующий символ в нижний или верхний регистр соответственно:
$x = "perl";
$string =~ /\u$x/; # matches 'Perl' in $string
$x = "M(rs?|s)\\."; # note the double backslash
$string =~ /\l$x/; # matches 'mr.', 'mrs.', and 'ms.', \L или \U указывают на постоянное преобразование регистра, пока оно не будет прервано \E или перекрыто другим \U или \L.
$x = "This word is in lower case:\L SHOUT\E";
$x =~ /shout/; # matches
$x = "I STILL KEYPUNCH CARDS FOR MY 360";
$x =~ /\Ukeypunch/; # matches punch card string Если \E отсутствует, регистр преобразуется до конца строки. Регулярные выражения \L\u$word или \u\L$word преобразуют первый символ $word в верхний регистр, а остальные символы — в нижний регистр. (За пределами ASCII-символов это становится несколько сложнее; \u фактически выполняет преобразование в заголовок, которое для большинства символов совпадает с верхним регистром, но не для всех; см. https://unicode.org/faq/casemap_charprop.html#4.)
Управляющие символы можно экранировать с помощью \c, поэтому символ управления-Z будет сопоставлен с \cZ. Управляющая последовательность \Q...\E помещает в кавычки или защищает большинство неалфавитных символов. Например,
$x = "\QThat !^*&%~& cat!";
$x =~ /\Q!^*&%~&\E/; # check for rough language Она не защищает '$' или '@', так что переменные по-прежнему могут быть заменены.
\Q, \L, \l, \U, \u и \E фактически являются частью синтаксиса двойных кавычек, а не частью синтаксиса регулярных выражений. Они будут работать, если они появятся в регулярном выражении, встроенном непосредственно в программу, но не когда они содержатся в строке, которая интерполируется в шаблоне.
Регулярные выражения Perl могут обрабатывать не только стандартный набор символов ASCII. Perl поддерживает Unicode, стандарт для представления алфавитов практически всех письменных языков мира, и множество символов. Строки Perl — это строки Unicode, поэтому они могут содержать символы со значением (кодовой точкой или номером символа) больше, чем 255.
Что это значит для регулярных выражений? Ну, пользователи регулярных выражений не должны знать много о внутреннем представлении строк в Perl. Но они должны знать 1) как представлять символы Unicode в регулярном выражении и 2) что операция сопоставления будет рассматривать строку для поиска как последовательность символов, а не байтов. Ответ на 1) заключается в том, что символы Unicode, большие, чем chr(255), представляются с помощью обозначения \x{hex}, потому что \xXY (без фигурных скобок и XY — две шестнадцатеричные цифры) не простирается дальше 255. (Начиная с Perl 5.14, если вы поклонник восьмеричной системы, вы также можете использовать \o{oct}. )
/\x{263a}/; # match a Unicode smiley face :)
/\x{ 263a }/; # Same ПРИМЕЧАНИЕ: В Perl 5.6.0 раньше требовалось написать use utf8, чтобы использовать любые функции Unicode. Это больше не так: для почти всех операций с Unicode явное использование псевдонима utf8 не требуется. (Единственный случай, когда это имеет значение, — если ваш скрипт Perl находится в Unicode и закодирован в UTF-8, тогда явное указание use utf8 необходимо.)
Выяснение шестнадцатеричной последовательности нужного символа Unicode или расшифровка шестнадцатеричного Unicode-регулярного выражения кого-то ещё — примерно такая же забава, как программирование на машинном коде. Поэтому другой способ указать символы Unicode — использовать управляющую последовательность именованного символа \N{name}. name — это имя символа Unicode, как указано в стандарте Unicode. Например, если мы хотели представить или сопоставить астрологический знак для планеты Меркурий, мы могли бы использовать
$x = "abc\N{MERCURY}def";
$x =~ /\N{MERCURY}/; # matches
$x =~ /\N{ MERCURY }/; # Also matches Можно также использовать сокращенные имена:
print "\N{GREEK SMALL LETTER SIGMA} is called sigma.\n";
print "\N{greek:Sigma} is an upper-case sigma.\n"; Вы также можете ограничить имена определённым алфавитом, указав псевдоним charnames:
use charnames qw(greek);
print "\N{sigma} is Greek sigma\n"; Список имен символов доступен в Интернете от консорциума Unicode по адресу https://www.unicode.org/charts/charindex.html; разъяснительные материалы со ссылками на другие ресурсы по адресу https://www.unicode.org/standard/where.
Начиная с Perl v5.32, существует альтернатива \N{...} для полных имён, и это запись
/\p{Name=greek small letter sigma}/ Регистр имени символа не имеет значения, когда используется в \p{}, как и большинство пробелов, подчёркиваний и дефисов. (Некоторые исключительные символы вызывают проблемы с игнорированием всех их всегда. Подробности (которые вы можете найти, когда станете более опытными, и если это когда-нибудь потребуется) см. в https://www.unicode.org/reports/tr44/tr44-24.html#UAX44-LM2).
Ответ на требование 2) заключается в том, что регулярное выражение (в основном) использует символы Unicode. «В основном» из-за проблем обратной совместимости, но начиная с Perl 5.14, любое регулярное выражение, скомпилированное в области действия use feature 'unicode_strings' (которое автоматически включается в области действия use v5.12 или выше), превратит это «в основном» в «всегда». Если вы хотите должным образом обработать Unicode, вы должны убедиться, что 'unicode_strings' включено. Внутренне это кодируется в байты с использованием UTF-8 или собственного кодирования с 8 битами, в зависимости от истории строки, но концептуально это последовательность символов, а не байтов. См. perlunitut для руководства по этому вопросу.
Теперь давайте обсудим классы Unicode-символов, чаще всего называемые «свойствами символов». Они представлены управляющей последовательностью \p{name}. Отрицание этого — \P{name}. Например, чтобы сопоставить символы строчных и прописных букв,
$x = "BOB";
$x =~ /^\p{IsUpper}/; # matches, uppercase char class
$x =~ /^\P{IsUpper}/; # doesn't match, char class sans uppercase
$x =~ /^\p{IsLower}/; # doesn't match, lowercase char class
$x =~ /^\P{IsLower}/; # matches, char class sans lowercase («Is» необязательно.)
Существует множество свойств символов Юникода. Полный список см. на странице 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:], где name — имя класса POSIX. Классы POSIX — alpha, alnum, ascii, cntrl, digit, graph, lower, print, punct, space, upper, и xdigit, а также два расширения, word (расширение Perl для соответствия \w) и blank (расширение GNU). Модификатор /a ограничивает их соответствием только в диапазоне ASCII; в противном случае они могут соответствовать так же, как и соответствующие классы Unicode Perl: [:upper:] эквивалентен \p{IsUpper}, и т. д. (Есть некоторые исключения и нюансы; подробнее см. perlrecharclass.) [:digit:], [:word:], и [:space:] соответствуют знакомым \d, \w, и \s классам символов. Для отрицания класса POSIX поместите '^' перед именем, так что, например, [:^digit:] соответствует \D и, в Юникоде, \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 проверяет и вперёд, и назад, чтобы убедиться, что символы с обеих сторон различаются по своему «словесному» характеру.
Утверждения lookahead и lookbehind являются обобщениями концепции якорей. Утверждения lookahead и lookbehind — это утверждения нулевой ширины, которые позволяют указать, какие символы мы хотим проверить. Утверждение lookahead обозначается (?=regexp) или (начиная с версии 5.32, экспериментально в 5.28) (*pla:regexp) или (*positive_lookahead:regexp); а утверждение lookbehind обозначается (?<=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 Обратите внимание, что скобки в этих выражениях являются незахватывающими, поскольку это утверждения нулевой ширины. Таким образом, во втором регулярном выражении подстроки, которые захватываются, — это сами подстроки всего регулярного выражения. Lookahead может соответствовать произвольным регулярным выражениям, но lookbehind до версии 5.30 (?<=fixed-regexp) работает только с регулярными выражениями фиксированной ширины, т.е. фиксированного количества символов. Таким образом, (?<=(ab|bc)) подходит, но (?<=(ab)*) до версии 5.30 не подходит.
Отрицательные версии утверждений lookahead и lookbehind обозначаются (?!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'). Вторая форма — это просто утверждение нулевой ширины (?...), будь то lookahead, lookbehind или утверждение кода (обсуждаемое в следующей секции). Третий набор форм предоставляет тесты, которые возвращают 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 Lookbehind condition позволяет, наряду со ссылками на предыдущие части, влиять на более поздние части совпадения. Например,
/[ATGC]+(?(?<=AA)G|C)$/; соответствует последовательности ДНК, которая либо заканчивается AAG, либо на сочетании других пар оснований и 'C'. Обратите внимание, что используется форма (?(?<=AA)G|C), а не (?((?<=AA))G|C); для утверждений lookahead, lookbehind или кода скобки вокруг условного выражения не нужны.
Определение именованных шаблонов
Некоторые регулярные выражения используют одинаковые подшаблоны в нескольких местах. Начиная с 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; }))+$/ что показывает, что пробелы всё ещё возможны в частях кода. Тем не менее, при работе с кодом и условными выражениями расширенная форма регулярных выражений почти необходима при создании и отладке регулярных выражений.
Backtracking control verbs
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 } ); значительно медленнее.)
Pragmas and debugging
Говоря об отладке, в Perl доступны несколько прагм для управления и отладки регулярных выражений. Мы уже встречались с одной прагмой в предыдущем разделе, use re 'eval';, которая позволяет переменной интерполяции и выражениям кода сосуществовать в регулярном выражении. Другие прагмы это
use re 'taint';
$tainted = <>;
@parts = ($tainted =~ /(\w+)\s+(\w+)/; # @parts is now tainted Прагма taint приводит к тому, что любые подстроки из совпадения с изменённой переменной также будут изменены, если ваш Perl поддерживает изменённые переменные (см. perlsec). Обычно это не так, так как регулярные выражения часто используются для извлечения безопасных частей из изменённой переменной. Используйте taint при отсутствии извлечения безопасных частей, но выполнении другой обработки. Прагмы taint и eval имеют лексический объем, что означает, что они действуют только до конца блока, содержащего прагмы.
use re '/m'; # or any other flags
$multiline_string =~ /^foo/; # /m is implied Прагма re '/flags' (введена в Perl 5.14) включает указанные флаги регулярного выражения до конца лексического объёма. См. "'/flags' режим" в re для получения более подробной информации.
use re 'debug';
/^(.*)$/s; # output debugging info
use re 'debugcolor';
/^(.*)$/s; # output debugging info in living color Глобальные прагмы debug и debugcolor позволяют получить подробную отладочную информацию о компиляции и выполнении регулярных выражений. debugcolor аналогична debug, за исключением того, что отладочная информация отображается в цвете на терминалах, способных отображать цветовые последовательности termcap. Вот пример вывода:
% perl -e 'use re "debug"; "abc" =~ /a*b+c/;'
Compiling REx 'a*b+c'
size 9 first at 1
1: STAR(4)
2: EXACT <a>(0)
4: PLUS(7)
5: EXACT <b>(0)
7: EXACT <c>(9)
9: END(0)
floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0
Matching REx 'a*b+c' against 'abc'
Setting an EVAL scope, savestack=3
0 <> <abc> | 1: STAR
EXACT <a> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
1 <a> <bc> | 4: PLUS
EXACT <b> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
2 <ab> <c> | 7: EXACT <c>
3 <abc> <> | 9: END
Match successful!
Freeing REx: 'a*b+c' Если вы дошли до этого момента в руководстве, вы, вероятно, можете догадаться, что разные части отладочного вывода вам сообщают. Первая часть
Compiling REx 'a*b+c'
size 9 first at 1
1: STAR(4)
2: EXACT <a>(0)
4: PLUS(7)
5: EXACT <b>(0)
7: EXACT <c>(9)
9: END(0) описывает этап компиляции. STAR(4) означает, что есть звездообразный объект, в данном случае 'a', и если он совпадает, перейти к строке 4, т.е., PLUS(7). Средние строки описывают некоторые эвристики и оптимизации, выполненные перед совпадением:
floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0 Затем выполняется совпадение, а оставшиеся строки описывают процесс:
Matching REx 'a*b+c' against 'abc'
Setting an EVAL scope, savestack=3
0 <> <abc> | 1: STAR
EXACT <a> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
1 <a> <bc> | 4: PLUS
EXACT <b> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
2 <ab> <c> | 7: EXACT <c>
3 <abc> <> | 9: END
Match successful!
Freeing REx: 'a*b+c' Каждый шаг имеет вид n <x> <y>, где <x> - часть строки, которая совпала, а <y> - часть, которая ещё не совпала. | 1: STAR указывает, что Perl находится на строке 1 в списке компиляции выше. См. "Отладка регулярных выражений" в perldebguts для получения более подробной информации.
Альтернативный метод отладки регулярных выражений заключается во встраивании операторов print в регулярное выражение. Это предоставляет подробный отчёт о возврате назад в альтернации:
"that this" =~ m@(?{print "Start at position ", pos, "\n";})
t(?{print "t1\n";})
h(?{print "h1\n";})
i(?{print "i1\n";})
s(?{print "s1\n";})
|
t(?{print "t2\n";})
h(?{print "h2\n";})
a(?{print "a2\n";})
t(?{print "t2\n";})
(?{print "Done at position ", pos, "\n";})
@x; выводит
Start at position 0
t1
h1
t2
h2
a2
t2
Done at position 4 SEE ALSO
Это всего лишь руководство. Для получения полной информации о регулярных выражениях Perl см. страницу справки по регулярным выражениям perlre.
Для получения дополнительной информации об операторах сопоставления m// и замены s/// см. "Операторы типа регулярных выражений" в perlop. Для получения информации об операции split см. "split" в perlfunc.
Для получения превосходного универсального ресурса по уходу и уходу за регулярными выражениями см. книгу Mastering Regular Expressions Джеффри Фридла (издана O'Reilly, ISBN 1556592-257-3).
AUTHOR AND COPYRIGHT
Copyright (c) 2000 Mark Kvale. Все права защищены. Сейчас поддерживается Perl-разработчиками.
Этот документ можно распространять на тех же условиях, что и сам Perl.
Acknowledgments
Вдохновение для примера кодона-стоп-кодона ДНК было взято из примера почтового индекса в главе 7 книги Mastering Regular Expressions.
Автор хотел бы поблагодарить Джеффа Пиньяна, Эндрю Джонсона, Питера Хоуорта, Рональда Дж. Кимболла и Джо Смита за все их полезные замечания.
© 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.36.0/perlretut