perlretut
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ОПИСАНИЕ
- Часть 1: Основы
- Простая проверка соответствия словам
- Использование символьных классов
- Сопоставление этому или тому
- Группировка элементов и иерархическое сопоставление
- Извлечение совпадений
- Обратные ссылки
- Относительные обратные ссылки
- Именованные обратные ссылки
- Альтернативная нумерация групп захвата
- Информация о позиции
- Незахватывающие группировки
- Сопоставление повторений
- Позиционные квантификаторы
- Создание regexp
- Использование регулярных выражений в 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/ Значение этого выражения будет ИСТИНА, если $var содержит эту последовательность символов, и ЛОЖЬ в противном случае. Часть, заключённая в '/' символы, обозначает искомую характеристику. Мы используем термин шаблон для этого. Процесс поиска в строке наличия шаблона называется сопоставлением, а "=~" оператор вместе с m// сообщает Perl о попытке сопоставить шаблон со строкой. Обратите внимание, что шаблон также является строкой, но очень специального вида, как мы увидим. Шаблоны широко используются в наши дни; примерами являются шаблоны, вводимые в поисковую систему для поиска веб-страниц, и шаблоны для перечисления файлов в каталоге, например, "ls *.txt" или "dir *.*". В Perl шаблоны, описанные регулярными выражениями, используются не только для поиска строк, но и для извлечения необходимых частей строк, а также для выполнения операций поиска и замены.
Регулярные выражения имеют незаслуженную репутацию абстрактных и сложных для понимания. Это в основном связано с тем, что обозначение, используемое для их выражения, имеет тенденцию быть кратким и плотным, а не с внутренней сложностью. Мы рекомендуем использовать /x модификатор регулярных выражений (описанный ниже) вместе с достаточным количеством пробелов, чтобы сделать их менее плотными и более удобными для чтения. Регулярные выражения строятся с использованием простых понятий, таких как условные операторы и циклы, и не сложнее для понимания, чем соответствующие if условные операторы и while циклы в самом языке Perl.
Это руководство сглаживает кривую обучения, обсуждая понятия регулярных выражений вместе с их обозначением по одному за раз и с множеством примеров. Первая часть руководства будет проходить от самых простых поисков слов до основных понятий регулярных выражений. Если вы освоите первую часть, у вас будет весь необходимый инструмент для решения около 98% ваших задач. Вторая часть руководства предназначена для тех, кто уже знаком с основами и жаждет более мощных инструментов. В ней обсуждаются более сложные операторы регулярных выражений и представляются новейшие инновации.
Примечание: для экономии времени «регулярное выражение» часто сокращается до regexp или regex. Regexp — более естественное сокращение, но его сложнее произносить. В документации Perl под документацией regexp и regex разделены поровну; в Perl есть не один способ сократить его. В этом руководстве мы будем использовать regexp.
В версии 5.22, use re 'strict' применяет более строгие правила при компиляции шаблонов регулярных выражений. Это может обнаруживать то, что, хотя и законно, может не соответствовать вашим намерениям.
Часть 1: Основы
Простая проверка соответствия словам
Самое простое regexp — это просто слово, или, более обобщенно, строка символов. regexp, состоящий только из слова, соответствует любой строке, содержащей это слово:
"Hello World" =~ /World/; # matches О чём говорит эта команда Perl? "Hello World" — это простая строка в двойных кавычках. World — это регулярное выражение, а // окружающие /World/ символы говорят Perl искать соответствие в строке. Оператор =~ связывает строку с совпадением regexp и возвращает значение истины, если regexp соответствует, или ложь, если regexp не соответствует. В нашем случае, World соответствует второму слову в "Hello World", поэтому выражение истинно. Такие выражения полезны в условных операторах:
if ("Hello World" =~ /World/) {
print "It matches\n";
}
else {
print "It doesn't match\n";
} Существуют полезные вариации на эту тему. Значение соответствия можно перевернуть, используя оператор !~:
if ("Hello World" !~ /World/) {
print "It doesn't match\n";
}
else {
print "It matches\n";
} Литеральная строка в regexp может быть заменена переменной:
my $greeting = "World";
if ("Hello World" =~ /$greeting/) {
print "It matches\n";
}
else {
print "It doesn't match\n";
} Если вы сопоставляете со специальной переменной по умолчанию $_, часть $_ =~ можно опустить:
$_ = "Hello World";
if (/World/) {
print "It matches\n";
}
else {
print "It doesn't match\n";
} И, наконец, // символы-разделители по умолчанию для соответствия могут быть изменены на произвольные, поместив 'm' перед ними:
"Hello World" =~ m!World!; # matches, delimited by '!'
"Hello World" =~ m{World}; # matches, note the matching '{}'
"/usr/bin/perl" =~ m"/perl"; # matches after '/usr/bin',
# '/' becomes an ordinary char /World/, m!World!, и m{World} всё представляют одно и то же. Когда, например, кавычка ('"') используется как разделитель, обратный слэш '/' становится обычным символом и может использоваться в этом regexp без проблем.
Давайте рассмотрим, как различные regexp будут соответствовать "Hello World":
"Hello World" =~ /world/; # doesn't match
"Hello World" =~ /o W/; # matches
"Hello World" =~ /oW/; # doesn't match
"Hello World" =~ /World /; # doesn't match Первое regexp world не соответствует, потому что regexp чувствительны к регистру. Второе regexp соответствует, потому что подстрока 'o W' встречается в строке "Hello World". Пробельный символ ' ' рассматривается как любой другой символ в regexp и необходим для сопоставления в данном случае. Отсутствие пробельного символа — причина, по которой третье regexp 'oW' не соответствует. Четвёртое regexp "World " не соответствует, потому что в regexp есть пробел в конце, но не в конце строки. Урок здесь в том, что regexp должны точно соответствовать части строки, чтобы утверждение было истинным.
Если regexp соответствует в нескольких местах в строке, Perl всегда будет соответствовать в самом раннем возможном месте в строке:
"Hello World" =~ /o/; # matches 'o' in 'Hello'
"That hat is red" =~ /hat/; # matches 'hat' in 'That' Что касается сопоставления символов, есть ещё несколько моментов, которые вам нужно знать. Во-первых, не все символы могут использоваться «как есть» в сопоставлении. Некоторые символы, называемые метасимволами, обычно зарезервированы для использования в обозначении regexp. Метасимволы —
{}[]()^$.|*+?-#\ Этот список не так определён, как может показаться (или как утверждается в другой документации). Например, "#" является метасимволом только тогда, когда используется модификатор шаблона /x (описанный ниже), а "}" и "]" являются метасимволами только при парной записи с открывающими "{" или "[" соответственно; также есть другие особенности.
Значение каждого из них будет объяснено в остальной части учебника, но пока важно только знать, что метасимвол можно сопоставить таким же образом, поставив перед ним обратную косую черту:
"2+2=4" =~ /2+2/; # doesn't match, + is a metacharacter
"2+2=4" =~ /2\+2/; # matches, \+ is treated like an ordinary +
"The interval is [0,1)." =~ /[0,1)./ # is a syntax error!
"The interval is [0,1)." =~ /\[0,1\)\./ # matches
"#!/usr/bin/perl" =~ /#!\/usr\/bin\/perl/; # matches В последнем регулярном выражении слэш '/' также экранирован, потому что он используется для разграничения регулярного выражения. Однако это может привести к синдрому LTS (синдрому наклонной зубочистки), и часто более читабельно изменить разделители.
"#!/usr/bin/perl" =~ m!#\!/usr/bin/perl!; # easier to read Символ обратной косой черты '\' сам является метасимволом и требует экранирования:
'C:\WIN32' =~ /C:\\WIN/; # matches В ситуациях, когда для определённого метасимвола бессмысленно иметь то же значение, что и обычно, он автоматически теряет своё свойство метасимвола и становится обычным символом, который необходимо сопоставить буквально. Например, '}' является метасимволом только тогда, когда он является партнёром метасимвола '{'. В противном случае он обрабатывается как буквальная ПРАВАЯ ФИГУРНАЯ СКОБКА. Это может привести к неожиданным результатам. use re 'strict' может поймать некоторые из них.
В дополнение к метасимволам существуют некоторые символы ASCII, у которых нет эквивалентов печатных символов, и они представлены последовательностями экранирования. Общие примеры: \t для табуляции, \n для новой строки, \r для возврата каретки и \a для звонка (или сигнала). Если ваша строка лучше рассматривается как последовательность произвольных байтов, октальная последовательность экранирования, например, \033, или шестнадцатеричная последовательность экранирования, например, \x1B могут быть более естественным представлением ваших байтов. Вот несколько примеров экранирования:
"1000\t2000" =~ m(0\t2) # matches
"1000\n2000" =~ /0\n20/ # matches
"1000\t2000" =~ /\000\t2/ # doesn't match, "0" ne "\000"
"cat" =~ /\o{143}\x61\x74/ # matches in ASCII, but a weird way
# to spell cat Если вы долго работаете с Perl, все эти разговоры о последовательностях экранирования могут показаться знакомыми. Аналогичные последовательности экранирования используются в строках с двойными кавычками, и на самом деле регулярные выражения в Perl в основном обрабатываются как строки с двойными кавычками. Это означает, что переменные также могут использоваться в регулярных выражениях. Точно так же, как и в строках с двойными кавычками, значения переменных в регулярном выражении будут заменены перед тем, как регулярное выражение будет проверено на соответствие.
$foo = 'house';
'housecat' =~ /$foo/; # matches
'cathouse' =~ /cat$foo/; # matches
'housecat' =~ /${foo}cat/; # matches Всё хорошо. С приведенными выше знаниями вы уже можете выполнять поиск с использованием почти любого регулярного выражения с литерными строками, которые вы можете придумать. Вот очень простая эмуляция программы Unix grep:
% cat > simple_grep
#!/usr/bin/perl
$regexp = shift;
while (<>) {
print if /$regexp/;
}
^D
% chmod +x simple_grep
% simple_grep abba /usr/dict/words
Babbage
cabbage
cabbages
sabbath
Sabbathize
Sabbathizes
sabbatical
scabbard
scabbards Эта программа легко понять. #!/usr/bin/perl — стандартный способ вызова программы perl из оболочки. $regexp = shift; сохраняет первый аргумент командной строки как регулярное выражение, используемое для обработки, оставляя остальные аргументы командной строки как файлы. while (<>) проходит по всем строкам во всех файлах. Для каждой строки print if /$regexp/; печатает строку, если регулярное выражение соответствует строке. В этой строке как print, так и /$regexp/ используют по умолчанию переменную $_ неявно.
При всех вышеприведённых регулярных выражениях, если регулярное выражение совпадало где-либо в строке, оно считалось совпадением. Иногда, однако, нам нужно указать где в строке регулярное выражение должно пытаться совпадать. Для этого мы будем использовать метасимволы якорных значений '^' и '$'. Якорное значение '^' означает совпадение в начале строки, а якорное значение '$' означает совпадение в конце строки или перед новой строкой в конце строки. Вот как они используются:
"housekeeper" =~ /keeper/; # matches
"housekeeper" =~ /^keeper/; # doesn't match
"housekeeper" =~ /keeper$/; # matches
"housekeeper\n" =~ /keeper$/; # matches Второе регулярное выражение не совпадает, потому что '^' ограничивает keeper совпадением только в начале строки, но "housekeeper" начинается посередине. Третье регулярное выражение соответствует, так как '$' ограничивает keeper совпадением только в конце строки.
Когда оба '^' и '$' используются одновременно, регулярное выражение должно совпадать как в начале, так и в конце строки, то есть регулярное выражение соответствует всей строке. Рассмотрим
"keeper" =~ /^keep$/; # doesn't match
"keeper" =~ /^keeper$/; # matches
"" =~ /^$/; # ^$ matches an empty string Первое регулярное выражение не совпадает, потому что в строке больше, чем keep. Поскольку второе регулярное выражение является ровно той же строкой, оно совпадает. Использование как '^', так и '$' в регулярном выражении принуждает к полному совпадению всей строки, так что это даёт вам полный контроль над тем, какие строки совпадают, а какие нет. Предположим, вы ищете человека по имени Берт, который стоит отдельно в строке:
"dogbert" =~ /bert/; # matches, but not what you want
"dilbert" =~ /^bert/; # doesn't match, but ..
"bertram" =~ /^bert/; # matches, so still not good enough
"bertram" =~ /^bert$/; # doesn't match, good
"dilbert" =~ /^bert$/; # doesn't match, good
"bert" =~ /^bert$/; # matches, perfect Конечно, в случае литерной строки можно было бы просто использовать сравнение строк $string eq 'bert', и это было бы более эффективно. Регулярное выражение ^...$ действительно становится полезным, когда мы добавляем более мощные инструменты регулярных выражений ниже.
Использование классов символов
Хотя с литерными строками регулярных выражений выше можно уже сделать довольно много, мы только скользнули по поверхности технологии регулярных выражений. В этой и последующих разделах мы представим понятия регулярных выражений (и связанные с ними обозначения метасимволов), которые позволят регулярному выражению представлять не только последовательность отдельных символов, но и весь класс символов.
Одним из таких понятий является класс символов. Класс символов позволяет набору возможных символов, а не одному символу, соответствовать в определённой точке регулярного выражения. Вы можете определять свои собственные пользовательские классы символов. Они обозначаются квадратными скобками [...], с набором символов, которые должны быть возможными для сопоставления внутри.
/cat/; # matches 'cat'
/[bcr]at/; # matches 'bat, 'cat', or 'rat'
/item[0123456789]/; # matches 'item0' or ... or 'item9'
"abc" =~ /[cab]/; # matches 'a' В последнем утверждении, хотя 'c' является первым символом в классе, 'a' соответствует, потому что первая позиция символа в строке является самой ранней точкой, в которой регулярное выражение может сопоставить.
/[yY][eE][sS]/; # match 'yes' in a case-insensitive way
# 'yes', 'Yes', 'YES', etc. Это регулярное выражение демонстрирует распространённую задачу: выполнить сопоставление без учёта регистра. Perl предоставляет способ избежать всех этих квадратных скобок, просто добавив 'i' в конец сопоставления. Тогда /[yY][eE][sS]/; можно переписать как /yes/i;. 'i' обозначает сопоставление без учёта регистра и является примером модификатора операции сопоставления. Мы встретим другие модификаторы позже в учебнике.
Мы видели в предыдущем разделе, что есть обычные символы, которые представляют сами себя, и специальные символы, которые требуют обратной косой черты '\' для представления самих себя. То же самое верно и в классе символов, но наборы обычных и специальных символов внутри класса символов отличаются от тех, что находятся вне класса символов. Специальными символами для класса символов являются -]\^$ (и разделитель шаблона, какой бы он ни был). ']' является специальным, потому что он обозначает конец класса символов. '$' является специальным, потому что он обозначает скалярную переменную. '\' является специальным, потому что он используется в последовательностях экранирования, как и выше. Вот как обрабатываются специальные символы ]$\.
/[\]c]def/; # matches ']def' or 'cdef'
$x = 'bcr';
/[$x]at/; # matches 'bat', 'cat', or 'rat'
/[\$x]at/; # matches '$at' or 'xat'
/[\\$x]at/; # matches '\at', 'bat, 'cat', or 'rat' Последние два немного сложны. В [\$x], обратная косая черта защищает знак доллара, поэтому в классе символов есть два члена '$' и 'x'. В [\\$x], обратная косая черта защищена, поэтому $x обрабатывается как переменная и подставляется в виде строки с двойными кавычками.
Специальный символ '-' действует как оператор диапазона в классах символов, так что непрерывный набор символов может быть записан как диапазон. С помощью диапазонов громоздкие [0123456789] и [abc...xyz] становятся изящными [0-9] и [a-z]. Вот некоторые примеры:
/item[0-9]/; # matches 'item0' or ... or 'item9'
/[0-9bx-z]aa/; # matches '0aa', ..., '9aa',
# 'baa', 'xaa', 'yaa', or 'zaa'
/[0-9a-fA-F]/; # matches a hexadecimal digit
/[0-9a-zA-Z_]/; # matches a "word" character,
# like those in a Perl variable name Если '-' является первым или последним символом в классе символов, он обрабатывается как обычный символ; [-ab], [ab-] и [a\-b] — все эквивалентны.
Специальный символ '^' в первой позиции класса символов обозначает отрицательный класс символов, который соответствует любому символу, кроме тех, что находятся в скобках. [...] и [^...] должны соответствовать символу, иначе совпадение не произойдёт. Тогда
/[^a]at/; # doesn't match 'aat' or 'at', but matches
# all other 'bat', 'cat, '0at', '%at', etc.
/[^0-9]/; # matches a non-numeric character
/[a^]at/; # matches 'aat' or '^at'; here '^' is ordinary Теперь даже [0-9] может быть проблематично писать несколько раз, поэтому в целях экономии нажатий клавиш и повышения читабельности регулярных выражений Perl имеет несколько сокращений для общих классов символов, как показано ниже. С появлением Unicode, если модификатор /a не активен, эти классы символов соответствуют не только нескольким символам в диапазоне ASCII.
-
\dсоответствует цифре, а не только[0-9], но также и цифрам из нелатинских скриптов -
\sсоответствует символу пробела, набору[\ \t\r\n\f]и другим -
\wсоответствует символу слова (буквенно-цифровому или'_'), а не только[0-9a-zA-Z_], но также и цифрам и символам из нелатинских скриптов -
\Dявляется отрицательным\d; он представляет любой символ, кроме цифры или[^\d] -
\Sявляется отрицательным\s; он представляет любой не-пробельный символ[^\s] -
\Wявляется отрицательным\w; он представляет любой не-символ слова[^\w] -
Точка
'.'соответствует любому символу, кроме"\n"(если модификатор/sне активен, как объяснено ниже). -
\N, подобно точке, соответствует любому символу, кроме"\n", но делает это независимо от того, активен ли модификатор/s.
Модификатор /a, доступный начиная с Perl 5.14, используется для ограничения сопоставлений \d, \s и \w только символами в диапазоне ASCII. Это полезно для предотвращения ненужного воздействия на программу полного Unicode (и связанных с ним проблем безопасности), когда всё, что вам нужно, это обработка текста, подобного английскому. (Буква «a» может быть удвоена, /aa, чтобы обеспечить ещё больше ограничений, предотвращая сопоставление без учёта регистра ASCII с не-ASCII символами; иначе символ Unicode «знак Кельвина» без учёта регистра будет соответствовать «k» или «K».)
Сокращения \d\s\w\D\S\W можно использовать как внутри, так и вне скобочных символьных классов. Вот несколько примеров их использования:
/\d\d:\d\d:\d\d/; # matches a hh:mm:ss time format
/[\d\s]/; # matches any digit or whitespace character
/\w\W\w/; # matches a word char, followed by a
# non-word char, followed by a word char
/..rt/; # matches any two chars, followed by 'rt'
/end\./; # matches 'end.'
/end[.]/; # same thing, matches 'end.' Поскольку точка является метасимволом, её необходимо экранировать, чтобы сопоставить её как обычную точку. Поскольку, например, \d и \w являются наборами символов, неправильно считать [^\d\w] как [\D\W]; фактически [^\d\w] равно [^\w], что равно [\W]. Подумайте о законах де Моргана.
На самом деле, точка и \d\s\w\D\S\W сокращения сами по себе являются типами символьных классов, поэтому те, которые окружены скобками, являются просто одним типом символьного класса. Когда нам нужно сделать различие, мы называем их «скобочными символьными классами».
Якор, полезный в базовых регулярных выражениях, — это якор слова \b. Он сопоставляет границу между символом слова и несимволом слова \w\W или \W\w:
$x = "Housecat catenates house and cat";
$x =~ /cat/; # matches cat in 'housecat'
$x =~ /\bcat/; # matches cat in 'catenates'
$x =~ /cat\b/; # matches cat in 'housecat'
$x =~ /\bcat\b/; # matches 'cat' at end of string Обратите внимание, что в последнем примере конец строки считается границей слова.
Для обработки естественного языка (например, для включения апострофов в слова) используйте вместо этого \b{wb}
"don't" =~ / .+? \b{wb} /x; # matches the whole string Вы можете задаться вопросом, почему '.' сопоставляет всё, кроме "\n" — почему не каждый символ? Причина в том, что часто сопоставление выполняется по строкам, и хотелось бы игнорировать символы новой строки. Например, хотя строка "\n" представляет одну строку, мы хотели бы рассматривать её как пустую. Тогда
"" =~ /^$/; # matches
"\n" =~ /^$/; # matches, $ anchors before "\n"
"" =~ /./; # doesn't match; it needs a char
"" =~ /^.$/; # doesn't match; it needs a char
"\n" =~ /^.$/; # doesn't match; it needs a char other than "\n"
"a" =~ /^.$/; # matches
"a\n" =~ /^.$/; # matches, $ anchors before "\n" Это поведение удобно, потому что обычно мы хотим игнорировать новые строки, когда считаем и сопоставляем символы в строке. Однако иногда мы хотим отслеживать новые строки. Мы даже можем '^' и '$' закрепиться в начале и конце строк внутри строки, а не только в начале и конце строки. Perl позволяет нам выбирать между игнорированием и учётом новых строк, используя модификаторы /s и /m.
/s и /m обозначают одну строку и многострочный режим, и они определяют, рассматривать ли строку как одну непрерывную строку или как набор строк. Два модификатора влияют на два аспекта интерпретации регулярного выражения: 1) как определяется '.' символьный класс, и 2) где якоря '^' и '$' могут совпадать. Вот четыре возможные комбинации:
-
Без модификаторов: По умолчанию.
'.'сопоставляет любой символ, кроме"\n".'^'сопоставляет только в начале строки, а'$'сопоставляет только в конце или перед новой строкой в конце. -
Модификатор s (
/s): Рассматривать строку как одну длинную строку.'.'сопоставляет любой символ, даже"\n".'^'сопоставляет только в начале строки, а'$'сопоставляет только в конце или перед новой строкой в конце. -
Модификатор m (
/m): Рассматривать строку как набор нескольких строк.'.'сопоставляет любой символ, кроме"\n".'^'и'$'могут сопоставляться в начале или конце любой строки в строке. -
Оба модификатора s и m (
/sm): Рассматривать строку как одну длинную строку, но обнаруживать несколько строк.'.'сопоставляет любой символ, даже"\n".'^'и'$', однако, могут сопоставляться в начале или конце любой строки в строке.
Вот примеры работы /s и /m:
$x = "There once was a girl\nWho programmed in Perl\n";
$x =~ /^Who/; # doesn't match, "Who" not at start of string
$x =~ /^Who/s; # doesn't match, "Who" not at start of string
$x =~ /^Who/m; # matches, "Who" at start of second line
$x =~ /^Who/sm; # matches, "Who" at start of second line
$x =~ /girl.Who/; # doesn't match, "." doesn't match "\n"
$x =~ /girl.Who/s; # matches, "." matches "\n"
$x =~ /girl.Who/m; # doesn't match, "." doesn't match "\n"
$x =~ /girl.Who/sm; # matches, "." matches "\n" В большинстве случаев поведение по умолчанию является желаемым, но /s и /m иногда очень полезны. Если используется /m , начало строки по-прежнему можно сопоставить с \A , а конец строки по-прежнему можно сопоставить с якорями \Z (сопоставляет конец и новую строку перед ним, как '$'), и \z (сопоставляет только конец):
$x =~ /^Who/m; # matches, "Who" at start of second line
$x =~ /\AWho/m; # doesn't match, "Who" is not at start of string
$x =~ /girl$/m; # matches, "girl" at end of first line
$x =~ /girl\Z/m; # doesn't match, "girl" is not at end of string
$x =~ /Perl\Z/m; # matches, "Perl" is at newline before end
$x =~ /Perl\z/m; # doesn't match, "Perl" is not at end of string Теперь мы знаем, как создавать выбор между классами символов в регулярном выражении. А как насчёт выбора между словами или строками символов? Такие выборы описаны в следующем разделе.
Сопоставление этого или того
Иногда мы хотим, чтобы наше регулярное выражение могло сопоставлять различные возможные слова или строки символов. Это достигается с помощью метасимвола альтернации '|'. Для сопоставления dog или cat мы формируем регулярное выражение dog|cat. Как и прежде, Perl будет пытаться сопоставить регулярное выражение в самой ранней возможной точке в строке. В каждой позиции символа Perl сначала попытается сопоставить первый вариант, dog. Если dog не сопоставится, Perl попытается сопоставить следующий вариант, cat. Если cat тоже не сопоставится, то сопоставление терпит неудачу, и Perl переходит к следующей позиции в строке. Вот несколько примеров:
"cats and dogs" =~ /cat|dog|bird/; # matches "cat"
"cats and dogs" =~ /dog|cat|bird/; # matches "cat" Несмотря на то, что dog является первым вариантом во втором регулярном выражении, cat может сопоставиться раньше в строке.
"cats" =~ /c|ca|cat|cats/; # matches "c"
"cats" =~ /cats|cat|ca|c/; # matches "cats" Здесь все варианты совпадают в первой позиции строки, поэтому первый вариант является тем, который совпадает. Если некоторые варианты являются усечениями других, поместите самые длинные вначале, чтобы дать им шанс сопоставиться.
"cab" =~ /a|b|c/ # matches "c"
# /a|b|c/ == /[abc]/ Последний пример показывает, что символьные классы подобны альтернациям символов. В данной позиции символа первым вариантом, который позволит успешно сопоставить регулярное выражение, будет тот, который совпадает.
Группирование и иерархическое сопоставление
Альтернация позволяет регулярному выражению выбирать из альтернатив, но сама по себе она неудовлетворительна. Причина в том, что каждый вариант — это целое регулярное выражение, но иногда мы хотим альтернативы только для части регулярного выражения. Например, предположим, что мы хотим найти housecats или housekeepers. Регулярное выражение housecat|housekeeper подходит, но неэффективно, потому что нам пришлось дважды набирать house. Было бы неплохо иметь части регулярного выражения постоянными, как house, а некоторые части имели альтернативы, как cat|keeper.
Метасимволы группирования () решают эту проблему. Группирование позволяет рассматривать части регулярного выражения как единый блок. Части регулярного выражения группируются путём заключения их в скобки. Таким образом, мы могли бы решить housecat|housekeeper , сформировав регулярное выражение в виде house(cat|keeper). Регулярное выражение house(cat|keeper) означает сопоставить house , за которым следует либо cat , либо keeper.
Ещё несколько примеров
/(a|b)b/; # matches 'ab' or 'bb'
/(ac|b)b/; # matches 'acb' or 'bb'
/(^a|b)c/; # matches 'ac' at start of string or 'bc' anywhere
/(a|[bc])d/; # matches 'ad', 'bd', or 'cd'
/house(cat|)/; # matches either 'housecat' or 'house'
/house(cat(s|)|)/; # matches either 'housecats' or 'housecat' or
# 'house'. Note groups can be nested.
/(19|20|)\d\d/; # match years 19xx, 20xx, or the Y2K problem, xx
"20" =~ /(19|20|)\d\d/; # matches the null alternative '()\d\d',
# because '20\d\d' can't match Альтернации ведут себя одинаково в группах и вне их: в данной позиции строки выбирается левая альтернатива, которая позволяет сопоставить регулярное выражение. Таким образом, в последнем примере в первой позиции строки "20" сопоставляет вторую альтернативу, но для сопоставления следующих двух цифр \d\d ничего не осталось. Поэтому Perl переходит к следующей альтернативе, которая является нулевой альтернативой, и это работает, так как "20" — это две цифры.
Процесс пробы одной альтернативы, проверки её совпадения и перехода к следующей альтернативе, а также возвращения в строку из места, где была проверена предыдущая альтернатива, если она не работает, называется обратным отслеживанием. Термин «обратное отслеживание» происходит от идеи, что сопоставление регулярного выражения подобно прогулке по лесу. Успешное сопоставление регулярного выражения похоже на прибытие в пункт назначения. Существует много возможных стартовых точек, одна для каждой позиции в строке, и каждая из них проверяется в порядке следования слева направо. Из каждой стартовой точки может быть много путей, некоторые из которых ведут к цели, а некоторые являются тупиковыми. Когда вы идёте по тропе и сталкиваетесь с тупиком, вы должны вернуться назад по тропе к более ранней точке, чтобы попробовать другую тропу. Если вы достигнете пункта назначения, вы сразу остановитесь и забудете о попытках пройти по остальным тропам. Вы упорны, и только если вы перепробовали все тропы со всех стартовых точек и не достигли пункта назначения, вы объявляете об ошибке. Для ясности, вот пошаговый анализ того, что делает Perl, когда пытается сопоставить регулярное выражение
"abcde" =~ /(abd|abc)(df|d|de)/; -
0. Начинаем с первой буквы в строке
'a'. -
1. Пытаемся сопоставить первый вариант в первой группе
'abd'. -
2. Сопоставляем
'a'за которым следует'b'. Пока всё хорошо. -
3.
'd'в регулярном выражении не совпадает с'c'в строке — тупик. Поэтому возвращаемся на две позиции назад и выбираем вторую альтернативу в первой группе'abc'. -
4. Сопоставляем
'a'за которым следует'b'за которым следует'c'. Успешно совпали все символы в первой группе. Присваиваем$1значение'abc'. -
5. Переходим ко второй группе и выбираем первую альтернативу
'df'. -
6. Сопоставляем
'd'. -
7.
'f'в регулярном выражении не совпадает с'e'в строке, поэтому тупик. Возвращаемся на одну позицию назад и выбираем вторую альтернативу во второй группе'd'. -
8.
'd'совпадает. Вторая группа удовлетворена, поэтому присваиваем$2значение'd'. -
9. Мы в конце регулярного выражения, всё готово! Мы сопоставили
'abcd'из строки"abcde".
Несколько моментов следует отметить относительно этого анализа. Во-первых, третий вариант во второй группе 'de' также допускает совпадение, но мы остановились до его достижения — в заданной позиции символа, слева направо, побеждает левее. Во-вторых, мы смогли найти совпадение в первой позиции символа строки 'a'. Если совпадений в первой позиции не было, Perl переходил ко второй позиции символа 'b' и вновь пытался найти совпадение. Только после того, как будут исчерпаны все возможные пути во всех возможных позициях символов, Perl прекращает поиск и объявляет $string =~ /(abd|abc)(df|d|de)/; ложным.
Несмотря на всю эту работу, сопоставление с регулярным выражением происходит поразительно быстро. Для ускорения Perl компилирует регулярное выражение в компактную последовательность кодов операций, которые часто помещаются в кэш процессора. При выполнении кода эти коды операций могут работать на полную мощность и выполнять поиск очень быстро.
Извлечение совпадений
Метасимволы группирования () также выполняют ещё одну совершенно иную функцию: они позволяют извлекать части строки, которые соответствуют шаблону. Это очень полезно для определения, что сопоставлено, и для обработки текста в целом. Для каждой группы соответствующая сопоставленная часть попадает в специальные переменные $1, $2, и т. д. Их можно использовать как обычные переменные:
# extract hours, minutes, seconds
if ($time =~ /(\d\d):(\d\d):(\d\d)/) { # match hh:mm:ss format
$hours = $1;
$minutes = $2;
$seconds = $3;
} Теперь мы знаем, что в скалярном контексте $time =~ /(\d\d):(\d\d):(\d\d)/ возвращает значение истинности или ложности. Однако в списочном контексте он возвращает список сопоставленных значений ($1,$2,$3). Таким образом, мы можем записать код более компактно как
# extract hours, minutes, seconds
($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/); Если группировки в регулярном выражении вложены, $1 получает группу с самой левой открывающей скобкой, $2 следующую открывающую скобку, и т. д. Вот регулярное выражение с вложенными группами:
/(ab(cd|ef)((gi)|j))/;
1 2 34 Если это регулярное выражение соответствует шаблону, то $1 содержит строку, начинающуюся с 'ab', $2 установлено либо в 'cd', либо в 'ef', $3 равно либо 'gi', либо 'j', и $4 установлено либо в 'gi', подобно $3, либо остаётся неопределённым.
Для удобства Perl устанавливает $+ в строку, содержащуюся в группе с наибольшим номером $1, $2,... которая была назначена (и, в некоторой степени, связанно с этим, $^N в значение группы $1, $2,... которая была назначена в последнюю очередь; т.е. значение $1, $2,... связанное с самой правой закрывающей скобкой, используемой в совпадении).
Обратные ссылки
Тесно связанные с переменными сопоставления $1, $2, ... — это обратные ссылки \g1, \g2,... Обратные ссылки — это просто переменные сопоставления, которые могут использоваться внутри регулярного выражения. Это действительно удобная функция; то, что сопоставляется позже в регулярном выражении, зависит от того, что сопоставлялось ранее в регулярном выражении. Предположим, что мы хотим найти удвоенные слова в тексте, например, «the the». Следующее регулярное выражение находит все удвоенные слова из 3 букв со пробелом между ними:
/\b(\w\w\w)\s\g1\b/; Группирование присваивает значение \g1, таким образом, чтобы одинаковая последовательность из 3 букв использовалась для обеих частей.
Аналогичная задача заключается в поиске слов, состоящих из двух одинаковых частей:
% simple_grep '^(\w\w\w\w|\w\w\w|\w\w|\w)\g1$' /usr/dict/words
beriberi
booboo
coco
mama
murmur
papa Регулярное выражение имеет одну группу, которая рассматривает комбинации из 4 букв, затем из 3 букв и так далее, и использует \g1 для поиска повтора. Хотя $1 и \g1 представляют одно и то же, следует с осторожностью использовать переменные сопоставления $1, $2, ... только вне регулярного выражения, и обратные ссылки \g1, \g2, ... только внутри регулярного выражения; в противном случае могут возникнуть неожиданные и неудовлетворительные результаты.
Относительные обратные ссылки
Подсчёт открывающихся скобок для получения правильного номера обратной ссылки становится затруднительным, как только количество захватывающих групп превышает одну. С Perl 5.10 доступна более удобная техника: относительные обратные ссылки. Для ссылки на немедленно предшествующую захватывающую группу можно использовать \g{-1}, предшествующую ей — \g{-2}, и так далее.
Ещё одна причина, помимо удобочитаемости и поддерживаемости, для использования относительных обратных ссылок проиллюстрирована следующим примером, где используется простой шаблон для сопоставления особых строк:
$a99a = '([a-z])(\d)\g2\g1'; # matches a11a, g22g, x33x, etc. Теперь, когда у нас есть этот шаблон, сохранённый в виде удобной строки, мы можем захотеть использовать его как часть другого шаблона:
$line = "code=e99e";
if ($line =~ /^(\w+)=$a99a$/){ # unexpected behavior!
print "$1 is valid\n";
} else {
print "bad line: '$line'\n";
} Но это не совпадает, по крайней мере не так, как можно было ожидать. Только после вставки интерполированной $a99a и просмотра полученного полного текста регулярного выражения становится очевидным, что обратные ссылки имеют обратную силу. Подвыражение (\w+) захватило номер 1 и понизило группы в $a99a на один ранг. Этому можно избежать, используя относительные обратные ссылки:
$a99a = '([a-z])(\d)\g{-1}\g{-2}'; # safe for being interpolated Именованные обратные ссылки
Perl 5.10 также представил именованные захватывающие группы и именованные обратные ссылки. Чтобы присвоить имя захватывающей группе, нужно написать либо (?<name>...), либо (?'name'...). Обратная ссылка может быть записана как \g{name}. Допустимо присваивать одно и то же имя нескольким группам, но тогда можно ссылаться только на самую левую из одноимённых групп. Вне шаблона именованная захватывающая группа доступна через хеш %+.
Предположим, что нам нужно сопоставить даты календаря, которые могут быть представлены в одном из трёх форматов: yyyy-mm-dd, mm/dd/yyyy или dd.mm.yyyy. Мы можем написать три подходящих шаблона, в которых мы используем 'd', 'm' и 'y' соответственно в качестве имён групп, захватывающих соответствующие компоненты даты. Операция сопоставления объединяет три шаблона как альтернативы:
$fmt1 = '(?<y>\d\d\d\d)-(?<m>\d\d)-(?<d>\d\d)';
$fmt2 = '(?<m>\d\d)/(?<d>\d\d)/(?<y>\d\d\d\d)';
$fmt3 = '(?<d>\d\d)\.(?<m>\d\d)\.(?<y>\d\d\d\d)';
for my $d (qw(2006-10-21 15.01.2007 10/31/2005)) {
if ( $d =~ m{$fmt1|$fmt2|$fmt3} ){
print "day=$+{d} month=$+{m} year=$+{y}\n";
}
} Если любая из альтернатив соответствует шаблону, хеш %+ должен содержать три пары ключ-значение.
Альтернативное нумерование захватывающих групп
Ещё один метод нумерации захватывающих групп (также начиная с Perl 5.10) решает проблему ссылки на группы в наборе альтернатив. Рассмотрим шаблон для сопоставления времени суток в гражданском или военном стиле:
if ( $time =~ /(\d\d|\d):(\d\d)|(\d\d)(\d\d)/ ){
# process hour and minute
} Обработка результатов требует дополнительного оператора if для определения того, содержат ли $1 и $2 или $3 и $4 нужные данные. Было бы проще, если бы мы могли использовать номера групп 1 и 2 и во второй альтернативе, и именно это достигается с помощью скобочного конструктора (?|...), помещённого вокруг альтернативы. Вот расширенная версия предыдущего шаблона:
if($time =~ /(?|(\d\d|\d):(\d\d)|(\d\d)(\d\d))\s+([A-Z][A-Z][A-Z])/){
print "hour=$1 minute=$2 zone=$3\n";
} Внутри группы альтернативного нумерования номера групп начинаются в той же позиции для каждой альтернативы. После группы нумерация продолжается с номера, на единицу большим, чем максимальный достигнут среди всех альтернатив.
Информация о позиции
Помимо сопоставленного текста, Perl также предоставляет позиции сопоставления в виде содержимого массивов @- и @+. $-[0] — позиция начала всего совпадения, а $+[0] — позиция конца. Аналогично, $-[n] — позиция начала совпадения $n , а $+[n] — позиция конца. Если $n не определено, то не определены и $-[n] и $+[n]. Тогда этот код
$x = "Mmm...donut, thought Homer";
$x =~ /^(Mmm|Yech)\.\.\.(donut|peas)/; # matches
foreach $exp (1..$#-) {
print "Match $exp: '${$exp}' at position ($-[$exp],$+[$exp])\n";
} выводит
Match 1: 'Mmm' at position (0,3)
Match 2: 'donut' at position (6,11) Даже если в регулярном выражении нет группировок, всё равно можно узнать, что именно было сопоставлено в строке. Если использовать их, Perl установит $` в часть строки перед совпадением, $& в часть строки, которая совпала, и $' в часть строки после совпадения. Пример:
$x = "the cat caught the mouse";
$x =~ /cat/; # $` = 'the ', $& = 'cat', $' = ' caught the mouse'
$x =~ /the/; # $` = '', $& = 'the', $' = ' cat caught the mouse' Во втором совпадении $` равно '', поскольку регулярное выражение совпало с первой позицией символа в строке и остановилось; оно никогда не видело второе «the».
Если ваш код будет выполняться на версиях Perl ранее 5.20, стоит отметить, что использование $` и $' значительно снижает скорость сопоставления с регулярным выражением, а $& замедляет его в меньшей степени, потому что если они используются в одном регулярном выражении в программе, они генерируются для всех регулярных выражений в программе. Поэтому, если вашей целью является производительность, от них следует отказаться. Если вам нужно извлечь соответствующие подстроки, используйте @- и @+ вместо них:
$` is the same as substr( $x, 0, $-[0] )
$& is the same as substr( $x, $-[0], $+[0]-$-[0] )
$' is the same as substr( $x, $+[0] ) С Perl 5.10 переменные ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH} могут быть использованы. Они устанавливаются только в том случае, если присутствует модификатор /p. Следовательно, они не накладывают штрафа на остальную часть программы. В Perl 5.20 переменные ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH} доступны независимо от использования модификатора /p (модификатор игнорируется), и переменные $`, $' и $& не влияют на скорость.
Незахватные группировки
Группа, необходимая для объединения набора альтернатив, может или не может быть полезна как захватывающая группа. Если нет, она просто создаёт лишнее добавление к набору доступных значений захватывающих групп, как внутри, так и вне регулярного выражения. Незахватные группировки, обозначаемые (?:regexp), всё ещё позволяют рассматривать регулярное выражение как единое целое, но не создают захватывающую группу одновременно. Захватывающие и незахватывающие группировки могут сосуществовать в одном регулярном выражении. Поскольку извлечения не происходит, незахватывающие группировки быстрее, чем захватывающие. Незахватные группировки также удобны для выбора точно тех частей регулярного выражения, которые необходимо извлечь в переменные сопоставления:
# match a number, $1-$4 are set, but we only want $1
/([+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)/;
# match a number faster , only $1 is set
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;
# match a number, get $1 = whole number, $2 = exponent
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]([+-]?\d+))?)/; Незахватные группировки также полезны для удаления ненужных элементов, полученных из операции разделения, где скобки необходимы по каким-либо причинам:
$x = '12aba34ba5';
@num = split /(a|b)+/, $x; # @num = ('12','a','34','a','5')
@num = split /(?:a|b)+/, $x; # @num = ('12','34','5') В Perl 5.22 и более поздних версиях все группы в регулярном выражении можно сделать незахватывающими, используя новый флаг /n.
"hello" =~ /(hi|hello)/n; # $1 is not set! См. "n" в perlre для получения дополнительной информации.
Сопоставление повторений
Примеры в предыдущем разделе демонстрируют неприятный недостаток. Мы сопоставляли только слова из 3 букв или куски слов из 4 букв или меньше. Мы хотели бы иметь возможность сопоставлять слова или, в более общем случае, строки любой длины, не записывая утомительные альтернативы, такие как \w\w\w\w|\w\w\w|\w\w|\w.
Это именно та проблема, для которой были созданы метасимволы квантификаторов '?', '*', '+', и {}. Они позволяют нам ограничить количество повторений для части регулярного выражения, которую мы считаем соответствием. Квантификаторы ставятся сразу после символа, класса символов или группировки, которую мы хотим указать. Они имеют следующие значения:
-
a?означает: сопоставить'a'1 или 0 раз -
a*означает: сопоставить'a'0 или более раз, т.е., любое количество раз -
a+означает: сопоставить'a'1 или более раз, т.е., по крайней мере один раз -
a{n,m}означает: сопоставить не менееnраз, но не болееmраз. -
a{n,}означает: сопоставить не менееnраз и более -
a{n}означает: сопоставить ровноnраз
Вот некоторые примеры:
/[a-z]+\s+\d*/; # match a lowercase word, at least one space, and
# any number of digits
/(\w+)\s+\g1/; # match doubled words of arbitrary length
/y(es)?/i; # matches 'y', 'Y', or a case-insensitive 'yes'
$year =~ /^\d{2,4}$/; # make sure year is at least 2 but not more
# than 4 digits
$year =~ /^\d{4}$|^\d{2}$/; # better match; throw out 3-digit dates
$year =~ /^\d{2}(\d{2})?$/; # same thing written differently.
# However, this captures the last two
# digits in $1 and the other does not.
% simple_grep '^(\w+)\g1$' /usr/dict/words # isn't this easier?
beriberi
booboo
coco
mama
murmur
papa Для всех этих квантификаторов Perl будет пытаться сопоставить как можно больше символов строки, при этом позволяя регулярному выражению успешно завершиться. Таким образом, с /a?.../, Perl сначала попытается сопоставить регулярное выражение с 'a'; если это не удастся, Perl попытается сопоставить регулярное выражение без 'a'. Для квантификатора '*', мы получим следующее:
$x = "the cat in the hat";
$x =~ /^(.*)(cat)(.*)$/; # matches,
# $1 = 'the '
# $2 = 'cat'
# $3 = ' in the hat' Что можно было бы ожидать, сопоставление находит единственный cat в строке и фиксируется на нем. Однако рассмотрим такое регулярное выражение:
$x =~ /^(.*)(at)(.*)$/; # matches,
# $1 = 'the cat in the h'
# $2 = 'at'
# $3 = '' (0 characters match) Можно предположить, что Perl найдет at в cat и остановится на этом, но это не даст самой длинной возможной строке для первого квантификатора .*. Вместо этого первый квантификатор .* захватывает как можно больше символов строки, при этом позволяя регулярному выражению соответствовать. В этом примере это означает наличие последовательности at с конечным at в строке. Другим важным принципом, проиллюстрированным здесь, является то, что когда в регулярном выражении есть два или более элементов, левый квантификатор, если таковой имеется, захватывает как можно больше символов строки, оставляя остальную часть регулярного выражения бороться за оставшиеся фрагменты. Таким образом, в нашем примере первый квантификатор .* захватывает большую часть строки, а второй квантификатор .* получает пустую строку. Квантификаторы, которые захватывают как можно больше символов строки, называются квантификаторами максимального соответствия или жадными квантификаторами.
Когда регулярное выражение может сопоставить строку несколькими разными способами, мы можем использовать указанные выше принципы, чтобы предсказать, каким способом регулярное выражение будет сопоставлено:
-
Принцип 0: В целом любое регулярное выражение будет сопоставлено в самой ранней возможной позиции в строке.
-
Принцип 1: В альтернации
a|b|c...будет использоваться левая альтернатива, которая позволяет сопоставление всего регулярного выражения. -
Принцип 2: Квантификаторы максимального соответствия
'?','*','+'и{n,m}в общем случае сопоставят как можно больше символов строки, при этом позволяя всему регулярному выражению соответствовать. -
Принцип 3: Если в регулярном выражении есть два или более элемента, самый левый жадный квантификатор, если таковой имеется, будет сопоставлять как можно больше символов строки, при этом позволяя всему регулярному выражению соответствовать. Следующий самый левый жадный квантификатор, если таковой имеется, будет пытаться сопоставить как можно больше оставшихся доступных ему символов строки, при этом позволяя всему регулярному выражению соответствовать. И так далее, пока все элементы регулярного выражения не будут удовлетворены.
Как мы видели выше, Принцип 0 перекрывает другие. Регулярное выражение будет сопоставлено как можно раньше, при этом другие принципы определяют, как регулярное выражение сопоставляется в этой самой ранней позиции символа.
Вот пример действия этих принципов:
$x = "The programming republic of Perl";
$x =~ /^(.+)(e|r)(.*)$/; # matches,
# $1 = 'The programming republic of Pe'
# $2 = 'r'
# $3 = 'l' Это регулярное выражение соответствует самой ранней позиции в строке, 'T'. Можно подумать, что 'e', будучи самым левым в альтернации, будет сопоставлено, но 'r' производит самую длинную строку в первом квантификаторе.
$x =~ /(m{1,2})(.*)$/; # matches,
# $1 = 'mm'
# $2 = 'ing republic of Perl' Здесь, Самое раннее возможное соответствие находится в первом 'm' в programming. m{1,2} - это первый квантификатор, поэтому он получает право сопоставить максимальное mm.
$x =~ /.*(m{1,2})(.*)$/; # matches,
# $1 = 'm'
# $2 = 'ing republic of Perl' Здесь регулярное выражение сопоставляется в начале строки. Первый квантификатор .* захватывает как можно больше, оставляя только один 'm' для второго квантификатора m{1,2}.
$x =~ /(.?)(m{1,2})(.*)$/; # matches,
# $1 = 'a'
# $2 = 'mm'
# $3 = 'ing republic of Perl' Здесь .? съедает свое максимальное односимвольное значение в самой ранней возможной позиции в строке, 'a' в programming, оставляя m{1,2} возможность сопоставить оба 'm''a. Наконец,
"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 её проигнорирует. Если вы вообще не хотите никаких замен, используйте специальный разделитель
@pattern = ('Seuss');
while (<>) {
print if m'@pattern'; # matches literal '@pattern', not 'Seuss'
}: @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 означает глобальное сопоставление и позволяет оператору сопоставления находить соответствия в строке как можно большее количество раз. В скалярном контексте последующие вызовы к строке будут перемещаться от совпадения к совпадению, отслеживая положение в строке по ходу. Вы можете получить или установить положение с помощью функции pos().
Использование /g показано в следующем примере. Предположим, у нас есть строка, состоящая из слов, разделённых пробелами. Если мы знаем, сколько слов в нём есть заранее, мы могли бы извлечь слова, используя группировки:
$x = "cat dog house"; # 3 words
$x =~ /^\s*(\w+)\s+(\w+)\s+(\w+)\s*$/; # matches,
# $1 = 'cat'
# $2 = 'dog'
# $3 = 'house' Но что, если у нас есть неопределённое количество слов? Вот для чего предназначен /g. Чтобы извлечь все слова, составьте простое регулярное выражение (\w+) и переберите все совпадения с /(\w+)/g:
while ($x =~ /(\w+)/g) {
print "Word is $1, ends at position ", pos $x, "\n";
} выводит
Word is cat, ends at position 3
Word is dog, ends at position 7
Word is house, ends at position 13 Неудачное соответствие или изменение целевой строки сбрасывают положение. Если вы не хотите сбрасывать положение после неудачи, добавьте /c, как в /regexp/gc. Текущее положение в строке связано со строкой, а не с регулярным выражением. Это означает, что у разных строк разные позиции, и их соответствующие позиции могут быть установлены или считываются независимо.
В списочном контексте /g возвращает список сопоставленных группировок, или, если группировок нет, список совпадений с целым регулярным выражением. Итак, если мы хотели только слова, мы могли бы использовать
@words = ($x =~ /(\w+)/g); # matches,
# $words[0] = 'cat'
# $words[1] = 'dog'
# $words[2] = 'house' Тесно связанный с модификатором /g является якорь \G . Якорь \G соответствует точке, где предыдущее совпадение /g закончилось. \G позволяет нам легко выполнять контекстно-зависимое сопоставление:
$metric = 1; # use metric units
...
$x = <FILE>; # read in measurement
$x =~ /^([+-]?\d+)\s*/g; # get magnitude
$weight = $1;
if ($metric) { # error checking
print "Units error!" unless $x =~ /\Gkg\./g;
}
else {
print "Units error!" unless $x =~ /\Glbs\./g;
}
$x =~ /\G\s+(widget|sprocket)/g; # continue processing Сочетание /g и \G позволяет нам обрабатывать строку по частям и использовать произвольную логику Perl для принятия решений о последующих действиях. В настоящее время якорь \G полностью поддерживается только при использовании для привязки к началу шаблона.
\G также незаменимо при обработке записей фиксированной длины с регулярными выражениями. Предположим, у нас есть фрагмент кодирующей области ДНК, закодированный буквами пар оснований ATCGTTGAAT..., и мы хотим найти все стоп-кодоны TGA. В кодирующей области кодоны представляют собой 3-буквенные последовательности, поэтому мы можем рассматривать фрагмент ДНК как последовательность 3-буквенных записей. Простое регулярное выражение
# expanded, this is "ATC GTT GAA TGC AAA TGA CAT GAC"
$dna = "ATCGTTGAATGCAAATGACATGAC";
$dna =~ /TGA/; не работает; оно может соответствовать TGA, но нет гарантии, что соответствие выровнено с границами кодонов, например, подстрока GTT GAA даёт соответствие. Лучшим решением является
while ($dna =~ /(\w\w\w)*?TGA/g) { # note the minimal *?
print "Got a TGA stop codon at position ", pos $dna, "\n";
} что выводит
Got a TGA stop codon at position 18
Got a TGA stop codon at position 23 Позиция 18 хорошая, но позиция 23 ошибочна. Что случилось?
Ответ заключается в том, что наше регулярное выражение работает хорошо, пока мы не пройдём последний реальный результат совпадения. Затем регулярное выражение не найдёт синхронизированный TGA и начнёт смещаться на одну позицию вправо за раз, что нам не нужно. Решение состоит в использовании \G для привязки совпадения к выравниванию кодонов:
while ($dna =~ /\G(\w\w\w)*?TGA/g) {
print "Got a TGA stop codon at position ", pos $dna, "\n";
} Это выводит
Got a TGA stop codon at position 18 что является правильным ответом. Этот пример показывает, что важно не только соответствовать желаемому, но и отбрасывать нежелательное.
(Существуют и другие модификаторы регулярных выражений, такие как /o, но их специализированное использование выходит за рамки данного введения.)
Поиск и замена
Регулярные выражения также играют важную роль в операциях поиска и замены в Perl. Поиск и замена выполняются с помощью оператора s///. Общий вид — s/regexp/replacement/modifiers, при этом всё, что мы знаем о регулярных выражениях и модификаторах, также применимо в данном случае. Замена — это Perl-строка в двойных кавычках, которая заменяет в строке то, что совпало с regexp. Оператор =~ также используется здесь для ассоциации строки с s///. При сопоставлении с $_, $_ =~ можно опустить. Если совпадение найдено, s/// возвращает количество проведённых замен; в противном случае — false. Вот несколько примеров:
$x = "Time to feed the cat!";
$x =~ s/cat/hacker/; # $x contains "Time to feed the hacker!"
if ($x =~ s/^(Time.*hacker)!$/$1 now!/) {
$more_insistent = 1;
}
$y = "'quoted words'";
$y =~ s/^'(.*)'$/$1/; # strip single quotes,
# $y contains "quoted words" В последнем примере была найдена вся строка, но сгруппирована только часть внутри одинарных кавычек. С оператором s///, сопоставленные переменные $1, $2, и т. д., сразу же доступны для использования в выражении замены, поэтому мы используем $1 для замены строки в кавычках только на то, что было в кавычках. С модификатором global, s///g будет искать и заменять все вхождения регулярного выражения в строке:
$x = "I batted 4 for 4";
$x =~ s/4/four/; # doesn't do it all:
# $x contains "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g; # does it all:
# $x contains "I batted four for four" Если в этом руководстве вы предпочитаете «regex» вместо «regexp», вы можете использовать следующую программу для замены:
% cat > simple_replace
#!/usr/bin/perl
$regexp = shift;
$replacement = shift;
while (<>) {
s/$regexp/$replacement/g;
print;
}
^D
% simple_replace regexp regex perlretut.pod В simple_replace мы использовали модификатор s///g для замены всех вхождений регулярного выражения в каждой строке. (Несмотря на то, что регулярное выражение появляется в цикле, Perl достаточно умен, чтобы скомпилировать его только один раз.) Как и в случае с simple_grep, как print, так и s/$regexp/$replacement/g неявно используют $_.
Если вы не хотите, чтобы s/// изменял исходную переменную, вы можете использовать модификатор неразрушающей замены s///r. Это меняет поведение, так что s///r возвращает окончательную строку с заменами (вместо количества замен):
$x = "I like dogs.";
$y = $x =~ s/dogs/cats/r;
print "$x $y\n"; Этот пример выведет «I like dogs. I like cats». Обратите внимание, что исходная переменная $x не была затронута. Вместо этого общий результат замены хранится в $y. Если замена ничего не меняет, возвращается исходная строка:
$x = "I like dogs.";
$y = $x =~ s/elephants/cougars/r;
print "$x $y\n"; # prints "I like dogs. I like dogs." Ещё одна интересная возможность, предоставляемая флагом s///r, — цепные замены:
$x = "Cats are great.";
print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~
s/Frogs/Hedgehogs/r, "\n";
# prints "Hedgehogs are great." Модификатор, доступный специально для поиска и замены, — это модификатор вычисления s///e. s///e обрабатывает текст замены как Perl-код, а не строку в двойных кавычках. Возвращаемое значение кода подставляется вместо сопоставленной подстроки. s///e полезно, если вам нужно выполнить вычисления при замене текста. В этом примере подсчитываются частоты символов в строке:
$x = "Bill the cat";
$x =~ s/(.)/$chars{$1}++;$1/eg; # final $1 replaces char with itself
print "frequency of '$_' is $chars{$_}\n"
foreach (sort {$chars{$b} <=> $chars{$a}} keys %chars); Это выводит
frequency of ' ' is 2
frequency of 't' is 2
frequency of 'l' is 2
frequency of 'B' is 1
frequency of 'c' is 1
frequency of 'e' is 1
frequency of 'h' is 1
frequency of 'i' is 1
frequency of 'a' is 1 Как и оператор совпадения m//, s/// может использовать другие разделители, такие как s!!! и s{}{}, и даже s{}//. Если используются одинарные кавычки s''', регулярное выражение и замена обрабатываются как строки в одинарных кавычках, и замены переменных не выполняются. s/// в контексте списка возвращает то же, что и в скалярном контексте, т.е., количество совпадений.
Функция split
Функция split() — ещё одно место, где используется регулярное выражение. split /regexp/, string, limit разделяет операнд string на список подстрок и возвращает этот список. Регулярное выражение должно быть разработано таким образом, чтобы соответствовать тому, что определяет разделители для требуемых подстрок. limit, если он присутствует, ограничивает разделение не более чем limit строками. Например, чтобы разделить строку на слова, используйте
$x = "Calvin and Hobbes";
@words = split /\s+/, $x; # $word[0] = 'Calvin'
# $word[1] = 'and'
# $word[2] = 'Hobbes' Если используется пустое регулярное выражение //, регулярное выражение всегда находит совпадение, и строка разделяется на отдельные символы. Если регулярное выражение содержит группы, то результирующий список содержит сопоставленные подстроки из этих групп. Например,
$x = "/usr/bin/perl";
@dirs = split m!/!, $x; # $dirs[0] = ''
# $dirs[1] = 'usr'
# $dirs[2] = 'bin'
# $dirs[3] = 'perl'
@parts = split m!(/)!, $x; # $parts[0] = ''
# $parts[1] = '/'
# $parts[2] = 'usr'
# $parts[3] = '/'
# $parts[4] = 'bin'
# $parts[5] = '/'
# $parts[6] = 'perl' Поскольку первый символ $x соответствовал регулярному выражению, split добавило пустой начальный элемент в список.
Если вы дочитали до этого места, поздравляем! Теперь у вас есть все основные инструменты, необходимые для использования регулярных выражений для решения широкого круга задач обработки текста. Если это ваше первое знакомство с руководством, почему бы не остановиться здесь и немного поэкспериментировать с регулярными выражениями… Часть 2 касается более экзотических аспектов регулярных выражений, и эти понятия определённо не нужны прямо с самого начала.
Часть 2: Расширенные инструменты
Хорошо, вы знаете основы регулярных выражений и хотите узнать больше. Если сопоставление регулярных выражений аналогично прогулке по лесу, то инструменты, рассмотренные в части 1, аналогичны топографическим картам и компасу — основным инструментам, которые мы используем постоянно. Большинство инструментов во второй части аналогичны сигнальным ракетам и спутниковым телефонам. Они не используются слишком часто в походе, но когда мы застряли, они могут быть очень полезны.
Ниже приведены более сложные, менее используемые или иногда экзотические возможности Perl-регулярных выражений. Во второй части мы будем исходить из того, что вы хорошо знакомы с основами, и сосредоточимся на расширенных функциях.
Подробно о символах, строках и классах символов
Существует ряд последовательностей экранирования и классов символов, которые мы ещё не рассмотрели.
Есть несколько последовательностей экранирования, которые преобразуют символы или строки между верхним и нижним регистром, и они также доступны в шаблонах. \l и \u преобразуют следующий символ в нижний или верхний регистр соответственно:
$x = "perl";
$string =~ /\u$x/; # matches 'Perl' in $string
$x = "M(rs?|s)\\."; # note the double backslash
$string =~ /\l$x/; # matches 'mr.', 'mrs.', and 'ms.', \L или \U указывают на постоянное изменение регистра до тех пор, пока не будет встречен \E или до тех пор, пока это не будет переопределено другим \U или \L:
$x = "This word is in lower case:\L SHOUT\E";
$x =~ /shout/; # matches
$x = "I STILL KEYPUNCH CARDS FOR MY 360";
$x =~ /\Ukeypunch/; # matches punch card string Если нет \E, регистр преобразуется до конца строки. Регулярные выражения \L\u$word или \u\L$word преобразуют первый символ $word в верхний регистр, а остальные — в нижний.
Управляющие символы можно экранировать с помощью \c, так что управляющий символ Z будет соответствовать \cZ. Последовательность экранирования \Q...\E экранирует, или защищает, большинство небуквенных символов. Например,
$x = "\QThat !^*&%~& cat!";
$x =~ /\Q!^*&%~&\E/; # check for rough language Она не защищает '$' или '@', так что переменные всё ещё могут быть подставлены.
\Q, \L, \l, \U, \u и \E фактически являются частью синтаксиса двойных кавычек, а не частью синтаксиса регулярных выражений. Они будут работать, если появятся в регулярном выражении, вставленном непосредственно в программу, но не при вставке в строку, которая интерполируется в шаблон.
Perl-регулярные выражения могут обрабатывать больше, чем стандартный набор символов ASCII. Perl поддерживает Unicode — стандарт для представления алфавитов практически всех письменных языков мира, а также множество символов. Текстовые строки Perl являются строками Unicode, поэтому они могут содержать символы со значением (кодовым пунктом или номером символа) выше 255.
Что это значит для регулярных выражений? Ну, пользователям регулярных выражений не нужно знать много о внутреннем представлении строк в Perl. Но они должны знать 1) как представлять символы Unicode в регулярном выражении и 2) что операция сопоставления будет рассматривать строку, в которой производится поиск, как последовательность символов, а не байтов. Ответ на 1) заключается в том, что символы Unicode, большие чем chr(255), представляются с помощью обозначения \x{hex}, потому что \xXY (без фигурных скобок и XY — две шестнадцатеричные цифры) не выходит за пределы 255. (Начиная с Perl 5.14, если вы поклонник восьмеричной системы счисления, вы также можете использовать \o{oct}.)
/\x{263a}/; # match a Unicode smiley face :) ПРИМЕЧАНИЕ: В Perl 5.6.0 для использования любых функций Unicode требовалось указать use utf8. Это больше не так: для практически всех операций с Unicode явное использование директивы utf8 не требуется. (Единственный случай, когда это имеет значение, — если ваш скрипт Perl находится в Unicode и закодирован в UTF-8, тогда явное указание use utf8 необходимо.)
Выяснение шестнадцатеричной последовательности нужного символа Unicode или расшифровка шестнадцатеричного Unicode-регулярного выражения — занятие примерно такое же увлекательное, как программирование на машинном коде. Поэтому другой способ указания символов Unicode — использование последовательности экранирования имени символа \N{name}. name — это имя символа Unicode, указанное в стандарте Unicode. Например, если мы хотели представить или сопоставить астрологический знак планеты Меркурий, мы могли бы использовать
$x = "abc\N{MERCURY}def";
$x =~ /\N{MERCURY}/; # matches Также можно использовать сокращённые имена:
print "\N{GREEK SMALL LETTER SIGMA} is called sigma.\n";
print "\N{greek:Sigma} is an upper-case sigma.\n"; Вы также можете ограничить имена определённым алфавитом, указав директиву charnames:
use charnames qw(greek);
print "\N{sigma} is Greek sigma\n"; Индекс имён символов доступен онлайн от консорциума Unicode, https://www.unicode.org/charts/charindex.html; справочный материал со ссылками на другие ресурсы на https://www.unicode.org/standard/where.
Начиная с Perl v5.32, альтернатива \N{...} для полных имён доступна, и это означает
/\p{Name=greek small letter sigma}/ Регистр имени символа не имеет значения при использовании в \p{}, как и большинство пробелов, подчёркиваний и дефисов. (Несколько исключительных символов вызывают проблемы с игнорированием всех их всегда. Подробности (которые вы можете посмотреть, когда станете более опытными, и если вам это когда-нибудь понадобится) находятся по адресу https://www.unicode.org/reports/tr44/tr44-24.html#UAX44-LM2).
Ответ на требование 2) заключается в том, что 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 https://www.unicode.org
Как будто все эти классы не были достаточно, Perl также определяет классы символов в стиле POSIX. Они имеют вид [:name:], где name — имя класса POSIX. Классы POSIX — alpha, alnum, ascii, cntrl, digit, graph, lower, print, punct, space, upper, и xdigit, и два расширения, word (расширение Perl для соответствия \w ), и blank (расширение GNU). Модификатор /a ограничивает их соответствием только в диапазоне ASCII; в противном случае они могут совпадать так же, как соответствующие классы Unicode Perl: [:upper:] эквивалентно \p{IsUpper}, и т.д. (Существуют некоторые исключения и нюансы; см. perlrecharclass для подробного обсуждения.) [:digit:], [:word:], и [:space:] соответствуют знакомым \d, \w, и \s классам символов. Чтобы отрицать класс POSIX, поставьте '^' перед именем, так что, например, [:^digit:] соответствует \D и, в 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 в качестве регулярного выражения и преобразует результат в форму, которую можно назначить переменной:
$reg = qr/foo+bar?/; # reg contains a compiled regexp Затем $reg может использоваться как регулярное выражение:
$x = "fooooba";
$x =~ $reg; # matches, just like /foo+bar?/
$x =~ /$reg/; # same thing, alternate form $reg также может быть включён в более крупное регулярное выражение:
$x =~ /(abc)?$reg/; # still matches Как и с оператором сопоставления, квота регулярных выражений может использовать разные разделители, например, qr!!, qr{} или qr~~. Апострофы в качестве разделителей (qr'') запрещают любую интерполяцию.
Предварительно скомпилированные регулярные выражения полезны для создания динамических совпадений, которые не нужно перекомпилировать каждый раз, когда они встречаются. Используя предварительно скомпилированные регулярные выражения, мы пишем программу grep_step, которая выполняет поиск по последовательности шаблонов, переходя к следующему шаблону, как только один будет удовлетворён.
% cat > grep_step
#!/usr/bin/perl
# grep_step - match <number> regexps, one after the other
# usage: multi_grep <number> regexp1 regexp2 ... file1 file2 ...
$number = shift;
$regexp[$_] = shift foreach (0..$number-1);
@compiled = map qr/$_/, @regexp;
while ($line = <>) {
if ($line =~ /$compiled[0]/) {
print $line;
shift @compiled;
last unless @compiled;
}
}
^D
% grep_step 3 shift print last grep_step
$number = shift;
print $line;
last unless @compiled; Хранение предварительно скомпилированных регулярных выражений в массиве @compiled позволяет нам просто перебирать регулярные выражения без перекомпиляции, тем самым повышая гибкость без ущерба для скорости.
Создание регулярных выражений во время выполнения
Обратная отслеживание более эффективна, чем многократные попытки с различными регулярными выражениями. Если существует несколько регулярных выражений и совпадение с любым из них приемлемо, то их можно объединить в набор альтернатив. Если отдельные выражения являются входными данными, это можно сделать, запрограммировав операцию объединения. Мы воспользуемся этой идеей в улучшенной версии программы simple_grep: программы, которая ищет соответствия по нескольким шаблонам:
% cat > multi_grep
#!/usr/bin/perl
# multi_grep - match any of <number> regexps
# usage: multi_grep <number> regexp1 regexp2 ... file1 file2 ...
$number = shift;
$regexp[$_] = shift foreach (0..$number-1);
$pattern = join '|', @regexp;
while ($line = <>) {
print $line if $line =~ /$pattern/;
}
^D
% multi_grep 2 shift for multi_grep
$number = shift;
$regexp[$_] = shift foreach (0..$number-1); Иногда выгодно создавать шаблон из входных данных, которые необходимо проанализировать, и использовать допустимые значения в левой части операций сопоставления. В качестве примера такой несколько парадоксальной ситуации предположим, что наш ввод содержит глагол команды, который должен совпадать с одним из набора доступных глаголов команды, с дополнительным поворотом, что команды могут сокращаться, пока заданная строка является уникальной. Ниже приведена программа, демонстрирующая основной алгоритм.
% cat > keymatch
#!/usr/bin/perl
$kwds = 'copy compare list print';
while( $cmd = <> ){
$cmd =~ s/^\s+|\s+$//g; # trim leading and trailing spaces
if( ( @matches = $kwds =~ /\b$cmd\w*/g ) == 1 ){
print "command: '@matches'\n";
} elsif( @matches == 0 ){
print "no such command: '$cmd'\n";
} else {
print "not unique: '$cmd' (could be one of: @matches)\n";
}
}
^D
% keymatch
li
command: 'list'
co
not unique: 'co' (could be one of: copy compare)
printer
no such command: 'printer' Вместо того, чтобы пытаться сопоставить ввод с ключевыми словами, мы сопоставляем объединённый набор ключевых слов со вводом. Операция сопоставления шаблонов $kwds =~ /\b($cmd\w*)/g делает несколько вещей одновременно. Она гарантирует, что заданная команда начинается там, где начинается ключевое слово (\b). Она допускает сокращения из-за добавленного \w*. Она сообщает нам количество совпадений (scalar @matches) и все ключевые слова, которые фактически совпали. Вам вряд ли захочется чего-то большего.
Встраивание комментариев и модификаторов в регулярное выражение
Начиная с этого раздела, мы будем обсуждать набор расширенных шаблонов Perl. Это расширения традиционной синтаксической конструкции регулярных выражений, которые предоставляют мощные новые инструменты для сопоставления шаблонов. Мы уже видели расширения в виде минимальных конструкций сопоставления ??, *?, +?, {n,m}?, и {n,}?. Большинство расширений ниже имеют вид (?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) или (начиная с 5.32, экспериментально в 5.28) (*pla:regexp) или (*positive_lookahead:regexp); а утверждение lookbehind обозначается (?<=fixed-regexp) или (начиная с 5.32, экспериментально в 5.28) (*plb:fixed-regexp) или (*positive_lookbehind:fixed-regexp). Некоторые примеры:
$x = "I catch the housecat 'Tom-cat' with catnip";
$x =~ /cat(*pla:\s)/; # matches 'cat' in 'housecat'
@catwords = ($x =~ /(?<=\s)cat\w+/g); # matches,
# $catwords[0] = 'catch'
# $catwords[1] = 'catnip'
$x =~ /\bcat\b/; # matches 'cat' in 'Tom-cat'
$x =~ /(?<=\s)cat(?=\s)/; # doesn't match; no isolated 'cat' in
# middle of $x Обратите внимание, что скобки в этих выражениях незахватывающие, так как это утверждения нулевой ширины. Таким образом, во втором регулярном выражении подстроки, которые были захвачены, являются подстроками самого регулярного выражения. Lookahead может сопоставлять произвольные регулярные выражения, но lookbehind до версии 5.30 (?<=fixed-regexp) работает только для регулярных выражений фиксированной ширины, то есть, имеющих фиксированное количество символов. Таким образом, (?<=(ab|bc)) подходит, но (?<=(ab)*) до версии 5.30 — нет.
Отрицательные версии утверждений lookahead и lookbehind обозначаются (?!regexp) и (?<!fixed-regexp) соответственно. Или, начиная с 5.32 (экспериментально в 5.28), (*nla:regexp), (*negative_lookahead:regexp), (*nlb:regexp), или (*negative_lookbehind:regexp). Они возвращают значение true, если регулярные выражения не совпадают:
$x = "foobar";
$x =~ /foo(?!bar)/; # doesn't match, 'bar' follows 'foo'
$x =~ /foo(?!baz)/; # matches, 'baz' doesn't follow 'foo'
$x =~ /(?<!\s)foo/; # matches, there is no \s before 'foo' Вот пример, где строка, содержащая слова, разделённые пробелами, числа и одиночные дефисы, должна быть разбита на свои компоненты. Только /\s+/ не сработает, потому что пробелы не требуются между дефисами или словом или дефисом. Дополнительные места для разбиения устанавливаются путём просмотра вперёд и назад:
$str = "one two - --6-8";
@toks = split / \s+ # a run of spaces
| (?<=\S) (?=-) # any non-space followed by '-'
| (?<=-) (?=\S) # a '-' followed by any non-space
/x, $str; # @toks = qw(one two - - - 6 - 8) Использование независимых подвыражений для предотвращения возврата
Независимые подвыражения (или атомарные подвыражения) — это регулярные выражения, которые в контексте более крупного регулярного выражения функционируют независимо от него. То есть, они потребляют столько или так мало символов строки, сколько им необходимо, не обращая внимания на возможность соответствия более крупного регулярного выражения. Независимые подвыражения представляются с помощью (?>regexp) или (начиная с 5.32, экспериментально в 5.28) (*atomic:regexp). Мы можем проиллюстрировать их поведение, сначала рассмотрев обычное регулярное выражение:
$x = "ab";
$x =~ /a*ab/; # matches Это, очевидно, соответствует, но в процессе соответствия подвыражение a* сначала захватило 'a'. Однако это не позволило бы всему регулярному выражению соответствовать, поэтому после возврата a* в конечном итоге вернуло 'a' и сопоставилось с пустой строкой. Здесь то, чему соответствовало a* было зависимо от того, чему соответствовала остальная часть регулярного выражения.
В контрасте с этим стоит независимое подвыражение:
$x =~ /(?>a*)ab/; # doesn't match! Независимое подвыражение (?>a*) не заботится об остальной части регулярного выражения, поэтому видит 'a' и захватывает его. Затем остальная часть регулярного выражения ab не может соответствовать. Поскольку (?>a*) независимо, нет возврата, и независимое подвыражение не отказывается от своего 'a'. Таким образом, соответствие всего регулярного выражения в целом завершается неудачей. Аналогичное поведение наблюдается у совершенно независимых регулярных выражений:
$x = "ab";
$x =~ /a*/g; # matches, eats an 'a'
$x =~ /\Gab/g; # doesn't match, no 'a' available Здесь /g и \G создают «командную передачу» строки от одного регулярного выражения к другому. Регулярные выражения с независимым подвыражением очень похожи на это, с передачей строки независимому подвыражению и передачей строки обратно в окружающее регулярное выражение.
Возможность независимого подвыражения предотвращать возврат может быть весьма полезной. Предположим, что мы хотим сопоставить непустую строку в скобках до глубины в два уровня. Тогда следующее регулярное выражение соответствует:
$x = "abc(de(fg)h"; # unbalanced parentheses
$x =~ /\( ( [ ^ () ]+ | \( [ ^ () ]* \) )+ \)/xx; Регулярное выражение соответствует открывающей скобке, одному или нескольким копиям альтернации и закрывающей скобке. Альтернатива двусторонняя, первая альтернатива [^()]+ соответствует подстроке без скобок, а вторая альтернатива \([^()]*\) соответствует подстроке, ограниченной скобками. Проблема с этим регулярным выражением заключается в том, что оно патологическое: оно имеет вложенные неопределённые квантификаторы вида (a+|b)+. Мы обсуждали в части 1, как вложенные квантификаторы такого рода могут занимать экспоненциально большое время для выполнения, если нет возможного соответствия. Для предотвращения экспоненциального роста нам нужно предотвратить бесполезный возврат на каком-то этапе. Это можно сделать, поместив внутренний квантификатор в качестве независимого подвыражения:
$x =~ /\( ( (?> [ ^ () ]+ ) | \([ ^ () ]* \) )+ \)/xx; Здесь (?>[^()]+) нарушает вырожденность разбиения строки, поглощая как можно больше символов строки и сохраняя их. Тогда сбои соответствия будут происходить гораздо быстрее.
Условные выражения
Условное выражение — это форма оператора if-then-else, которая позволяет выбирать, какие шаблоны сопоставлять на основе некоторого условия. Существуют два типа условных выражений: (?(condition)yes-regexp) и (?(condition)yes-regexp|no-regexp). (?(condition)yes-regexp) подобен оператору 'if () {}' в Perl. Если условие истинно, будет сопоставлено выражение-да. Если условие ложно, выражение-да будет пропущено, и Perl перейдёт к следующему элементу регулярного выражения. Второй вариант подобен оператору 'if () {} else {}' в Perl. Если условие истинно, будет сопоставлено выражение-да, в противном случае будет сопоставлено выражение-нет.
Условие может иметь несколько форм. Первая форма — это просто целое число в скобках (integer). Оно истинно, если соответствующая ссылка на обратную ссылку \integer совпала ранее в регулярном выражении. То же самое можно сделать с именем, связанным с группой захвата, записанным как (<name>) или ('name'). Вторая форма — это чистое утверждение нулевой ширины (?...), будь то утверждение lookahead, lookbehind или утверждение кода (обсуждается в следующем разделе). Третий набор форм предоставляет тесты, которые возвращают true, если выражение выполняется в рекурсии ((R)) или вызывается из какой-либо группы захвата, ссылаясь на неё по номеру ((R1), (R2),...) или по имени ((R&name)).
Целочисленная или именованная форма condition позволяет нам с большей гибкостью выбирать, что сопоставлять на основе того, что сопоставлялось ранее в регулярном выражении. Это ищет слова вида "$x$x" или "$x$y$y$x":
% simple_grep '^(\w+)(\w+)?(?(2)\g2\g1|\g1)$' /usr/dict/words
beriberi
coco
couscous
deed
...
toot
toto
tutu Lookbehind condition позволяет, наряду со ссылками на обратные ссылки, использовать предыдущую часть совпадения для влияния на последующую часть совпадения. Например,
/[ATGC]+(?(?<=AA)G|C)$/; сопоставляет последовательность ДНК, такая что она либо заканчивается AAG, или какой-то другой комбинацией пар оснований и 'C'. Обратите внимание, что форма (?(?<=AA)G|C) , а не (?((?<=AA))G|C); для утверждений lookahead, lookbehind или кода скобки вокруг условного оператора не требуются.
Определение именованных шаблонов
Некоторые регулярные выражения используют идентичные подшаблоны в нескольких местах. Начиная с Perl 5.10, можно определять именованные подшаблоны в разделе шаблона, чтобы их можно было вызывать по имени в любом месте шаблона. Этот синтаксический шаблон для этой группы определений — (?(DEFINE)(?<name>pattern)...). Вставка именованного шаблона записывается как (?&name).
Нижеприведённый пример иллюстрирует эту функцию, используя шаблон для чисел с плавающей запятой, который был представлен ранее. Три подшаблона, которые используются более одного раза, — это необязательный знак, последовательность цифр для целого числа и десятичная дробь. Группа DEFINE в конце шаблона содержит их определение. Обратите внимание, что шаблон десятичной дроби — первое место, где мы можем повторно использовать шаблон целого числа.
/^ (?&osg)\ * ( (?&int)(?&dec)? | (?&dec) )
(?: [eE](?&osg)(?&int) )?
$
(?(DEFINE)
(?<osg>[-+]?) # optional sign
(?<int>\d++) # integer
(?<dec>\.(?&int)) # decimal fraction
)/x Рекурсивные шаблоны
Эта функция (введённая в Perl 5.10) значительно расширяет возможности сопоставления шаблонов Perl. Ссылаясь на какую-либо другую группу захвата в любом месте шаблона с конструкцией (?group-ref), шаблон внутри указанной группы используется как независимый подшаблон вместо ссылки на группу. Поскольку ссылка на группу может быть внутри группы, на которую она ссылается, теперь можно применять сопоставление шаблонов к задачам, для которых ранее требовался рекурсивный анализатор.
Для иллюстрации этой функции мы разработаем шаблон, который соответствует, если строка содержит палиндром. (Это слово или предложение, которое, игнорируя пробелы, знаки препинания и регистр, читается одинаково справа налево и слева направо. Мы начинаем с того, что пустая строка или строка, содержащая только один символ слова, является палиндромом. В противном случае она должна иметь символ слова в начале и такой же в конце, с другим палиндромом между ними.
/(?: (\w) (?...Here be a palindrome...) \g{-1} | \w? )/x Добавив \W* с любой стороны, чтобы исключить то, что должно быть проигнорировано, у нас уже есть полный шаблон:
my $pp = qr/^(\W* (?: (\w) (?1) \g{-1} | \w? ) \W*)$/ix;
for $s ( "saippuakauppias", "A man, a plan, a canal: Panama!" ){
print "'$s' is a palindrome\n" if $s =~ /$pp/;
} В (?...) можно использовать как абсолютные, так и относительные обратные ссылки. Весь шаблон можно повторно вставить с помощью (?R) или (?0). Если вы предпочитаете именовать свои группы, вы можете использовать (?&name) для рекурсии в эту группу.
Чуть-чуть магии: выполнение кода Perl в регулярном выражении
Обычно, регулярные выражения являются частью выражений Perl. Выражения вычисления кода переворачивают это, позволяя произвольный код Perl быть частью регулярного выражения. Выражение вычисления кода обозначается (?{code}), где код - это строка из утверждений Perl.
Выражения кода являются утверждениями нулевой ширины, и значение, которое они возвращают, зависит от их среды. Существует два варианта: либо выражение кода используется в качестве условия в условном выражении (?(condition)...), либо нет. Если выражение кода является условным, код вычисляется, и результат (т. е., результат последнего оператора) используется для определения истинности или ложности. Если выражение кода не используется в качестве условного, утверждение всегда оценивается как истинное, а результат помещается в специальную переменную $^R. Переменная $^R может затем использоваться в выражениях кода позже в регулярном выражении. Вот несколько глупых примеров:
$x = "abcdef";
$x =~ /abc(?{print "Hi Mom!";})def/; # matches,
# prints 'Hi Mom!'
$x =~ /aaa(?{print "Hi Mom!";})def/; # doesn't match,
# no 'Hi Mom!' Обратите пристальное внимание на следующий пример:
$x =~ /abc(?{print "Hi Mom!";})ddd/; # doesn't match,
# no 'Hi Mom!'
# but why not? На первый взгляд, вы могли бы подумать, что он не должен печатать, потому что очевидно, что ddd не будет соответствовать целевой строке. Но посмотрите на этот пример:
$x =~ /abc(?{print "Hi Mom!";})[dD]dd/; # doesn't match,
# but _does_ print Хм. Что здесь произошло? Если вы следили за ходом рассуждений, вы знаете, что приведенный выше шаблон должен быть фактически (почти) таким же, как последний; заключение 'd' в класс символов не изменит того, чему он соответствует. Так почему же первый не печатает, а второй — да?
Ответ кроется в оптимизациях, которые выполняет движок регулярных выражений. В первом случае движок видит только обычные символы (кроме конструкции ?{}). Он достаточно умен, чтобы понять, что строка 'ddd' не встречается в нашей целевой строке, прежде чем фактически применить шаблон. Но во втором случае мы обманули его, заставив думать, что наш шаблон сложнее. Он смотрит, видит наш класс символов и решает, что ему придется фактически применить шаблон, чтобы определить, соответствует ли он, и в процессе применения наталкивается на оператор печати, прежде чем обнаружит, что у нас нет совпадения.
Чтобы более подробно рассмотреть, как движок выполняет оптимизации, см. раздел "Предикаты и отладка" ниже.
Еще больше веселья с ?{}:
$x =~ /(?{print "Hi Mom!";})/; # matches,
# prints 'Hi Mom!'
$x =~ /(?{$c = 1;})(?{print "$c";})/; # matches,
# prints '1'
$x =~ /(?{$c = 1;})(?{print "$^R";})/; # matches,
# prints '1' Упомянутая в названии раздела магия происходит, когда регулярное выражение возвращается назад в процессе поиска соответствия. Если регулярное выражение возвращается назад через выражение кода, и если используемые в нем переменные локализованы с помощью local, изменения в переменных, созданные выражением кода, отменяются! Таким образом, если мы хотели бы подсчитать, сколько раз символ был сопоставлен внутри группы, мы могли бы использовать, например:
$x = "aaaa";
$count = 0; # initialize 'a' count
$c = "bob"; # test if $c gets clobbered
$x =~ /(?{local $c = 0;}) # initialize count
( a # match 'a'
(?{local $c = $c + 1;}) # increment count
)* # do this any number of times,
aa # but match 'aa' at the end
(?{$count = $c;}) # copy local $c var into $count
/x;
print "'a' count is $count, \$c variable is '$c'\n"; Это печатает
'a' count is 2, $c variable is 'bob' Если мы заменим (?{local $c = $c + 1;}) на (?{$c = $c + 1;}), изменения переменной не отменяются во время возврата назад, и мы получаем
'a' count is 4, $c variable is 'bob' Обратите внимание, что отменяются только локальные изменения переменных. Другие побочные эффекты выполнения выражения кода сохраняются. Таким образом
$x = "aaaa";
$x =~ /(a(?{print "Yow\n";}))*aa/; выводит
Yow
Yow
Yow
Yow Результат $^R автоматически локализуется, чтобы он работал правильно в присутствии возврата назад.
В этом примере используется выражение кода в условном операторе для соответствия определенному артиклю, либо 'the' на английском языке, либо 'der|die|das' на немецком:
$lang = 'DE'; # use German
...
$text = "das";
print "matched\n"
if $text =~ /(?(?{
$lang eq 'EN'; # is the language English?
})
the | # if so, then match 'the'
(der|die|das) # else, match 'der|die|das'
)
/xi; Обратите внимание, что синтаксис здесь (?(?{...})yes-regexp|no-regexp), а не (?((?{...}))yes-regexp|no-regexp). Другими словами, в случае выражения кода нам не нужны дополнительные скобки вокруг условного оператора.
Если вы попытаетесь использовать выражения кода, где текст кода содержится внутри интерполированной переменной, а не отображается буквально в шаблоне, Perl может вас удивить:
$bar = 5;
$pat = '(?{ 1 })';
/foo(?{ $bar })bar/; # compiles ok, $bar not interpolated
/foo(?{ 1 })$bar/; # compiles ok, $bar interpolated
/foo${pat}bar/; # compile error!
$pat = qr/(?{ $foo = 1 })/; # precompile code regexp
/foo${pat}bar/; # compiles ok Если регулярное выражение имеет переменную, которая интерполирует выражение кода, Perl обрабатывает регулярное выражение как ошибку. Однако если выражение кода предварительно скомпилировано в переменную, интерполяция допустима. Вопрос в том, почему это ошибка?
Причина в том, что интерполяция переменных и выражения кода вместе представляют собой угрозу безопасности. Это сочетание опасно, потому что многие программисты, которые пишут поисковые системы, часто берут данные пользователя и подключают их непосредственно к регулярному выражению:
$regexp = <>; # read user-supplied regexp
$chomp $regexp; # get rid of possible newline
$text =~ /$regexp/; # search $text for the $regexp Если переменная $regexp содержит выражение кода, пользователь может затем выполнить произвольный код Perl. Например, кто-то может искать system('rm -rf *');, чтобы стереть ваши файлы. В этом смысле сочетание интерполяции и выражений кода загрязняет ваше регулярное выражение. Таким образом, по умолчанию использование интерполяции и выражений кода в одном регулярном выражении не допускается. Если вы не беспокоитесь о злонамеренных пользователях, можно обойти эту проверку безопасности, вызвав use re 'eval':
use re 'eval'; # throw caution out the door
$bar = 5;
$pat = '(?{ 1 })';
/foo${pat}bar/; # compiles ok Другой формой выражения кода является выражение кода шаблона. Выражение кода шаблона подобно обычному выражению кода, за исключением того, что результат вычисления кода обрабатывается как регулярное выражение и сразу же соответствует. Простой пример
$length = 5;
$char = 'a';
$x = 'aaaaabb';
$x =~ /(??{$char x $length})/x; # matches, there are 5 of 'a' Этот последний пример содержит как обычные, так и выражения кода шаблона. Он определяет, имеет ли двоичная строка 1101010010001... последовательность Фибоначчи 0,1,1,2,3,5,... '1':
$x = "1101010010001000001";
$z0 = ''; $z1 = '0'; # initial conditions
print "It is a Fibonacci sequence\n"
if $x =~ /^1 # match an initial '1'
(?:
((??{ $z0 })) # match some '0'
1 # and then a '1'
(?{ $z0 = $z1; $z1 .= $^N; })
)+ # repeat as needed
$ # that is all there is
/x;
printf "Largest sequence matched was %d\n", length($z1)-length($z0); Помните, что $^N устанавливается в значение, которое было сопоставлено последней завершенной группой захвата. Это печатает
It is a Fibonacci sequence
Largest sequence matched was 5 Ха! Попробуйте это с вашим обычным пакетом регулярных выражений...
Обратите внимание, что переменные $z0 и $z1 не подставляются при компиляции регулярного выражения, как это происходит для обычных переменных вне выражения кода. Скорее, весь блок кода анализируется как код perl одновременно с компиляцией кода, содержащего литеральный шаблон регулярного выражения perl.
Это регулярное выражение без модификатора /x
/^1(?:((??{ $z0 }))1(?{ $z0 = $z1; $z1 .= $^N; }))+$/ что показывает, что пробелы все еще возможны в частях кода. Тем не менее, при работе с кодом и условными выражениями расширенная форма регулярных выражений практически необходима для создания и отладки регулярных выражений.
Команды управления возвратом назад
Perl 5.10 представил ряд команд управления, предназначенных для обеспечения подробного управления процессом возврата назад, путем непосредственного влияния на движок регулярных выражений и предоставления методов мониторинга. Подробное описание см. в разделе "Special Backtracking Control Verbs" в 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' mode" в re.
use re 'debug';
/^(.*)$/s; # output debugging info
use re 'debugcolor';
/^(.*)$/s; # output debugging info in living color Глобальные предикаты debug и debugcolor позволяют получить подробную информацию об отладке компиляции и выполнения регулярных выражений. debugcolor эквивалентно debug, за исключением того, что отладочная информация отображается в цвете на терминалах, которые могут отображать цветовые последовательности termcap. Вот пример вывода:
% perl -e 'use re "debug"; "abc" =~ /a*b+c/;'
Compiling REx 'a*b+c'
size 9 first at 1
1: STAR(4)
2: EXACT <a>(0)
4: PLUS(7)
5: EXACT <b>(0)
7: EXACT <c>(9)
9: END(0)
floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0
Matching REx 'a*b+c' against 'abc'
Setting an EVAL scope, savestack=3
0 <> <abc> | 1: STAR
EXACT <a> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
1 <a> <bc> | 4: PLUS
EXACT <b> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
2 <ab> <c> | 7: EXACT <c>
3 <abc> <> | 9: END
Match successful!
Freeing REx: 'a*b+c' Если вы дошли до этого места в руководстве, вы, вероятно, догадаетесь, что разные части вывода отладки сообщают вам. Первая часть
Compiling REx 'a*b+c'
size 9 first at 1
1: STAR(4)
2: EXACT <a>(0)
4: PLUS(7)
5: EXACT <b>(0)
7: EXACT <c>(9)
9: END(0) описывает этап компиляции. STAR(4) означает, что есть звездочный объект, в данном случае 'a', и если он соответствует, перейти к строке 4, т. е., PLUS(7). Средние строки описывают некоторые эвристики и оптимизации, выполненные до совпадения:
floating 'bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx 'a*b+c' against 'abc'...
Found floating substr 'bc' at offset 1...
Guessed: match at offset 0 Затем выполняется соответствие, а оставшиеся строки описывают процесс:
Matching REx 'a*b+c' against 'abc'
Setting an EVAL scope, savestack=3
0 <> <abc> | 1: STAR
EXACT <a> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
1 <a> <bc> | 4: PLUS
EXACT <b> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
2 <ab> <c> | 7: EXACT <c>
3 <abc> <> | 9: END
Match successful!
Freeing REx: 'a*b+c' Каждый шаг имеет вид n <x> <y>, где <x> — сопоставленная часть строки, а <y> — часть, которая ещё не сопоставлена. | 1: STAR указывает, что Perl находится на строке 1 в списке компиляции выше. Более подробную информацию см. в "Отладка регулярных выражений" в perldebguts.
Альтернативный метод отладки регулярных выражений заключается в встраивании print операторов в само регулярное выражение. Это даёт подробный отчёт о пошаговом выполнении подбора в альтернации:
"that this" =~ m@(?{print "Start at position ", pos, "\n";})
t(?{print "t1\n";})
h(?{print "h1\n";})
i(?{print "i1\n";})
s(?{print "s1\n";})
|
t(?{print "t2\n";})
h(?{print "h2\n";})
a(?{print "a2\n";})
t(?{print "t2\n";})
(?{print "Done at position ", pos, "\n";})
@x; печатает
Start at position 0
t1
h1
t2
h2
a2
t2
Done at position 4 СПРАВКА
Это всего лишь учебник. Полную информацию о регулярных выражениях Perl см. на странице справки по регулярным выражениям perlre.
Для получения дополнительной информации об операторах сопоставления m// и подстановки s/// см. "Операторы цитирования регулярных выражений" в perlop. Сведения об операции split см. в "split" в perlfunc.
Отличным универсальным ресурсом по работе с регулярными выражениями является книга Джеффри Фридла «Мастерство регулярных выражений» (издательство O'Reilly, ISBN 1556592-257-3).
АВТОР И АВТОРСКИЕ ПРАВА
Авторские права (c) 2000 Марк Квале. Все права защищены. Сейчас поддерживается разработчиками Perl.
Этот документ может распространяться на тех же условиях, что и сам Perl.
Благодарности
Пример с кодоном стоп в ДНК взят из примера с почтовым индексом в главе 7 книги «Мастерство регулярных выражений».
Автор выражает благодарность Джефу Пиньяну, Эндрю Джонсону, Питеру Хоуорту, Рональду Дж. Кимбаллу и Джо Смиту за все их ценные замечания.
© 1993–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.32.0/perlretut