perlretut
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ОПИСАНИЕ
- Часть 1: Основы
- Простое сопоставление слов
- Использование классов символов
- Сопоставление того или иного
- Группировка элементов и иерархическое сопоставление
- Извлечение совпадений
- Обратные ссылки
- Относительные обратные ссылки
- Именованные обратные ссылки
- Альтернативная нумерация групп захвата
- Информация о позиции
- Незахватывающие группировки
- Сопоставление повторений
- Позиционные квантификаторы
- Создание регулярного выражения
- Использование регулярных выражений в Perl
- Часть 2: Расширенные инструменты
- Подробнее о символах, строках и классах символов
- Компиляция и сохранение регулярных выражений
- Составление регулярных выражений во время выполнения
- Встраивание комментариев и модификаторов в регулярное выражение
- Просмотр вперед и назад
- Использование независимых подвыражений для предотвращения возврата
- Условные выражения
- Определение именованных шаблонов
- Рекурсивные шаблоны
- Немного магии: выполнение кода Perl в регулярном выражении
- Глаголы управления возвратом
- Прагмы и отладка
- СМОТРИТЕ ТАКЖЕ
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlretut - Руководство по регулярным выражениям Perl
ОПИСАНИЕ
Эта страница предоставляет базовое руководство по пониманию, созданию и использованию регулярных выражений в Perl. Она служит дополнением к справочной странице по регулярным выражениям perlre. Регулярные выражения являются неотъемлемой частью m//, s///, qr// и split операторов, поэтому это руководство также перекликается с "Regexp Quote-Like Operators" в perlop и "split" в perlfunc.
Perl широко известен своей эффективностью в обработке текста, и регулярные выражения являются одной из основных причин этой известности. Регулярные выражения Perl демонстрируют эффективность и гибкость, неизвестные большинству других компьютерных языков. Овладение даже основами регулярных выражений позволит вам манипулировать текстом с удивительной лёгкостью.
Что такое регулярное выражение? В самом простом случае, регулярное выражение представляет собой шаблон, который используется для определения наличия определённых характеристик в строке. Строка чаще всего представляет собой текст, такой как строка, предложение, веб-страница или даже целая книга, но реже это могут быть и двоичные данные. Предположим, мы хотим определить, содержит ли текст в переменной $var последовательность символов m u s h r o o m (пробелы добавлены для читабельности). Мы можем написать в Perl
$var =~ m/mushroom/ Значение этого выражения будет TRUE, если $var содержит эту последовательность символов, и FALSE в противном случае. Часть, заключённая в '/' символы, обозначает характеристику, которую мы ищем. Мы используем термин шаблон для неё. Процесс проверки наличия шаблона в строке называется сопоставлением, а "=~" оператор вместе с m// сообщают Perl о попытке сопоставить шаблон со строкой. Обратите внимание, что шаблон также является строкой, но очень специального типа, как мы увидим. Шаблоны широко используются в наши дни; примерами являются шаблоны, вводимые в поисковой системе для поиска веб-страниц и шаблоны для перечисления файлов в каталоге, например, "ls *.txt" или "dir *.*". В Perl шаблоны, описанные регулярными выражениями, используются не только для поиска строк, но и для извлечения нужных частей строк, а также для поиска и замены.
Регулярные выражения имеют незаслуженную репутацию абстрактных и трудных для понимания. Это связано просто с тем, что обозначение, используемое для их выражения, имеет тенденцию быть кратким и плотным, а не из-за внутренней сложности. Мы рекомендуем использовать /x модификатор регулярных выражений (описанный ниже) вместе с достаточным количеством пробелов, чтобы сделать их менее плотными и более читабельными. Регулярные выражения строятся с использованием простых понятий, таких как условные операторы и циклы, и не сложнее для понимания, чем соответствующие if условные операторы и while циклы в языке Perl.
Это руководство упрощает процесс обучения, рассматривая понятия регулярных выражений вместе с их обозначениями по одному и со многими примерами. Первая часть руководства будет переходить от самых простых поисков слов к основным понятиям регулярных выражений. Если вы освоите первую часть, у вас будут все инструменты, необходимые для решения примерно 98% ваших задач. Вторая часть руководства предназначена для тех, кто знаком с основами и хочет расширить свои навыки. Она рассматривает более сложные операторы регулярных выражений и представляет последние инновации.
Примечание: для экономии времени «регулярное выражение» часто сокращается до regexp или regex. Regexp – более естественное сокращение, но его труднее произносить. Документация Perl pod разделена поровну на regexp vs regex; в Perl существует более одного способа сокращения. В этом руководстве мы будем использовать regexp.
Нововведение в версии 5.22, use re 'strict' применяет более строгие правила, чем обычно, при компиляции шаблонов регулярных выражений. Он может находить вещи, которые, хотя и являются законными, могут не соответствовать вашим намерениям.
Часть 1: Основы
Простое сопоставление слов
Самое простое регулярное выражение – это просто слово или, более обще, строка символов. Регулярное выражение, состоящее только из слова, соответствует любой строке, содержащей это слово:
"Hello World" =~ /World/; # matches О чем говорит это утверждение Perl? "Hello World" — это простая строка в двойных кавычках. World — это регулярное выражение, а // окружающие /World/ указывают Perl на поиск соответствия в строке. Оператор =~ связывает строку с соответствием регулярного выражения и возвращает значение true, если регулярное выражение соответствует, или false, если регулярное выражение не соответствует. В нашем случае 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 matching '{}'
"/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. Поскольку второе регулярное выражение точно соответствует строке, оно совпадает. Использование '^' и '$' в регулярном выражении заставляет всю строку совпадать, поэтому это даёт вам полный контроль над тем, какие строки соответствуют, а какие нет. Предположим, вы ищете человека по имени берт, находящегося в строке сам по себе:
"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 имеет несколько сокращений для общих классов символов, как показано ниже. С появлением Юникода, если модификатор /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. Это полезно для предотвращения ненужного воздействия на всю систему Юникода (и сопутствующие соображения по безопасности) в случаях, когда вам нужно обработать текст на английском языке. (Символ "a" может быть удвоен, /aa, для ещё большей защиты, предотвращая сопоставление без учёта регистра ASCII и не-ASCII символов; в противном случае Юникод "Знак Кельвина" без учёта регистра сопоставляется с "k" или "K").
Сокращения \d\s\w\D\S\W можно использовать как внутри, так и вне квадратных скобок в классах символов. Вот несколько примеров их использования:
/\d\d:\d\d:\d\d/; # matches a hh:mm:ss time format
/[\d\s]/; # matches any digit or whitespace character
/\w\W\w/; # matches a word char, followed by a
# non-word char, followed by a word char
/..rt/; # matches any two chars, followed by 'rt'
/end\./; # matches 'end.'
/end[.]/; # same thing, matches 'end.' Поскольку точка является метасимволом, её необходимо экранировать, чтобы сопоставить обычную точку. Например, \d и \w являются наборами символов, поэтому неправильно считать [^\d\w] как [\D\W]; фактически [^\d\w] равно [^\w], что равно [\W]. Подумайте о законах Де Моргана.
На самом деле, точка и сокращения \d\s\w\D\S\W сами по себе являются типами классов символов, поэтому те, что заключены в квадратные скобки, — это просто один тип класса символов. Когда нам нужно сделать различие, мы называем их «классами символов в квадратных скобках».
Полезным якорем в базовых регулярных выражениях является якорь слова \b. Он соответствует границе между символом слова и несимволом слова \w\W или \W\w:
$x = "Housecat catenates house and cat";
$x =~ /cat/; # matches cat in 'housecat'
$x =~ /\bcat/; # matches cat in 'catenates'
$x =~ /cat\b/; # matches cat in 'housecat'
$x =~ /\bcat\b/; # matches 'cat' at end of string Обратите внимание, что в последнем примере конец строки считается границей слова.
Для обработки естественного языка (чтобы, например, апострофы включались в слова), используйте вместо этого \b{wb}
"don't" =~ / .+? \b{wb} /x; # matches the whole string Вы можете задаться вопросом, почему '.' соответствует всему, кроме "\n" — а не каждому символу? Причина в том, что часто сопоставление происходит с линиями, и вы хотите игнорировать символы новой строки. Например, хотя строка "\n" представляет одну строку, мы хотели бы считать её пустой. Тогда
"" =~ /^$/; # matches
"\n" =~ /^$/; # matches, $ anchors before "\n"
"" =~ /./; # doesn't match; it needs a char
"" =~ /^.$/; # doesn't match; it needs a char
"\n" =~ /^.$/; # doesn't match; it needs a char other than "\n"
"a" =~ /^.$/; # matches
"a\n" =~ /^.$/; # matches, $ anchors before "\n" Это поведение удобно, потому что мы обычно хотим игнорировать новые строки при подсчёте и поиске символов в строке. Однако иногда мы хотим отслеживать новые строки. Мы даже можем захотеть '^' и '$' привязаться к началу и концу строк внутри строки, а не только к началу и концу всей строки. Perl позволяет нам выбирать, игнорировать или учитывать новые строки с помощью модификаторов /s и /m . /s и /m обозначают одинарную строку и многострочный режим, и они определяют, будет ли строка обрабатываться как одна непрерывная строка или как набор строк. Два модификатора влияют на два аспекта интерпретации регулярного выражения: 1) как определяется класс символов '.', и 2) где якоря '^' и '$' могут сопоставляться. Вот четыре возможных комбинации:
-
Без модификаторов: По умолчанию.
'.'соответствует любому символу, кроме"\n".'^'соответствует только началу строки, а'$'— только концу или перед символом новой строки в конце. -
Модификатор s (
/s): Строка обрабатывается как одна длинная строка.'.'соответствует любому символу, даже"\n".'^'соответствует только началу строки, а'$'— только концу или перед символом новой строки в конце. -
Модификатор m (
/m): Строка обрабатывается как набор строк.'.'соответствует любому символу, кроме"\n".'^'и'$'могут соответствовать началу или концу любой строки внутри строки. -
Оба модификатора s и m (
/sm): Строка обрабатывается как одна длинная строка, но обнаруживаются несколько строк.'.'соответствует любому символу, даже"\n".'^'и'$', однако, могут соответствовать началу или концу любой строки внутри строки.
Вот примеры работы модификаторов /s и /m:
$x = "There once was a girl\nWho programmed in Perl\n";
$x =~ /^Who/; # doesn't match, "Who" not at start of string
$x =~ /^Who/s; # doesn't match, "Who" not at start of string
$x =~ /^Who/m; # matches, "Who" at start of second line
$x =~ /^Who/sm; # matches, "Who" at start of second line
$x =~ /girl.Who/; # doesn't match, "." doesn't match "\n"
$x =~ /girl.Who/s; # matches, "." matches "\n"
$x =~ /girl.Who/m; # doesn't match, "." doesn't match "\n"
$x =~ /girl.Who/sm; # matches, "." matches "\n" Большинство раз, поведение по умолчанию является желаемым, но /s и /m иногда очень полезны. Если используется модификатор /m, начало строки всё ещё может быть сопоставлено с \A, а конец строки всё ещё может быть сопоставлен с якорями \Z (сопоставляет как конец, так и символ новой строки перед ним, как '$'), и \z (сопоставляет только конец):
$x =~ /^Who/m; # matches, "Who" at start of second line
$x =~ /\AWho/m; # doesn't match, "Who" is not at start of string
$x =~ /girl$/m; # matches, "girl" at end of first line
$x =~ /girl\Z/m; # doesn't match, "girl" is not at end of string
$x =~ /Perl\Z/m; # matches, "Perl" is at newline before end
$x =~ /Perl\z/m; # doesn't match, "Perl" is not at end of string Теперь мы знаем, как создавать выбор среди классов символов в регулярном выражении. А как насчёт выбора среди слов или строк символов? Такие выборы описаны в следующем разделе.
Сопоставление того или другого
Иногда мы хотим, чтобы наше регулярное выражение могло сопоставлять разные возможные слова или строки символов. Это достигается с помощью метасимвола альтернации '|'. Чтобы сопоставить dog или cat, мы формируем регулярное выражение dog|cat. Как и прежде, Perl попытается сопоставить регулярное выражение в самой ранней возможной точке в строке. В каждой позиции символа Perl сначала попытается сопоставить первый вариант, dog. Если dog не соответствует, Perl затем попробует следующий вариант, cat. Если cat тоже не соответствует, то сопоставление не выполняется, и Perl переходит к следующей позиции в строке. Вот несколько примеров:
"cats and dogs" =~ /cat|dog|bird/; # matches "cat"
"cats and dogs" =~ /dog|cat|bird/; # matches "cat" Несмотря на то, что dog является первым вариантом во втором регулярном выражении, cat может соответствовать раньше в строке.
"cats" =~ /c|ca|cat|cats/; # matches "c"
"cats" =~ /cats|cat|ca|c/; # matches "cats" Здесь все варианты соответствуют в первой позиции строки, поэтому первый вариант — это тот, который соответствует. Если некоторые варианты являются сокращениями других, поместите самые длинные варианты в начало, чтобы дать им шанс соответствовать.
"cab" =~ /a|b|c/ # matches "c"
# /a|b|c/ == /[abc]/ Последний пример показывает, что классы символов подобны альтернациям символов. В данной позиции символа, первый вариант, который позволяет регулярному выражению успешно соответствовать, будет тем, который соответствует.
Группировка элементов и иерархическое сопоставление
Альтернация позволяет регулярному выражению выбирать среди вариантов, но сама по себе она неудовлетворительна. Причина в том, что каждый вариант является целым регулярным выражением, но иногда мы хотим альтернатив только для части регулярного выражения. Например, предположим, что мы хотим найти housecats или housekeepers. Регулярное выражение housecat|housekeeper подходит для этой задачи, но неэффективно, потому что нам пришлось написать house дважды. Было бы неплохо, если бы части регулярного выражения были постоянными, например, house, а некоторые части имели альтернативы, например, cat|keeper.
Метасимволы группирования () решают эту проблему. Группирование позволяет рассматривать части регулярного выражения как единое целое. Части регулярного выражения группируются, заключая их в скобки. Таким образом, мы могли бы решить задачу housecat|housekeeper, сформировав регулярное выражение как house(cat|keeper). Регулярное выражение house(cat|keeper) означает сопоставить house с последующим либо cat, либо keeper. Вот ещё несколько примеров
/(a|b)b/; # matches 'ab' or 'bb'
/(ac|b)b/; # matches 'acb' or 'bb'
/(^a|b)c/; # matches 'ac' at start of string or 'bc' anywhere
/(a|[bc])d/; # matches 'ad', 'bd', or 'cd'
/house(cat|)/; # matches either 'housecat' or 'house'
/house(cat(s|)|)/; # matches either 'housecats' or 'housecat' or
# 'house'. Note groups can be nested.
/(19|20|)\d\d/; # match years 19xx, 20xx, or the Y2K problem, xx
"20" =~ /(19|20|)\d\d/; # matches the null alternative '()\d\d',
# because '20\d\d' can't match Альтернации ведут себя одинаково в группах и вне их: в данной позиции строки, левый альтернативный вариант, который позволяет регулярному выражению соответствовать, и будет выбран. Таким образом, в последнем примере в первой позиции строки "20" соответствует второму варианту, но ничего не осталось для соответствия следующим двум цифрам \d\d. Поэтому Perl переходит к следующему варианту, который является пустым вариантом, и это работает, так как "20" — это две цифры.
Процесс попытки одного варианта, проверки его соответствия и перехода к следующему варианту, а при необходимости возврата в строку от места, где был опробован предыдущий вариант, если он не соответствует, называется повторным отслеживанием. Термин «повторное отслеживание» происходит от идеи, что сопоставление регулярного выражения похоже на прогулку в лесу. Успешное сопоставление регулярного выражения — это, как достижение пункта назначения. Есть много возможных начальных точек, одна на каждую позицию строки, и они пробуются в порядке слева направо. Из каждой начальной точки может быть много путей, некоторые из которых ведут к цели, а некоторые — тупиковые. Когда вы идёте по пути и попадаете в тупик, вам нужно вернуться по пути к более ранней точке, чтобы попробовать другой путь. Если вы достигнете вашей цели, вы сразу остановитесь и забудете о попытке всех остальных путей. Вы упорны, и только если вы попробовали все пути со всех начальных точек и не достигли вашей цели, вы объявляете об ошибке. Для большей конкретности, вот пошаговый анализ того, что делает Perl, когда пытается сопоставить регулярное выражение
"abcde" =~ /(abd|abc)(df|d|de)/; -
0. Начать с первой буквы в строке
'a'. -
1. Попробовать первый вариант в первой группе
'abd'. -
2. Сопоставить
'a'с последующим'b'. Пока всё хорошо. -
3.
'd'в регулярном выражении не соответствует'c'в строке — тупик. Поэтому откатиться на две позиции и выбрать второй вариант в первой группе'abc'. -
4. Сопоставить
'a'с последующим'b'с последующим'c'. Мы на верном пути и удовлетворили первую группу. Установить$1на'abc'. -
5. Перейти ко второй группе и выбрать первый вариант
'df'. -
6. Сопоставить
'd'. -
7.
'f'в регулярном выражении не соответствует'e'в строке, поэтому тупик. Откатиться на одну позицию и выбрать второй вариант во второй группе'd'. -
8.
'd'соответствует. Вторая группа удовлетворена, поэтому установить$2на'd'. -
9. Мы в конце регулярного выражения, значит всё готово! Мы сопоставили
'abcd'из строки"abcde".
Следует обратить внимание на несколько моментов в данном анализе. Во-первых, третий вариант во второй группе 'de' также допускает совпадение, но мы остановились раньше, чем дошли до него — в данной позиции символа, левее всех, побеждает. Во-вторых, нам удалось получить совпадение в первой позиции символа строки 'a'. Если совпадений не было в первой позиции, Perl перейдёт ко второй позиции символа 'b' и повторит попытку совпадения. Только когда будут исчерпаны все возможные пути во всех возможных позициях символов, Perl сдаётся и объявляет $string =~ /(abd|abc)(df|d|de)/; ложным.
Даже с учётом всей этой работы, сопоставление с регулярным выражением происходит очень быстро. Для ускорения процесса Perl компилирует регулярное выражение в компактную последовательность операционных кодов, которые часто помещаются в кэш процессора. При выполнении кода эти операционные коды могут работать на полную мощность и осуществлять поиск очень быстро.
Извлечение совпадений
Метасимволы группировки () также выполняют ещё одну совершенно иную функцию: они позволяют извлекать части строки, которые соответствуют шаблону. Это очень полезно для определения того, что совпало, и для обработки текста в целом. Для каждой группы часть, соответствующая шаблону внутри, попадает в специальные переменные $1, $2, и т. д. Они могут использоваться как обычные переменные:
# extract hours, minutes, seconds
if ($time =~ /(\d\d):(\d\d):(\d\d)/) { # match hh:mm:ss format
$hours = $1;
$minutes = $2;
$seconds = $3;
} Теперь мы знаем, что в скалярном контексте $time =~ /(\d\d):(\d\d):(\d\d)/ возвращает значение истинности или ложности. Однако в контексте списка он возвращает список сопоставленных значений ($1,$2,$3). Поэтому мы можем записать код более компактно как
# extract hours, minutes, seconds
($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/); Если группировки в регулярном выражении вложены, $1 получает группу с самой левой открывающей скобкой, $2 — следующую открывающую скобку, и так далее. Вот регулярное выражение с вложенными группами:
/(ab(cd|ef)((gi)|j))/;
1 2 34 Если это регулярное выражение соответствует шаблону, $1 содержит строку, начинающуюся с 'ab', $2 установлено либо в 'cd', либо в 'ef', $3 равно либо 'gi', либо 'j', и $4 установлено либо в 'gi', так же как и $3, либо остаётся неопределённым.
Для удобства Perl устанавливает $+ в строку, содержащуюся в группе с наибольшим номером $1, $2,... которая получила значение (и, отчасти, связанно с этим, $^N со значением группы $1, $2,... которая была назначена последней; т.е. с $1, $2,... связанной с правой закрывающей скобкой, используемой в совпадении).
Обратные ссылки
Тесно связанные с переменными сопоставления $1, $2, ... — обратные ссылки \g1, \g2,... Обратные ссылки — это просто переменные сопоставления, которые могут быть использованы внутри регулярного выражения. Это действительно удобная функция; то, что соответствует шаблону позже в регулярном выражении, зависит от того, что соответствовало ранее в регулярном выражении. Предположим, что нам нужно найти удвоенные слова в тексте, например, "the the". Следующее регулярное выражение находит все удвоенные слова из 3 букв с пробелом между ними:
/\b(\w\w\w)\s\g1\b/; Группировка присваивает значение \g1, чтобы одна и та же последовательность из 3 букв использовалась для обеих частей.
Аналогичная задача — найти слова, состоящие из двух одинаковых частей:
% simple_grep '^(\w\w\w\w|\w\w\w|\w\w|\w)\g1$' /usr/dict/words
beriberi
booboo
coco
mama
murmur
papa Регулярное выражение имеет одну группировку, которая рассматривает комбинации из 4 букв, затем из 3 букв и т. д., и использует \g1 для поиска повторения. Хотя $1 и \g1 представляют одно и то же, необходимо соблюдать осторожность, используя переменные сопоставления $1, $2, ... только вне регулярного выражения и обратные ссылки \g1, \g2, ... только внутри регулярного выражения; в противном случае могут получиться неожиданные и неудовлетворительные результаты.
Относительные обратные ссылки
Подсчёт открывающих скобок для получения правильного номера обратной ссылки становится трудоёмким, как только количество захватывающих групп превышает одну. Более удобная техника появилась в Perl 5.10: относительные обратные ссылки. Для ссылки на предыдущую захватывающую группу теперь можно использовать \g{-1}, на предпоследнюю — \g{-2}, и так далее.
Ещё одна причина помимо читабельности и поддерживаемости для использования относительных обратных ссылок показана в следующем примере, где используется простой шаблон для сопоставления особых строк:
$a99a = '([a-z])(\d)\g2\g1'; # matches a11a, g22g, x33x, etc. Теперь, когда у нас есть этот шаблон, сохранённый в удобной строке, мы, возможно, захотим использовать его в качестве части другого шаблона:
$line = "code=e99e";
if ($line =~ /^(\w+)=$a99a$/){ # unexpected behavior!
print "$1 is valid\n";
} else {
print "bad line: '$line'\n";
} Но этого не происходит, по крайней мере, не так, как можно ожидать. Только после вставки интерполированного $a99a и просмотра полученного полного текста регулярного выражения становится очевидно, что обратные ссылки сработали не так, как нужно. Подвыражение (\w+) захватило номер 1 и понизило ранг групп в $a99a на единицу. Это можно избежать, используя относительные обратные ссылки:
$a99a = '([a-z])(\d)\g{-1}\g{-2}'; # safe for being interpolated Именованные обратные ссылки
Perl 5.10 также представил именованные захватывающие группы и именованные обратные ссылки. Чтобы добавить имя к захватывающей группе, нужно написать либо (?<name>...), либо (?'name'...). Обратная ссылка затем может быть записана как \g{name}. Разрешается присвоить одно и то же имя нескольким группам, но тогда можно обратиться только к самой левой из одноимённых групп. Вне шаблона именованная захватывающая группа доступна через хеш %+.
Предположим, что нужно сопоставить даты календаря, которые могут быть заданы в одном из трёх форматов yyyy-mm-dd, mm/dd/yyyy или dd.mm.yyyy, мы можем написать три подходящих шаблона, где 'd', 'm' и 'y' соответственно будут именами групп, захватывающих соответствующие компоненты даты. Операция сопоставления объединяет три шаблона в качестве альтернатив:
$fmt1 = '(?<y>\d\d\d\d)-(?<m>\d\d)-(?<d>\d\d)';
$fmt2 = '(?<m>\d\d)/(?<d>\d\d)/(?<y>\d\d\d\d)';
$fmt3 = '(?<d>\d\d)\.(?<m>\d\d)\.(?<y>\d\d\d\d)';
for my $d (qw(2006-10-21 15.01.2007 10/31/2005)) {
if ( $d =~ m{$fmt1|$fmt2|$fmt3} ){
print "day=$+{d} month=$+{m} year=$+{y}\n";
}
} Если любая из альтернатив соответствует шаблону, хеш %+ будет содержать три пары ключ-значение.
Альтернативная нумерация захватывающих групп
Ещё один метод нумерации захватывающих групп (также начиная с Perl 5.10) решает проблему ссылки на группы внутри набора альтернатив. Рассмотрим шаблон для сопоставления времени суток, гражданского или военного стиля:
if ( $time =~ /(\d\d|\d):(\d\d)|(\d\d)(\d\d)/ ){
# process hour and minute
} Обработка результатов требует дополнительного оператора if для определения того, содержат ли $1 и $2 или $3 и $4 нужные данные. Было бы проще, если бы мы могли использовать номера групп 1 и 2 и во второй альтернативе, и это именно то, что достигается с помощью скобок (?|...), помещённых вокруг альтернативы. Вот расширенная версия предыдущего шаблона:
if($time =~ /(?|(\d\d|\d):(\d\d)|(\d\d)(\d\d))\s+([A-Z][A-Z][A-Z])/){
print "hour=$1 minute=$2 zone=$3\n";
} Внутри группы альтернативной нумерации номера групп начинаются с той же позиции для каждой альтернативы. После группы нумерация продолжается с номера, на единицу превышающего максимальное значение, достигнутое во всех альтернативах.
Информация о позиции
В дополнение к сопоставленному значению Perl также предоставляет позиции сопоставления в массивах @- и @+. $-[0] — позиция начала всего совпадения, а $+[0] — позиция конца. Аналогично, $-[n] — позиция начала совпадения $n, а $+[n] — позиция конца. Если $n не определено, то не определены и $-[n] и $+[n]. Тогда этот код
$x = "Mmm...donut, thought Homer";
$x =~ /^(Mmm|Yech)\.\.\.(donut|peas)/; # matches
foreach $exp (1..$#-) {
print "Match $exp: '${$exp}' at position ($-[$exp],$+[$exp])\n";
} выводит
Match 1: 'Mmm' at position (0,3)
Match 2: 'donut' at position (6,11) Даже если в регулярном выражении нет группировок, всё ещё можно узнать, что именно соответствовало в строке. Если вы их используете, Perl установит $` в часть строки перед совпадением, $& в часть строки, которая соответствовала шаблону, и '$' в часть строки после совпадения. Пример:
$x = "the cat caught the mouse";
$x =~ /cat/; # $` = 'the ', $& = 'cat', $' = ' caught the mouse'
$x =~ /the/; # $` = '', $& = 'the', $' = ' cat caught the mouse' При втором совпадении $` равно '', потому что регулярное выражение соответствовало шаблону в первой позиции символа в строке и остановилось; оно никогда не видела второе "the".
Если ваш код будет выполняться на версиях Perl раньше 5.20, стоит отметить, что использование $` и '$' значительно замедляет сопоставление с регулярным выражением, а $& замедляет его в меньшей степени, поскольку, если они используются в одном регулярном выражении в программе, они генерируются для всех регулярных выражений в программе. Поэтому, если для вашего приложения важна производительность, их следует избегать. Если вам нужно извлечь соответствующие подстроки, используйте @- и @+ вместо этого:
$` is the same as substr( $x, 0, $-[0] )
$& is the same as substr( $x, $-[0], $+[0]-$-[0] )
$' is the same as substr( $x, $+[0] ) Начиная с Perl 5.10, переменные ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH} могут быть использованы. Они устанавливаются только если присутствует модификатор /p. Соответственно, они не накладывают штрафа на остальную часть программы. В Perl 5.20 ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH} доступны независимо от того, использовался ли модификатор /p (модификатор игнорируется), а $`, '$' и $& не влияют на скорость.
Незахватывающие группировки
Группа, которая необходима для объединения набора альтернатив, может или не может быть полезна в качестве захватывающей группы. Если нет, она просто создаёт излишнее дополнение к набору доступных значений захватывающих групп, как внутри, так и вне регулярного выражения. Незахватывающие группировки, обозначаемые (?:regexp), по-прежнему позволяют рассматривать регулярное выражение как единый элемент, но при этом не создают захватывающей группы. Как захватывающие, так и незахватывающие группировки могут сосуществовать в одном регулярном выражении. Поскольку извлечения не происходит, незахватывающие группировки быстрее, чем захватывающие. Незахватывающие группировки также удобны для выбора именно тех частей регулярного выражения, которые должны быть извлечены в переменные сопоставления:
# match a number, $1-$4 are set, but we only want $1
/([+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)/;
# match a number faster , only $1 is set
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;
# match a number, get $1 = whole number, $2 = exponent
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]([+-]?\d+))?)/; Незахватывающие группировки также полезны для удаления бесполезных элементов, полученных из операции split, где скобки по какой-либо причине необходимы:
$x = '12aba34ba5';
@num = split /(a|b)+/, $x; # @num = ('12','a','34','a','5')
@num = split /(?:a|b)+/, $x; # @num = ('12','34','5') В Perl 5.22 и более поздних версиях все группы в регулярном выражении могут быть установлены как незахватывающие, используя новый флаг /n.
"hello" =~ /(hi|hello)/n; # $1 is not set! См. "n" в perlre для получения дополнительной информации.
Сопоставление повторений
Примеры в предыдущем разделе демонстрируют неприятный недостаток. Мы сопоставляли только слова из 3 букв или части слов из 4 букв или меньше. Мы хотели бы иметь возможность сопоставлять слова или, более общо, строки любой длины, не выписывая утомительные альтернативы, такие как \w\w\w\w|\w\w\w|\w\w|\w.
Это именно та проблема, для которой были созданы метасимволы квантификаторов '?', '*', '+', и {}. Они позволяют нам ограничить количество повторений для части регулярного выражения, которую мы считаем соответствием. Квантификаторы ставятся сразу после символа, класса символов или группировки, которую мы хотим указать. Они имеют следующие значения:
-
a?означает: сопоставить'a'1 или 0 раз -
a*означает: сопоставить'a'0 или более раз, т.е., любое количество раз -
a+означает: сопоставить'a'1 или более раз, т.е., по крайней мере один раз -
a{n,m}означает: сопоставить не менееnраз, но не болееmраз. -
a{n,}означает: сопоставить не менееnили более раз -
a{n}означает: сопоставить ровноnраз
Вот несколько примеров:
/[a-z]+\s+\d*/; # match a lowercase word, at least one space, and
# any number of digits
/(\w+)\s+\g1/; # match doubled words of arbitrary length
/y(es)?/i; # matches 'y', 'Y', or a case-insensitive 'yes'
$year =~ /^\d{2,4}$/; # make sure year is at least 2 but not more
# than 4 digits
$year =~ /^\d{4}$|^\d{2}$/; # better match; throw out 3-digit dates
$year =~ /^\d{2}(\d{2})?$/; # same thing written differently.
# However, this captures the last two
# digits in $1 and the other does not.
% simple_grep '^(\w+)\g1$' /usr/dict/words # isn't this easier?
beriberi
booboo
coco
mama
murmur
papa Для всех этих квантификаторов Perl попытается сопоставить как можно большую часть строки, при этом позволяя регулярному выражению успешно завершиться. Таким образом, при использовании /a?.../, Perl сначала попытается сопоставить регулярное выражение с 'a'; если это не удастся, Perl попытается сопоставить регулярное выражение без 'a'. Для квантификатора '*' мы получим следующее:
$x = "the cat in the hat";
$x =~ /^(.*)(cat)(.*)$/; # matches,
# $1 = 'the '
# $2 = 'cat'
# $3 = ' in the hat' Что, можно было ожидать, соответствие находит единственное cat в строке и фиксируется на нём. Однако рассмотрим это регулярное выражение:
$x =~ /^(.*)(at)(.*)$/; # matches,
# $1 = 'the cat in the h'
# $2 = 'at'
# $3 = '' (0 characters match) Можно было бы предположить, что Perl найдёт at в cat и остановится на этом, но это не даст самой длинной возможной строке для первого квантификатора .*. Вместо этого первый квантификатор .* захватывает как можно большую часть строки, при этом позволяя регулярному выражению соответствовать. В этом примере это означает наличие последовательности at с последним at в строке. Другим важным принципом, иллюстрируемым здесь, является то, что когда в регулярном выражении есть два или более элемента, левый квантификатор, если он есть, захватывает как можно большую часть строки, оставляя остальную часть регулярного выражения бороться за оставшиеся фрагменты. Таким образом, в нашем примере первый квантификатор .* захватывает большую часть строки, а второй квантификатор .* получает пустую строку. Квантификаторы, которые захватывают как можно большую часть строки, называются квантификаторами максимального соответствия или жадными квантификаторами.
Когда регулярное выражение может сопоставить строку несколькими различными способами, мы можем использовать вышеприведенные принципы для предсказания того, каким способом регулярное выражение будет сопоставляться:
-
Принцип 0: В целом, любое регулярное выражение будет сопоставляться в самой ранней возможной позиции в строке.
-
Принцип 1: В чередовании
a|b|c...будет использована самая левая альтернатива, которая позволяет совпасть со всем регулярным выражением. -
Принцип 2: Квантификаторы максимального соответствия
'?','*','+'и{n,m}в общем случае будут сопоставлять как можно большую часть строки, при этом позволяя всему регулярному выражению соответствовать. -
Принцип 3: Если в регулярном выражении есть два или более элемента, самый левый жадный квантификатор, если таковой имеется, будет сопоставлять как можно большую часть строки, при этом позволяя всему регулярному выражению соответствовать. Следующий самый левый жадный квантификатор, если таковой имеется, попытается сопоставить как можно большую часть оставшейся для него части строки, при этом позволяя всему регулярному выражению соответствовать. И так далее, пока все элементы регулярного выражения не будут удовлетворены.
Как мы видели выше, принцип 0 имеет приоритет над другими. Регулярное выражение будет сопоставляться как можно раньше, а другие принципы определят, как регулярное выражение сопоставляется в этой самой ранней позиции символа.
Вот пример этих принципов в действии:
$x = "The programming republic of Perl";
$x =~ /^(.+)(e|r)(.*)$/; # matches,
# $1 = 'The programming republic of Pe'
# $2 = 'r'
# $3 = 'l' Это регулярное выражение соответствует самой ранней позиции строки, 'T'. Можно было бы подумать, что 'e', будучи самым левым в чередовании, будет сопоставлен, но 'r' производит самую длинную строку в первом квантификаторе.
$x =~ /(m{1,2})(.*)$/; # matches,
# $1 = 'mm'
# $2 = 'ing republic of Perl' Здесь самое раннее возможное соответствие находится в первом 'm' в programming. m{1,2} является первым квантификатором, поэтому он соответствует максимальному mm.
$x =~ /.*(m{1,2})(.*)$/; # matches,
# $1 = 'm'
# $2 = 'ing republic of Perl' Здесь регулярное выражение сопоставляется в начале строки. Первый квантификатор .* захватывает как можно больше, оставляя только один 'm' для второго квантификатора m{1,2}.
$x =~ /(.?)(m{1,2})(.*)$/; # matches,
# $1 = 'a'
# $2 = 'mm'
# $3 = 'ing republic of Perl' Здесь .? съедает максимальный один символ в самой ранней возможной позиции в строке, 'a' в programming, оставляя m{1,2} возможность сопоставить оба 'm'.
"aXXXb" =~ /(X*)/; # matches with $1 = '' потому что оно может сопоставить ноль копий 'X' в начале строки. Если вы определенно хотите сопоставить по крайней мере один 'X', используйте X+, а не X*.
Иногда жадность нехороша. В некоторых случаях мы хотели бы, чтобы квантификаторы соответствовали минимальному фрагменту строки, а не максимальному фрагменту. Для этой цели Лэрри Уолл создал квантификаторы минимального соответствия или нежадные квантификаторы ??, *?, +?, и {}?. Это обычные квантификаторы с добавленным к ним '?'. Они имеют следующие значения:
-
a??означает: сопоставить'a'0 или 1 раз. Попробуйте сначала 0, затем 1. -
a*?означает: сопоставить'a'0 или более раз, т.е., любое количество раз, но как можно меньше раз -
a+?означает: сопоставить'a'1 или более раз, т.е., по крайней мере один раз, но как можно меньше раз -
a{n,m}?означает: сопоставить не менееnраз, не болееmраз, как можно меньше раз -
a{n,}?означает: сопоставить не менееnраз, но как можно меньше раз -
a{n}?означает: сопоставить ровноnраз. Поскольку мы сопоставляем ровноnраз,a{n}?эквивалентноa{n}и присутствует только для согласованности обозначений.
Давайте рассмотрим пример выше, но с минимальными квантификаторами:
$x = "The programming republic of Perl";
$x =~ /^(.+?)(e|r)(.*)$/; # matches,
# $1 = 'Th'
# $2 = 'e'
# $3 = ' programming republic of Perl' Наименьшая строка, которая позволит как началу строки '^', так и чередованию совпасть, это Th, где чередование e|r соответствует 'e'. Второй квантификатор .* свободен поглощать остальную часть строки.
$x =~ /(m{1,2}?)(.*?)$/; # matches,
# $1 = 'm'
# $2 = 'ming republic of Perl' Первая позиция строки, для которой это регулярное выражение может соответствовать, находится в первом 'm' в programming. В этой позиции минимальное m{1,2}? соответствует только одному 'm'. Хотя второй квантификатор .*? хотел бы сопоставить ноль символов, он ограничен якорем конца строки '$' для сопоставления остальной части строки.
$x =~ /(.*?)(m{1,2}?)(.*)$/; # matches,
# $1 = 'The progra'
# $2 = 'm'
# $3 = 'ming republic of Perl' В этом регулярном выражении вы, возможно, ожидали, что первый минимальный квантификатор .*? соответствует пустой строке, потому что он не ограничен якорем '^' для сопоставления начала слова. Однако здесь применяется принцип 0. Поскольку возможно, что все регулярное выражение совпадает в начале строки, оно будет соответствовать началу строки. Таким образом, первый квантификатор должен сопоставить все до первого 'm'. Второй минимальный квантификатор сопоставляет только один 'm', а третий квантификатор сопоставляет остальную часть строки.
$x =~ /(.??)(m{1,2})(.*)$/; # matches,
# $1 = 'a'
# $2 = 'mm'
# $3 = 'ing republic of Perl' Точно так же, как и в предыдущем регулярном выражении, первый квантификатор .?? может соответствовать раньше всего в позиции 'a', поэтому так и происходит. Второй квантификатор жадный, поэтому он сопоставляет mm, а третий сопоставляет остальную часть строки.
Мы можем изменить принцип 3 выше, чтобы учесть нежадные квантификаторы:
-
Принцип 3: Если в регулярном выражении есть два или более элемента, самый левый жадный (нежадный) квантификатор, если таковой имеется, будет сопоставлять как можно больше (меньше) части строки, при этом позволяя всему регулярному выражению соответствовать. Следующий самый левый жадный (нежадный) квантификатор, если таковой имеется, попытается сопоставить как можно больше (меньше) оставшейся части строки, при этом позволяя всему регулярному выражению соответствовать. И так далее, пока все элементы регулярного выражения не будут удовлетворены.
Точно так же, как и чередование, квантификаторы также подвержены возврату назад. Вот пошаговый анализ примера
$x = "the cat in the hat";
$x =~ /^(.*)(at)(.*)$/; # matches,
# $1 = 'the cat in the h'
# $2 = 'at'
# $3 = '' (0 matches) -
0. Начинаем с первой буквы строки
't'. -
1. Первый квантификатор
'.*'начинает сопоставление всей строки "the cat in the hat". -
2.
'a'в элементе регулярного выражения'at'не соответствует концу строки. Вернёмся назад на один символ. -
3.
'a'в элементе регулярного выражения'at'все еще не соответствует последней букве строки't', поэтому вернёмся на один символ назад. -
4. Теперь мы можем сопоставить
'a'и't'. -
5. Переходим к третьему элементу
'.*'. Поскольку мы находимся в конце строки, и'.*'может сопоставляться 0 раз, присвоим ему пустую строку. - 6. Завершено!
В большинстве случаев все эти перемещения вперед и назад происходят быстро, и поиск быстрый. Однако есть некоторые патологические регулярные выражения, время выполнения которых экспоненциально растёт с размером строки. Типичная структура, которая может привести к таким проблемам, имеет вид
/(a|b+)*/; Проблема заключается во вложенных неопределенных квантификаторах. Существует множество различных способов разбиения строки длиной n между '+' и '*': одно повторение с b+ длины n, два повторения с первой b+ длины k и второй длиной n-k, m повторений, сумма битов которых равна длине n, и т.д.. Фактически, существует экспоненциальное количество способов разбиения строки в зависимости от её длины. Регулярное выражение может повезти и совпасть на ранней стадии процесса, но если совпадения нет, Perl будет пытаться перебрать все возможности, прежде чем отказаться. Поэтому будьте осторожны с вложенными '*', {n,m} и '+'. Книга Джеффри Фридла «Мастерство регулярных выражений» содержит прекрасное обсуждение этого и других проблем эффективности.
Позиционные квантификаторы
Повторное обратное отслеживание во время неустанного поиска совпадения может быть пустой тратой времени, особенно когда совпадение заведомо обречено на неудачу. Рассмотрим простое выражение
/^\w+\s+\w+$/; # a word, spaces, a word Когда это применяется к строке, которая не совсем соответствует ожиданиям выражения, например, "abc " или "abc def ", движок регулярных выражений будет выполнять обратное отслеживание примерно один раз для каждого символа в строке. Но мы знаем, что нет способа обойтись без взятия всех начальных символов слова для соответствия первому повторению, что все пробелы должны быть съедены средней частью, и то же самое относится ко второму слову.
С введением позиционных квантификаторов в Perl 5.10 у нас есть способ указать движку регулярных выражений не выполнять обратное отслеживание, используя обычные квантификаторы с добавленным '+' к ним. Это делает их жадными, а также скупыми; однажды добившись успеха, они больше ничего не вернут, чтобы разрешить другое решение. Они имеют следующие значения:
-
a{n,m}+означает: сопоставить как минимумnраз, не болееmраз, как можно больше раз, и не отступать.a?+является сокращением отa{0,1}+ -
a{n,}+означает: сопоставить как минимумnраз, но как можно больше раз, и не отступать.a*+является сокращением отa{0,}+иa++является сокращением отa{1,}+. -
a{n}+означает: сопоставить ровноnраз. Это просто для согласованности обозначений.
Эти позиционные квантификаторы представляют собой частный случай более общего понятия, независимого подвыражения, см. ниже.
В качестве примера, где подходит позиционный квантификатор, рассмотрим сопоставление строки в кавычках, как она появляется в нескольких языках программирования. Обратная косая черта используется в качестве управляющего символа, указывающего, что следующий символ должен быть взят буквально, как другой символ для строки. Следовательно, после открывающей кавычки мы ожидаем (возможно, пустую) последовательность альтернатив: либо какой-либо символ, кроме неуправляемой кавычки или обратной косой черты, либо управляемый символ.
/"(?:[^"\\]++|\\.)*+"/; Создание регулярного выражения
На этом этапе мы рассмотрели все основные понятия регулярных выражений, поэтому давайте рассмотрим более сложный пример регулярного выражения. Мы создадим регулярное выражение, которое соответствует числам.
Первая задача при создании регулярного выражения — определить, что мы хотим сопоставить, а что хотим исключить. В нашем случае мы хотим сопоставить как целые, так и числа с плавающей запятой, и мы хотим отклонить любую строку, которая не является числом.
Следующая задача — разбить проблему на более мелкие подзадачи, которые легко преобразуются в регулярное выражение.
Простейший случай — целые числа. Они состоят из последовательности цифр с необязательным знаком перед ними. Цифры мы можем представить с помощью \d+ , а знак можно сопоставить с [+-] . Таким образом, регулярное выражение для целого числа —
/[+-]?\d+/; # matches integers Число с плавающей запятой может потенциально иметь знак, целую часть, десятичную точку, дробную часть и показатель степени. Одна или несколько из этих частей являются необязательными, поэтому нам нужно проверить различные возможности. Числа с плавающей запятой, имеющие правильную форму, включают 123., 0.345, .34, -1e6 и 25.4E-72. Как и с целыми числами, знак перед ним является полностью необязательным и может быть сопоставлен с [+-]? . Мы можем видеть, что если нет показателя степени, числа с плавающей запятой должны иметь десятичную точку, в противном случае они являются целыми числами. Мы могли бы быть склонны смоделировать их с помощью \d*\.\d* , но это также будет соответствовать только одной десятичной точке, что не является числом. Итак, три случая числа с плавающей запятой без показателя степени:
/[+-]?\d+\./; # 1., 321., etc.
/[+-]?\.\d+/; # .1, .234, etc.
/[+-]?\d+\.\d+/; # 1.0, 30.56, etc. Эти случаи можно объединить в одно регулярное выражение с трёхсторонней заменой:
/[+-]?(\d+\.\d+|\d+\.|\.\d+)/; # floating point, no exponent В этой замене важно поместить '\d+\.\d+' перед '\d+\.' . Если бы '\d+\.' было первым, регулярное выражение без проблем сопоставилось бы с ним и проигнорировало дробную часть числа.
Теперь рассмотрим числа с плавающей запятой с показателями степени. Ключевое наблюдение состоит в том, что обе целые числа и числа с десятичными точками допускаются перед показателем степени. Затем показатели степени, как и общий знак, независимы от того, сопоставляем ли мы числа с или без десятичных точек, и могут быть «отвязаны» от мантиссы. Теперь общая форма регулярного выражения становится ясной:
/^(optional sign)(integer | f.p. mantissa)(optional exponent)$/; Показатель степени — это 'e' или 'E' , за которым следует целое число. Итак, регулярное выражение для показателя степени —
/[eE][+-]?\d+/; # exponent Объединив все части, мы получим регулярное выражение, которое соответствует числам:
/^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/; # Ta da! Длинные регулярные выражения, подобные этому, могут произвести впечатление на ваших друзей, но могут быть трудно расшифровать. В сложных ситуациях, подобных этой, модификатор /x для соответствия является неоценимым. Он позволяет вставлять практически любые пробелы и комментарии в регулярное выражение без изменения его смысла. С его помощью мы можем переписать наше «расширенное» регулярное выражение в более удобочитаемой форме
/^
[+-]? # first, match an optional sign
( # then match integers or f.p. mantissas:
\d+\.\d+ # mantissa of the form a.b
|\d+\. # mantissa of the form a.
|\.\d+ # mantissa of the form .b
|\d+ # integer of the form a
)
( [eE] [+-]? \d+ )? # finally, optionally match an exponent
$/x; Если пробелы в основном не имеют значения, как можно включить пробелы в расширенном регулярном выражении? Ответ — добавить обратную косую черту '\ ' или поместить его в класс символов [ ]. То же самое относится и к знакам фунта: используйте \# или [#] . Например, Perl позволяет пробел между знаком и мантиссой или целым числом, и мы можем добавить это в наше регулярное выражение следующим образом:
/^
[+-]?\ * # first, match an optional sign *and space*
( # then match integers or f.p. mantissas:
\d+\.\d+ # mantissa of the form a.b
|\d+\. # mantissa of the form a.
|\.\d+ # mantissa of the form .b
|\d+ # integer of the form a
)
( [eE] [+-]? \d+ )? # finally, optionally match an exponent
$/x; В этой форме легче увидеть способ упростить замену. Альтернативы 1, 2 и 4 все начинаются с \d+, поэтому его можно вынести за скобки:
/^
[+-]?\ * # first, match an optional sign
( # then match integers or f.p. mantissas:
\d+ # start out with a ...
(
\.\d* # mantissa of the form a.b or a.
)? # ? takes care of integers of the form a
|\.\d+ # mantissa of the form .b
)
( [eE] [+-]? \d+ )? # finally, optionally match an exponent
$/x; Начиная с Perl v5.26, указание /xx изменяет квадратные скобки в шаблоне, чтобы игнорировать символы табуляции и пробелы, если они не экранированы предшествующей обратной косой чертой. Таким образом, мы могли бы написать
/^
[ + - ]?\ * # first, match an optional sign
( # then match integers or f.p. mantissas:
\d+ # start out with a ...
(
\.\d* # mantissa of the form a.b or a.
)? # ? takes care of integers of the form a
|\.\d+ # mantissa of the form .b
)
( [ e E ] [ + - ]? \d+ )? # finally, optionally match an exponent
$/xx; Это не улучшает удобочитаемость этого примера, но оно доступно, если вам это нужно. Сжимая шаблон до компактной формы, у нас есть
/^[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/; Это наше окончательное регулярное выражение. В заключение, мы построили регулярное выражение, следуя
-
подробному описанию задачи,
-
разбиению проблемы на более мелкие части,
-
переводу мелких частей в регулярные выражения,
-
объединению регулярных выражений,
-
и оптимизации конечного объединённого регулярного выражения.
Это также типичные этапы написания компьютерной программы. Это имеет смысл, потому что регулярные выражения по сути представляют собой программы, написанные на небольшом компьютерном языке, который определяет шаблоны.
Использование регулярных выражений в Perl
Последняя тема части 1 кратко описывает, как используются регулярные выражения в программах Perl. Где они вписываются в синтаксис Perl?
Мы уже представили оператор соответствия в его стандартной /regexp/ и произвольной форме разделителя m!regexp! . Мы использовали оператор связывания =~ и его отрицание !~ для проверки соответствия строк. В связи с оператором сопоставления мы обсудили однострочные /s, многострочные /m, регистронезависимые /i и расширенные /x модификаторы. Есть ещё несколько моментов, которые вам могут быть интересны относительно операторов соответствия.
Запрещение замены
Если вы измените $pattern после первой замены, Perl проигнорирует её. Если вы не хотите никаких замен вообще, используйте специальный разделитель m'':
@pattern = ('Seuss');
while (<>) {
print if m'@pattern'; # matches literal '@pattern', not 'Seuss'
} Аналогично строкам, m'' действует как апострофы в регулярном выражении; все остальные 'm' разделители действуют как кавычки. Если регулярное выражение вычисляется как пустая строка, вместо этого используется регулярное выражение в последнем успешном соответствии. Итак, у нас есть
"dog" =~ /d/; # 'd' matches
"dogbert =~ //; # this matches the 'd' regexp used before Глобальное сопоставление
Последние два модификатора, которые мы обсудим здесь, /g и /c , относятся к множественным соответствиям. Модификатор /g означает глобальное соответствие и позволяет оператору сопоставления искать в строке как можно больше совпадений. В скалярном контексте последующие вызовы к строке будут /g перепрыгивать от соответствия к соответствию, отслеживая позицию в строке по мере продвижения. Вы можете получить или установить позицию с помощью функции pos().
Использование /g показано в следующем примере. Предположим, что у нас есть строка, состоящая из слов, разделенных пробелами. Если мы знаем, сколько слов в строке заранее, мы можем извлечь слова с помощью группировок:
$x = "cat dog house"; # 3 words
$x =~ /^\s*(\w+)\s+(\w+)\s+(\w+)\s*$/; # matches,
# $1 = 'cat'
# $2 = 'dog'
# $3 = 'house' Но что если у нас неопределённое количество слов? Вот для чего был создан /g . Чтобы извлечь все слова, сформируйте простое регулярное выражение (\w+) и циклически пройдитесь по всем совпадениям с /(\w+)/g:
while ($x =~ /(\w+)/g) {
print "Word is $1, ends at position ", pos $x, "\n";
} печатает
Word is cat, ends at position 3
Word is dog, ends at position 7
Word is house, ends at position 13 Неудачное соответствие или изменение целевой строки сбрасывают позицию. Если вы не хотите сбрасывать позицию после неудачного соответствия, добавьте /c, как в /regexp/gc. Текущая позиция в строке связана со строкой, а не с регулярным выражением. Это означает, что у разных строк разные позиции, и их соответствующие позиции можно устанавливать или считывать независимо.
В списковом контексте /g возвращает список сопоставленных группировок, или, если нет группировок, список соответствий для всего регулярного выражения. Итак, если мы хотели только слова, мы могли бы использовать
@words = ($x =~ /(\w+)/g); # matches,
# $words[0] = 'cat'
# $words[1] = 'dog'
# $words[2] = 'house' В тесной связи с модификатором /g находится якорь \G . Якорь \G соответствует позиции, где остановилось предыдущее совпадение /g . \G позволяет нам легко выполнять контекстно-зависимое сопоставление:
$metric = 1; # use metric units
...
$x = <FILE>; # read in measurement
$x =~ /^([+-]?\d+)\s*/g; # get magnitude
$weight = $1;
if ($metric) { # error checking
print "Units error!" unless $x =~ /\Gkg\./g;
}
else {
print "Units error!" unless $x =~ /\Glbs\./g;
}
$x =~ /\G\s+(widget|sprocket)/g; # continue processing Сочетание /g и \G позволяет нам обрабатывать строку по частям и использовать произвольную Perl-логику, чтобы решить, что делать дальше. В настоящее время якорь \G полностью поддерживается только при использовании для привязки к началу шаблона.
\G также неоценимо при обработке записей фиксированной длины с регулярными выражениями. Предположим, у нас есть фрагмент кодирующей области ДНК, закодированной в виде пар оснований ATCGTTGAAT..., и мы хотим найти все стоп-кодоны TGA. В кодирующей области кодоны представляют собой последовательности из 3 букв, поэтому мы можем рассматривать фрагмент ДНК как последовательность записей из 3 букв. Примитивное регулярное выражение
# expanded, this is "ATC GTT GAA TGC AAA TGA CAT GAC"
$dna = "ATCGTTGAATGCAAATGACATGAC";
$dna =~ /TGA/; не работает; оно может сопоставиться с TGA, но нет гарантии, что совпадение выровнено с границами кодонов, например, подстрока GTT GAA даёт совпадение. Лучшее решение —
while ($dna =~ /(\w\w\w)*?TGA/g) { # note the minimal *?
print "Got a TGA stop codon at position ", pos $dna, "\n";
} что выводит
Got a TGA stop codon at position 18
Got a TGA stop codon at position 23 Позиция 18 хорошая, но позиция 23 ошибочная. Что произошло?
Ответ заключается в том, что наше регулярное выражение работает хорошо, пока мы не пройдём за последним реальным совпадением. Затем регулярное выражение не сможет сопоставиться с синхронизированным TGA и начнёт перемещаться вперёд на одну позицию за раз, чего нам не нужно. Решение состоит в использовании \G для привязки совпадения к выравниванию кодонов:
while ($dna =~ /\G(\w\w\w)*?TGA/g) {
print "Got a TGA stop codon at position ", pos $dna, "\n";
} Это выводит
Got a TGA stop codon at position 18 что является правильным ответом. Этот пример показывает, что важно не только найти то, что требуется, но и отвергнуть то, что не нужно.
(Существуют и другие модификаторы регулярных выражений, такие как /o, но их специализированное использование выходит за рамки этого введения.)
Поиск и замена
Регулярные выражения также играют важную роль в операциях поиска и замены в Perl. Поиск и замена выполняются с помощью оператора s///. Общий вид s/regexp/replacement/modifiers, при этом всё, что мы знаем о регулярных выражениях и модификаторах, применяется и в этом случае. Замена — это Perl-строка в двойных кавычках, которая заменяет в строке всё, что совпадает с regexp. Оператор =~ также используется здесь для связывания строки с s///. Если сопоставление происходит с $_, то $_ =~ можно опустить. Если совпадение есть, s/// возвращает количество проведённых замещений; в противном случае возвращает false. Вот несколько примеров:
$x = "Time to feed the cat!";
$x =~ s/cat/hacker/; # $x contains "Time to feed the hacker!"
if ($x =~ s/^(Time.*hacker)!$/$1 now!/) {
$more_insistent = 1;
}
$y = "'quoted words'";
$y =~ s/^'(.*)'$/$1/; # strip single quotes,
# $y contains "quoted words" В последнем примере вся строка была найдена, но только часть внутри одинарных кавычек была сгруппирована. С оператором s///, сопоставленные переменные $1, $2, и т. д., сразу доступны для использования в выражении замены, поэтому мы используем $1 для замены строки в кавычках только на то, что было в кавычках. С глобальным модификатором s///g будет осуществляться поиск и замена всех вхождений регулярного выражения в строке:
$x = "I batted 4 for 4";
$x =~ s/4/four/; # doesn't do it all:
# $x contains "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g; # does it all:
# $x contains "I batted four for four" Если в этом руководстве вы предпочитаете "regex" вместо "regexp", вы можете использовать следующую программу для замены:
% cat > simple_replace
#!/usr/bin/perl
$regexp = shift;
$replacement = shift;
while (<>) {
s/$regexp/$replacement/g;
print;
}
^D
% simple_replace regexp regex perlretut.pod В simple_replace мы использовали модификатор s///g для замены всех вхождений регулярного выражения в каждой строке. (Несмотря на то, что регулярное выражение появляется в цикле, Perl достаточно умен, чтобы скомпилировать его только один раз.) Как и в случае с simple_grep, как print, так и s/$regexp/$replacement/g неявно используют $_.
Если вы не хотите, чтобы s/// изменяло исходную переменную, вы можете использовать модификатор неразрушающей замены s///r. Это изменяет поведение таким образом, что s///r возвращает окончательно заменённую строку (вместо количества замещений):
$x = "I like dogs.";
$y = $x =~ s/dogs/cats/r;
print "$x $y\n"; Этот пример выведет «Мне нравятся собаки. Мне нравятся кошки». Обратите внимание, что исходная переменная $x не была затронута. Вместо этого весь результат замены сохраняется в $y. Если замена ничего не изменила, возвращается исходная строка:
$x = "I like dogs.";
$y = $x =~ s/elephants/cougars/r;
print "$x $y\n"; # prints "I like dogs. I like dogs." Ещё одна интересная возможность, предоставляемая флагом s///r, — это цепочечные замены:
$x = "Cats are great.";
print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~
s/Frogs/Hedgehogs/r, "\n";
# prints "Hedgehogs are great." Модификатор, доступный именно для поиска и замены, — это модификатор вычисления s///e. s///e обрабатывает текст замены как Perl-код, а не строку в двойных кавычках. Значение, возвращаемое кодом, подставляется вместо сопоставленной подстроки. s///e полезно, если вам нужно выполнить вычисления при замене текста. Этот пример подсчитывает частоту символов в строке:
$x = "Bill the cat";
$x =~ s/(.)/$chars{$1}++;$1/eg; # final $1 replaces char with itself
print "frequency of '$_' is $chars{$_}\n"
foreach (sort {$chars{$b} <=> $chars{$a}} keys %chars); Это выводит
frequency of ' ' is 2
frequency of 't' is 2
frequency of 'l' is 2
frequency of 'B' is 1
frequency of 'c' is 1
frequency of 'e' is 1
frequency of 'h' is 1
frequency of 'i' is 1
frequency of 'a' is 1 Как и оператор совпадения m//, s/// может использовать другие разделители, такие как s!!! и s{}{}, а также s{}//. Если используются одинарные кавычки s''', то регулярное выражение и замена обрабатываются как строки в одинарных кавычках, и подстановок переменных не происходит. s/// в контексте списка возвращает то же самое, что и в скалярном контексте, т. е., количество совпадений.
Функция split
Функция split() — это ещё одно место, где используется регулярное выражение. split /regexp/, string, limit разделяет операнд string на список подстрок и возвращает этот список. Регулярное выражение должно быть разработано таким образом, чтобы находить всё, что является разделителями для нужных подстрок. limit, если присутствует, ограничивает разделение не более чем limit количеством строк. Например, чтобы разбить строку на слова, используйте
$x = "Calvin and Hobbes";
@words = split /\s+/, $x; # $word[0] = 'Calvin'
# $word[1] = 'and'
# $word[2] = 'Hobbes' Если используется пустое регулярное выражение //, регулярное выражение всегда совпадает, и строка разделяется на отдельные символы. Если регулярное выражение содержит группировки, то результирующий список содержит также сопоставленные подстроки из группировок. Например,
$x = "/usr/bin/perl";
@dirs = split m!/!, $x; # $dirs[0] = ''
# $dirs[1] = 'usr'
# $dirs[2] = 'bin'
# $dirs[3] = 'perl'
@parts = split m!(/)!, $x; # $parts[0] = ''
# $parts[1] = '/'
# $parts[2] = 'usr'
# $parts[3] = '/'
# $parts[4] = 'bin'
# $parts[5] = '/'
# $parts[6] = 'perl' Поскольку первый символ $x сопоставился с регулярным выражением, split добавил в начало списка пустой элемент.
Если вы дочитали до этого места, поздравляем! Теперь у вас есть все основные инструменты, необходимые для использования регулярных выражений для решения широкого круга задач обработки текста. Если это ваш первый раз в этом руководстве, почему бы не остановиться здесь и немного поиграть с регулярными выражениями.... Часть 2 касается более экзотических аспектов регулярных выражений, и эти понятия, безусловно, не нужны сразу.
Часть 2: Мощные инструменты
Хорошо, вы знаете основы регулярных выражений и хотите узнать больше. Если сопоставление регулярных выражений аналогично прогулке по лесу, то инструменты, обсуждаемые в части 1, аналогичны топографическим картам и компасу, — это базовые инструменты, которые мы используем постоянно. Большинство инструментов во второй части аналогичны сигнальным ракетам и спутниковым телефонам. Ими не очень часто пользуются при походе, но когда мы оказываемся в затруднительном положении, они могут быть бесценными.
В дальнейшем будут рассмотрены более продвинутые, реже используемые или порой экзотические возможности Perl-регулярных выражений. Во второй части мы предполагаем, что вы знакомы с основами и сосредоточимся на расширенных функциях.
Подробнее о символах, строках и классах символов
Существует ряд последовательностей escape и классов символов, которые мы ещё не рассмотрели.
Существует несколько последовательностей escape, преобразующих символы или строки между верхним и нижним регистром, и они также доступны в шаблонах. \l и \u преобразуют следующий символ в нижний или верхний регистр соответственно:
$x = "perl";
$string =~ /\u$x/; # matches 'Perl' in $string
$x = "M(rs?|s)\\."; # note the double backslash
$string =~ /\l$x/; # matches 'mr.', 'mrs.', and 'ms.', \L или \U указывают на длительное преобразование регистра до тех пор, пока оно не будет прекращено \E или не будет перекрыто другим \U или \L:
$x = "This word is in lower case:\L SHOUT\E";
$x =~ /shout/; # matches
$x = "I STILL KEYPUNCH CARDS FOR MY 360"
$x =~ /\Ukeypunch/; # matches punch card string Если \E отсутствует, регистр преобразуется до конца строки. Регулярные выражения \L\u$word или \u\L$word преобразуют первый символ $word в верхний регистр, а остальные символы — в нижний.
Управляющие символы можно экранировать с помощью \c, так что символ управления Z сопоставляется с \cZ. Последовательность escape \Q...\E экранирует, или защищает, большинство небуквенных символов. Например,
$x = "\QThat !^*&%~& cat!";
$x =~ /\Q!^*&%~&\E/; # check for rough language Она не защищает '$' или '@', так что подстановка переменных всё ещё возможна.
\Q, \L, \l, \U, \u и \E на самом деле являются частью синтаксиса двойных кавычек, а не частью синтаксиса регулярных выражений. Они будут работать, если появятся в регулярном выражении, встроенном непосредственно в программу, но не когда содержатся в строке, интерполированной в шаблон.
Perl-регулярные выражения могут обрабатывать не только стандартный набор символов ASCII. Perl поддерживает Unicode — стандарт для представления алфавитов практически всех письменных языков мира, и множество символов. Perl-строки являются строками Unicode, поэтому они могут содержать символы со значением (кодовой точкой или номером символа) большим, чем 255.
Что это значит для регулярных выражений? Пользователям регулярных выражений не нужно много знать об внутренней представлении строк в Perl. Но им нужно знать 1) как представлять символы Unicode в регулярном выражении и 2) что операция сопоставления будет рассматривать строку, в которой ищется, как последовательность символов, а не байтов. Ответ на 1) заключается в том, что символы Unicode, большие, чем chr(255), представляются с помощью обозначения \x{hex}, так как \xXY (без фигурных скобок, а XY — две шестнадцатеричные цифры) не выходит за пределы 255. (Начиная с Perl 5.14, если вы поклонник восьмеричной системы, вы также можете использовать \o{oct}.)
/\x{263a}/; # match a Unicode smiley face :) ПРИМЕЧАНИЕ: В Perl 5.6.0 для использования функций Unicode требовалось указать use utf8. Это больше не требуется: для почти всех операций с Unicode явное использование директивы utf8 не требуется. (Единственный случай, когда это имеет значение, — это если ваша Perl-программа находится в Unicode и закодирована в UTF-8, тогда нужно явное использование директивы use utf8.)
Выяснение шестнадцатеричной последовательности нужного символа Unicode или расшифровка шестнадцатеричного Unicode-регулярного выражения другого человека — задача настолько же увлекательная, как программирование на машинном коде. Поэтому другой способ задания символов Unicode — использование последовательности escape именированного символа \N{name}. name — это имя символа Unicode, как указано в стандарте Unicode. Например, если мы хотим представить или найти астрологический знак планеты Меркурий, мы можем использовать
$x = "abc\N{MERCURY}def";
$x =~ /\N{MERCURY}/; # matches Также можно использовать «короткие» имена:
print "\N{GREEK SMALL LETTER SIGMA} is called sigma.\n";
print "\N{greek:Sigma} is an upper-case sigma.\n"; Вы также можете ограничить имена определённым алфавитом, указав директиву charnames:
use charnames qw(greek);
print "\N{sigma} is Greek sigma\n"; Индекс имён символов доступен онлайн в консорциуме Unicode, http://www.unicode.org/charts/charindex.html; справочные материалы со ссылками на другие ресурсы на http://www.unicode.org/standard/where.
Ответ на требование 2) заключается в том, что regexp (в основном) использует символы Unicode. «В основном» — из-за проблем со сложностью обратной совместимости, но начиная с Perl 5.14, любой regexp, скомпилированный в рамках use feature 'unicode_strings' (который автоматически включается в рамках use 5.012 или выше), превратит это «в основном» в «всегда». Если вы хотите должным образом обработать Unicode, вы должны убедиться, что 'unicode_strings' включен. Внутренне он кодируется в байты с использованием UTF-8 или родного 8-битного кодирования, в зависимости от истории строки, но концептуально это последовательность символов, а не байтов. Смотрите perlunitut для получения руководства по этому вопросу.
Теперь давайте обсудим классы символов Unicode, обычно называемые «свойствами символов». Они представлены в виде escape-последовательности \p{name}. Отрицание этого — \P{name}. Например, для сопоставления строчных и прописных символов,
$x = "BOB";
$x =~ /^\p{IsUpper}/; # matches, uppercase char class
$x =~ /^\P{IsUpper}/; # doesn't match, char class sans uppercase
$x =~ /^\p{IsLower}/; # doesn't match, lowercase char class
$x =~ /^\P{IsLower}/; # matches, char class sans lowercase («Is» — необязательно.)
Существует множество свойств символов Unicode. Для получения полного списка см. perluniprops. У большинства из них есть синонимы с более короткими именами, также перечисленные там. Некоторые синонимы — это один символ. Для них можно опустить фигурные скобки. Например, \pM — это то же самое, что и \p{Mark}, что означает такие вещи, как диакритические знаки.
Свойства Unicode \p{Script} и \p{Script_Extensions} используются для категоризации каждого символа Unicode по письменному языку, к которому он относится. (Script_Extensions — улучшенная версия Script, которая сохраняется для обратной совместимости, поэтому в целом следует использовать Script_Extensions.) Например, английский, французский и ряд других европейских языков написаны латинским алфавитом. Но есть также греческий алфавит, тайский алфавит, катакана, и т.д. Вы можете проверить, находится ли символ в определённом алфавите (на основе Script_Extensions) с помощью, например, \p{Latin}, \p{Greek}, или \p{Katakana}. Чтобы проверить, не входит ли он в балийский алфавит, используйте \P{Balinese}.
Что мы описали до сих пор, это одиночная форма классов символов \p{...}. Также существует составная форма, с которой вы можете столкнуться. Они выглядят как \p{name=value} или \p{name:value} (знак равенства и двоеточие могут использоваться взаимозаменяемо). Они более общие, чем одиночная форма, и фактически большинство одиночных форм являются просто определёнными в Perl сокращениями для общих составных форм. Например, примеры с алфавитами в предыдущем абзаце можно эквивалентно записать как \p{Script_Extensions=Latin}, \p{Script_Extensions:Greek}, \p{script_extensions=katakana}, и \P{script_extensions=balinese} (регистр не имеет значения внутри {} фигурных скобок). Вам, возможно, никогда не придётся использовать составные формы, но иногда это необходимо, и их использование может сделать ваш код более понятным.
\X — аббревиатура класса символов, который включает в себя расширенную графемную кластеризацию Unicode. Это представляет «логический символ»: то, что выглядит как один символ, но может представляться внутри несколькими. Например, используя полные имена Unicode, например, "A + COMBINING RING" — это графемный кластер с базовым символом "A" и объединяющим символом "COMBINING RING, который на датском языке переводится как "A" с кружком сверху, как в слове Ångstrom.
Для получения полной и актуальной информации об Unicode см. последнюю версию стандарта Unicode или веб-сайт консорциума Unicode http://www.unicode.org
Как будто всех этих классов было недостаточно, Perl также определяет классы символов в стиле POSIX. Они имеют вид [:name:], где имя — имя класса POSIX. Классы POSIX — это alpha, alnum, ascii, cntrl, digit, graph, lower, print, punct, space, upper, и xdigit, и два расширения, word (расширение Perl для соответствия \w) и blank (расширение GNU). Модификатор /a ограничивает их соответствием только в диапазоне ASCII; в противном случае они могут соответствовать тому же, что и соответствующие классы Perl Unicode: [:upper:] — это то же, что и \p{IsUpper}, и т.д. (Существуют некоторые исключения и подводные камни; см. perlrecharclass для подробного обсуждения). [:digit:], [:word:], и [:space:] соответствуют знакомым \d, \w, и \s классам символов. Чтобы отрицать класс POSIX, поставьте '^' перед именем, так что, например, [:^digit:] соответствует \D и, при Unicode, \P{IsDigit}. Классы символов Unicode и POSIX могут использоваться так же, как \d, за исключением того, что классы символов POSIX могут быть использованы только внутри класса символов:
/\s+[abc[:digit:]xyz]\s*/; # match a,b,c,x,y,z, or a digit
/^=item\s[[:digit:]]/; # match '=item',
# followed by a space and a digit
/\s+[abc\p{IsDigit}xyz]\s+/; # match a,b,c,x,y,z, or a digit
/^=item\s\p{IsDigit}/; # match '=item',
# followed by a space and a digit Уф! Это всё остальное по поводу символов и классов символов.
Компиляция и сохранение регулярных выражений
В части 1 мы упомянули, что Perl компилирует regexp в компактную последовательность кодов операций. Таким образом, скомпилированное регулярное выражение — это структура данных, которая может быть сохранена один раз и использована снова и снова. Квотирование регулярного выражения qr// делает именно это: qr/string/ компилирует string как regexp и преобразует результат в форму, которая может быть назначена переменной:
$reg = qr/foo+bar?/; # reg contains a compiled regexp Затем $reg может использоваться как regexp:
$x = "fooooba";
$x =~ $reg; # matches, just like /foo+bar?/
$x =~ /$reg/; # same thing, alternate form $reg также можно интерполировать в более крупное регулярное выражение:
$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,}?. Большинство расширений ниже имеют вид (?char...), где char — символ, определяющий тип расширения.
Первое расширение — вложенный комментарий (?#text). Это встраивает комментарий в регулярное выражение без влияния на его смысл. Комментарий не должен содержать никаких закрывающих скобок в тексте. Пример:
/(?# Match an integer:)[+-]?\d+/; Этот стиль комментирования в значительной степени устарел в пользу сырого, свободноформатного комментирования, разрешённого модификатором /x.
Большинство модификаторов, таких как /i, /m, /s и /x (или любое их сочетание) также могут быть встроены в regexp с помощью (?i), (?m), (?s), и (?x). Например,
/(?i)yes/; # match 'yes' case insensitively
/yes/i; # same thing
/(?x)( # freeform version of an integer regexp
[+-]? # match an optional sign
\d+ # match a sequence of digits
)
/x; Встроенные модификаторы могут иметь два важных преимущества перед обычными модификаторами. Встроенные модификаторы позволяют настраивать набор модификаторов для каждого шаблона регулярных выражений. Это отлично подходит для сопоставления массива регулярных выражений, которые должны иметь разные модификаторы:
$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 без учёта регистра и отключает многострочный режим.
Просмотр вперёд и назад
В этом разделе рассматриваются утверждения lookahead и lookbehind. Сначала немного предыстории.
В регулярных выражениях Perl большинство элементов «поглощают» определенное количество символов строки при совпадении. Например, элемент регулярного выражения [abc] поглощает один символ строки при совпадении, в том смысле, что Perl переходит к следующей позиции символа в строке после совпадения. Однако существуют некоторые элементы, которые не поглощают символы (не перемещают позицию символа), если они совпадают. Примерами, которые мы видели до сих пор, являются якоря. Якорь '^' соответствует началу строки, но не поглощает никаких символов. Аналогично, якорь границ слов \b соответствует позиции, где символ, соответствующий \w, находится рядом с символом, который не соответствует, но сам не поглощает символов. Якоря являются примерами утверждений нулевой ширины: нулевой ширины, потому что они не потребляют символы, и утверждений, потому что они проверяют некоторое свойство строки. В контексте нашей аналогии с походом по лесу для сопоставления регулярных выражений большинство элементов регулярных выражений перемещают нас по тропе, но якоря заставляют нас ненадолго остановиться и осмотреться. Если местная среда удовлетворяет нас, мы можем продолжить движение. Но если местная среда не удовлетворяет нас, мы должны вернуться назад.
Проверка среды подразумевает либо просмотр вперёд по тропе, либо просмотр назад, либо и то, и другое. '^' смотрит назад, чтобы убедиться, что перед ним нет символов. '$' смотрит вперёд, чтобы убедиться, что после него нет символов. \b смотрит и вперёд, и назад, чтобы увидеть, отличаются ли символы по обе стороны по своему «словесному» характеру.
Утверждения lookahead и lookbehind являются обобщениями концепции якорей. Lookahead и lookbehind — это утверждения нулевой ширины, которые позволяют нам указать, какие символы мы хотим проверить. Утверждение lookahead обозначается (?=regexp), а утверждение lookbehind обозначается (?<=fixed-regexp). Вот несколько примеров:
$x = "I catch the housecat 'Tom-cat' with catnip";
$x =~ /cat(?=\s)/; # matches 'cat' in 'housecat'
@catwords = ($x =~ /(?<=\s)cat\w+/g); # matches,
# $catwords[0] = 'catch'
# $catwords[1] = 'catnip'
$x =~ /\bcat\b/; # matches 'cat' in 'Tom-cat'
$x =~ /(?<=\s)cat(?=\s)/; # doesn't match; no isolated 'cat' in
# middle of $x Обратите внимание, что скобки в (?=regexp) и (?<=regexp) являются незахватывающими, поскольку это утверждения нулевой ширины. Таким образом, во втором регулярном выражении подстроки, которые захватятся, — это подстроки всего регулярного выражения. Lookahead (?=regexp) может соответствовать произвольным регулярным выражениям, но lookbehind (?<=fixed-regexp) работает только для регулярных выражений фиксированной длины, т. е., имеющих фиксированное количество символов. Таким образом, (?<=(ab|bc)) в порядке, но (?<=(ab)*) нет. Отрицательные версии утверждений lookahead и lookbehind обозначаются соответственно (?!regexp) и (?<!fixed-regexp). Они оцениваются как истинные, если регулярные выражения не совпадают:
$x = "foobar";
$x =~ /foo(?!bar)/; # doesn't match, 'bar' follows 'foo'
$x =~ /foo(?!baz)/; # matches, 'baz' doesn't follow 'foo'
$x =~ /(?<!\s)foo/; # matches, there is no \s before 'foo' Вот пример, где строка, содержащая слова, разделенные пробелами, числа и одиночные дефисы, должна быть разделена на свои компоненты. Использование только /\s+/ не сработает, так как пробелы не требуются между дефисами, или словом и дефисом. Дополнительные места для разделения создаются с помощью lookahead и lookbehind:
$str = "one two - --6-8";
@toks = split / \s+ # a run of spaces
| (?<=\S) (?=-) # any non-space followed by '-'
| (?<=-) (?=\S) # a '-' followed by any non-space
/x, $str; # @toks = qw(one two - - - 6 - 8) Начиная с Perl 5.28, экспериментально добавлены буквенные эквиваленты этих утверждений, так что вы можете использовать тот, который вам больше нравится.
(?=...) (*pla:...) or (*positive_lookahead:...)
(?!...) (*nla:...) or (*negative_lookahead:...)
(?<=...) (*plb:...) or (*positive_lookbehind:...)
(?<!...) (*nlb:...) or (*negative_lookbehind:...)
(?>...) (*atomic:...) Использование любого из этих элементов приведет к появлению предупреждения (если оно не отключено) в категории experimental::alpha_assertions.
Использование независимых подвыражений для предотвращения обратного отслеживания
Независимые подвыражения — это регулярные выражения, которые в контексте более крупного регулярного выражения работают независимо от него. То есть они потребляют столько или так мало символов строки, сколько им нужно, не обращая внимания на способность большего регулярного выражения совпадать. Независимые подвыражения представлены как (?>regexp). Мы можем проиллюстрировать их поведение, сначала рассмотрев обычное регулярное выражение:
$x = "ab";
$x =~ /a*ab/; # matches Это очевидно соответствует, но в процессе соответствия подвыражение a* сначала захватило 'a'. Однако это не позволит всему регулярному выражению совпасть, поэтому после обратного отслеживания a* в конечном итоге вернет 'a' и найдет совпадение с пустой строкой. Здесь то, что соответствовало a*, зависело от того, с чем совпало остальное регулярное выражение.
В отличие от этого, с независимым подвыражением:
$x =~ /(?>a*)ab/; # doesn't match! Независимое подвыражение (?>a*) не заботится о остальной части регулярного выражения, поэтому оно видит 'a' и захватывает его. Затем остальная часть регулярного выражения ab не может совпасть. Поскольку (?>a*) независимо, обратного отслеживания нет, и независимое подвыражение не отказывается от своего 'a'. Таким образом, соответствие регулярного выражения в целом терпит неудачу. Подобное поведение наблюдается и с полностью независимыми регулярными выражениями:
$x = "ab";
$x =~ /a*/g; # matches, eats an 'a'
$x =~ /\Gab/g; # doesn't match, no 'a' available Здесь /g и \G создают «командную передачу» строки из одного регулярного выражения в другое. Регулярные выражения с независимым подвыражением похожи на это, с передачей строки независимому подвыражению и передачей строки обратно включающему регулярному выражению.
Способность независимого подвыражения предотвращать обратное отслеживание может быть очень полезной. Предположим, мы хотим найти непустую строку в скобках, вложенную до двух уровней вглубь. Тогда следующее регулярное выражение соответствует:
$x = "abc(de(fg)h"; # unbalanced parentheses
$x =~ /\( ( [ ^ () ]+ | \( [ ^ () ]* \) )+ \)/xx; Регулярное выражение соответствует открывающей скобке, одному или нескольким экземплярам альтернативного выражения и закрывающей скобке. Альтернатива двусторонняя, при этом первое альтернативное выражение [^()]+ соответствует подстроке без скобок, а второе альтернативное выражение \([^()]*\) соответствует подстроке, ограниченной скобками. Проблема с этим регулярным выражением в том, что оно патологическое: оно имеет вложенные неопределённые квантификаторы вида (a+|b)+. Мы обсуждали в Части 1, как вложенные квантификаторы такого типа могут занимать экспоненциально много времени для выполнения, если совпадения нет. Чтобы предотвратить экспоненциальный взрыв, нам нужно предотвратить бесполезное обратное отслеживание в какой-то момент. Это можно сделать, поместив внутренний квантификатор в качестве независимого подвыражения:
$x =~ /\( ( (?> [ ^ () ]+ ) | \([ ^ () ]* \) )+ \)/xx; Здесь (?>[^()]+) нарушает вырождение разделения строк, поглощая как можно больше символов строки и сохраняя их. Тогда неудачи соответствия происходят намного быстрее.
Условные выражения
Условное выражение — это форма оператора if-then-else, который позволяет выбирать, какие шаблоны подлежат соответствию на основе некоторого условия. Существует два типа условных выражений: (?(condition)yes-regexp) и (?(condition)yes-regexp|no-regexp). (?(condition)yes-regexp) похож на оператор 'if () {}' в Perl. Если условие истинно, будет найдено совпадение с regexp-выражением yes. Если условие ложно, regexp-выражение yes будет пропущено, и Perl перейдёт к следующему элементу регулярного выражения. Второй вариант похож на оператор 'if () {} else {}' в Perl. Если условие истинно, будет найдено совпадение с regexp-выражением yes, иначе будет найдено совпадение с regexp-выражением no.
Условие может иметь несколько форм. Первая форма — это просто целое число в скобках (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 в регулярном выражении
Обычно, регулярные выражения (regexp) являются частью выражений 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' Чудо, упомянутое в заголовке раздела, происходит, когда регулярное выражение возвращается назад (backtracks) в процессе поиска совпадения. Если регулярное выражение возвращается назад над выражением кода, и если используемые переменные локализованы с помощью 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;}), изменения переменных не отменяются при возвращении назад (backtracking), и мы получаем
'a' count is 4, $c variable is 'bob' Обратите внимание, что только локализованные изменения переменных отменяются. Другие побочные эффекты выполнения выражения кода остаются постоянными. Таким образом
$x = "aaaa";
$x =~ /(a(?{print "Yow\n";}))*aa/; выводит
Yow
Yow
Yow
Yow Результат $^R автоматически локализуется, чтобы он правильно работал при наличии возврата назад.
Этот пример использует выражение кода в условии для сопоставления определённого артикля, либо 'the' на английском языке, либо 'der|die|das' на немецком языке:
$lang = 'DE'; # use German
...
$text = "das";
print "matched\n"
if $text =~ /(?(?{
$lang eq 'EN'; # is the language English?
})
the | # if so, then match 'the'
(der|die|das) # else, match 'der|die|das'
)
/xi; Обратите внимание, что здесь синтаксис (?(?{...})yes-regexp|no-regexp), а не (?((?{...}))yes-regexp|no-regexp). Другими словами, в случае выражения кода нам не нужны дополнительные скобки вокруг условия.
Если вы попытаетесь использовать выражения кода, где текст кода содержится в интерполированной переменной, а не появляется буквально в шаблоне, Perl может вас удивить:
$bar = 5;
$pat = '(?{ 1 })';
/foo(?{ $bar })bar/; # compiles ok, $bar not interpolated
/foo(?{ 1 })$bar/; # compiles ok, $bar interpolated
/foo${pat}bar/; # compile error!
$pat = qr/(?{ $foo = 1 })/; # precompile code regexp
/foo${pat}bar/; # compiles ok Если в регулярном выражении есть переменная, интерполирующая выражение кода, Perl обрабатывает регулярное выражение как ошибку. Однако, если выражение кода предварительно скомпилировано в переменную, интерполяция разрешена. Вопрос в том, почему это ошибка?
Причина в том, что интерполяция переменных и выражения кода вместе представляют собой риск для безопасности. Такое сочетание опасно, потому что многие программисты, пишущие поисковые системы, часто берут пользовательский ввод и подключают его непосредственно в регулярное выражение:
$regexp = <>; # read user-supplied regexp
$chomp $regexp; # get rid of possible newline
$text =~ /$regexp/; # search $text for the $regexp Если переменная $regexp содержит выражение кода, пользователь может выполнить произвольный Perl-код. Например, какой-нибудь шутник мог бы искать system('rm -rf *');, чтобы стереть ваши файлы. В этом смысле сочетание интерполяции и выражений кода загрязняет ваше регулярное выражение. Таким образом, по умолчанию использование как интерполяции, так и выражений кода в одном регулярном выражении не разрешено. Если вы не беспокоитесь о злонамеренных пользователях, можно обойти эту проверку безопасности, вызвав use re 'eval':
use re 'eval'; # throw caution out the door
$bar = 5;
$pat = '(?{ 1 })';
/foo${pat}bar/; # compiles ok Другой формой выражения кода является выражение кода шаблона. Выражение кода шаблона подобно обычному выражению кода, за исключением того, что результат вычисления кода обрабатывается как регулярное выражение и немедленно сопоставляется. Простой пример —
$length = 5;
$char = 'a';
$x = 'aaaaabb';
$x =~ /(??{$char x $length})/x; # matches, there are 5 of 'a' Этот последний пример содержит как обычные, так и выражения кода шаблона. Он определяет, имеет ли двоичная строка 1101010010001... фибоначчиевское расстояние 0,1,1,2,3,5,... от '1':
$x = "1101010010001000001";
$z0 = ''; $z1 = '0'; # initial conditions
print "It is a Fibonacci sequence\n"
if $x =~ /^1 # match an initial '1'
(?:
((??{ $z0 })) # match some '0'
1 # and then a '1'
(?{ $z0 = $z1; $z1 .= $^N; })
)+ # repeat as needed
$ # that is all there is
/x;
printf "Largest sequence matched was %d\n", length($z1)-length($z0); Помните, что $^N устанавливается в значение, которое было сопоставлено последней завершённой группой захвата. Это печатает
It is a Fibonacci sequence
Largest sequence matched was 5 Ха! Попробуйте это с обычным регулярным выражением...
Обратите внимание, что переменные $z0 и $z1 не подставляются при компиляции регулярного выражения, как это происходит для обычных переменных вне выражения кода. Вместо этого весь блок кода парсится как код Perl одновременно с компиляцией Perl-кода, содержащего буквальный шаблон регулярного выражения.
Это регулярное выражение без модификатора /x равно
/^1(?:((??{ $z0 }))1(?{ $z0 = $z1; $z1 .= $^N; }))+$/ что показывает, что пробелы всё ещё возможны в частях кода. Тем не менее, при работе с кодом и условными выражениями расширенная форма регулярных выражений практически необходима при создании и отладке регулярных выражений.
Управляющие глаголы возврата назад
Perl 5.10 ввёл ряд управляющих глаголов, предназначенных для предоставления подробного контроля над процессом возврата назад (backtracking), путём прямого влияния на движок регулярных выражений и предоставления методов мониторинга. Подробное описание см. в разделе "Специальные управляющие глаголы возврата назад" в perlre.
Ниже приведён всего один пример, иллюстрирующий управляющий глагол (*FAIL), который может быть сокращён до (*F). Если это вставить в регулярное выражение, это приведёт к его отказу, точно так же, как это происходило бы при несоответствии шаблона и строки. Обработка регулярного выражения продолжается так же, как и после любой «обычной» ошибки, так что, например, будет пробоваться следующая позиция в строке или другая альтернатива. Поскольку провал совпадения не сохраняет группы захвата или не генерирует результаты, может потребоваться использовать его в сочетании с вложенным кодом.
%count = ();
"supercalifragilisticexpialidocious" =~
/([aeiou])(?{ $count{$1}++; })(*FAIL)/i;
printf "%3d '%s'\n", $count{$_}, $_ for (sort keys %count); Шаблон начинается с класса, сопоставляющего подмножество букв. Всякий раз, когда это совпадает, выполняется оператор, подобный $count{'a'}++;, увеличивающий счётчик буквы. Затем (*FAIL) делает то, что говорится, и движок регулярных выражений следует правилам: пока не достигнута граница строки, позиция переводится вперёд, прежде чем искать другую гласную. Таким образом, совпадение или нет, не имеет значения, и движок регулярных выражений продолжает работу, пока вся строка не будет проверена. (Примечательно, что альтернативное решение, использующее что-то вроде
$count{lc($_)}++ for split('', "supercalifragilisticexpialidocious");
printf "%3d '%s'\n", $count2{$_}, $_ for ( qw{ a e i o u } ); значительно медленнее.)
Прагмы и отладка
Говоря об отладке, в Perl доступно несколько прагм для управления и отладки регулярных выражений. Мы уже сталкивались с одной прагмой в предыдущем разделе, use re 'eval';, которая позволяет переменной интерполяции и выражениям кода сосуществовать в регулярном выражении. Другие прагмы:
use re 'taint';
$tainted = <>;
@parts = ($tainted =~ /(\w+)\s+(\w+)/; # @parts is now tainted Прагма taint заставляет все подстроки из совпадения с заражённой переменной быть заражёнными тоже. Обычно это не так, так как регулярные выражения часто используются для извлечения безопасных частей из заражённой переменной. Используйте taint когда вы не извлекаете безопасные части, а выполняете другую обработку. Прагмы taint и eval имеют лексическое охватывание, что означает, что они действуют только до конца блока, содержащего прагмы.
use re '/m'; # or any other flags
$multiline_string =~ /^foo/; # /m is implied Прагма re '/flags' (введённая в Perl 5.14) включает заданные флаги регулярного выражения до конца лексического охвата. Более подробную информацию см. в разделе "'/flags' режим" в re.
use re 'debug';
/^(.*)$/s; # output debugging info
use re 'debugcolor';
/^(.*)$/s; # output debugging info in living color Глобальные прагмы debug и debugcolor позволяют получить подробную отладочную информацию о компиляции и выполнении регулярных выражений. debugcolor эквивалентно debug, за исключением того, что отладочная информация отображается в цвете на терминалах, способных отображать цветовые последовательности termcap. Вот пример вывода:
% perl -e 'use re "debug"; "abc" =~ /a*b+c/;'
Compiling REx 'a*b+c'
size 9 first at 1
1: STAR(4)
2: EXACT <a>(0)
4: PLUS(7)
5: EXACT <b>(0)
7: EXACT <c>(9)
9: END(0)
floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0
Matching REx 'a*b+c' against 'abc'
Setting an EVAL scope, savestack=3
0 <> <abc> | 1: STAR
EXACT <a> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
1 <a> <bc> | 4: PLUS
EXACT <b> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
2 <ab> <c> | 7: EXACT <c>
3 <abc> <> | 9: END
Match successful!
Freeing REx: 'a*b+c' Если вы дошли до этой части руководства, вы, вероятно, можете догадаться, что означают различные части отладочного вывода. Первая часть
Compiling REx 'a*b+c'
size 9 first at 1
1: STAR(4)
2: EXACT <a>(0)
4: PLUS(7)
5: EXACT <b>(0)
7: EXACT <c>(9)
9: END(0) описывает этап компиляции. STAR(4) означает, что есть отмеченный объект, в данном случае 'a', и если он совпадёт, перейти к строке 4, то есть PLUS(7). Средние строки описывают некоторые эвристики и оптимизации, выполненные до совпадения:
floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0 Затем выполняется сопоставление, а оставшиеся строки описывают этот процесс:
Matching REx 'a*b+c' against 'abc'
Setting an EVAL scope, savestack=3
0 <> <abc> | 1: STAR
EXACT <a> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
1 <a> <bc> | 4: PLUS
EXACT <b> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
2 <ab> <c> | 7: EXACT <c>
3 <abc> <> | 9: END
Match successful!
Freeing REx: 'a*b+c' Каждый шаг имеет вид n <x> <y>, где <x> - часть строки, которая сопоставляется, и <y> - часть, которая ещё не сопоставлена. | 1: STAR говорит, что Perl находится на строке 1 в списке компиляции выше. Более подробную информацию см. в разделе "Отладка регулярных выражений" в perldebguts.
Альтернативный метод отладки регулярных выражений — встраивание print операторов в регулярное выражение. Это обеспечивает подробный отчёт о возврате назад при использовании альтернативы:
"that this" =~ m@(?{print "Start at position ", pos, "\n";})
t(?{print "t1\n";})
h(?{print "h1\n";})
i(?{print "i1\n";})
s(?{print "s1\n";})
|
t(?{print "t2\n";})
h(?{print "h2\n";})
a(?{print "a2\n";})
t(?{print "t2\n";})
(?{print "Done at position ", pos, "\n";})
@x; выводит
Start at position 0
t1
h1
t2
h2
a2
t2
Done at position 4 См. также
Это всего лишь учебное пособие. Для получения полной информации о регулярных выражениях Perl см. страницу справки по регулярным выражениям perlre.
Для получения дополнительной информации об операторах сопоставления m// и подстановки s///, см. "Regexp Quote-Like Operators" в perlop. Сведения об операции split см. в "split" в perlfunc.
Для получения отличного справочника по работе с регулярными выражениями обратитесь к книге Mastering Regular Expressions Джеффри Фридла (издана O'Reilly, ISBN 1556592-257-3).
АВТОР И АВТОРСКИЕ ПРАВА
Авторские права (c) 2000 Марк Квале. Все права защищены. Сейчас поддерживается Perl-разработчиками.
Данный документ может быть распространен на тех же условиях, что и Perl сам по себе.
Благодарности
Вдохновение для примера стоп-кодона ДНК взято из примера с почтовыми индексами в главе 7 книги Mastering Regular Expressions.
Автор выражает благодарность Джеффу Пиньяну, Эндрю Джонсону, Питеру Хоуорту, Рональду Дж. Кимбаллу и Джо Смиту за все их ценные замечания.
© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.30.3/perlretut