perlfaq6
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ВЕРСИЯ
- ОПИСАНИЕ
- Как можно использовать регулярные выражения, не создавая нечитаемый и плохо поддерживаемый код?
- У меня проблемы с сопоставлением строк более чем из одной строки. В чем проблема?
- Как извлечь строки между двумя шаблонами, которые сами находятся на разных строках?
- Как сопоставить XML, HTML или другие неприятные, уродливые вещи с помощью регулярного выражения?
- Я поместил регулярное выражение в $/ — но оно не сработало. В чём проблема?
- Как выполнить замену без учета регистра в левой части, сохраняя регистр в правой части?
- Как заставить \w соответствовать национальным наборам символов?
- Как сопоставить локально-осознанную версию /[a-zA-Z]/?
- Как привести переменную к цитатному виду для использования в регулярном выражении?
- Что на самом деле означает /o?
- Как использовать регулярное выражение для удаления комментариев в стиле C из файла?
- Можно ли использовать регулярные выражения Perl для сопоставления сбалансированного текста?
- Что означает, что регулярные выражения жадные? Как этого избежать?
- Как обработать каждое слово в каждой строке?
- Как вывести сводку частоты слов или частоты строк?
- Как выполнить приблизительное сопоставление?
- Как эффективно сопоставить сразу несколько регулярных выражений?
- Почему поиск границ слов с \b не работает для меня?
- Почему использование $&, $`, или $' замедляет мою программу?
- Что хорошего в \G в регулярном выражении?
- Являются ли регулярные выражения Perl DFA или NFA? Соответствуют ли они стандарту POSIX?
- В чем проблема использования grep в контексте void?
- Как сопоставить строки с многобайтовыми символами?
- Как сопоставить регулярное выражение, которое находится в переменной?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq6 - Регулярные выражения
ВЕРСИЯ
версия 5.20200523
ОПИСАНИЕ
Этот раздел удивительно мал, потому что остальная часть FAQ содержит ответы, включающие регулярные выражения. Например, декодирование URL и проверка того, является ли что-то числом, может выполняться с помощью регулярных выражений, но эти ответы находятся в других частях этого документа (в perlfaq9: «Как декодировать или создавать эти кодировки % на веб-сайте» и perlfaq4: «Как определить, является ли скаляр числом/целым/числом/вещественным числом» соответственно).
Как можно использовать регулярные выражения, не создавая нечитаемый и плохо поддерживаемый код?
Существует три техники, которые могут сделать регулярные выражения поддерживаемыми и понятными.
- Комментарии вне регулярного выражения
-
Опишите, что вы делаете и как вы это делаете, используя обычные комментарии Perl.
# turn the line into the first word, a colon, and the # number of characters on the rest of the line s/^(\w+)(.*)/ lc($1) . ":" . length($2) /meg; - Комментарии внутри регулярного выражения
-
Модификатор
/xзаставляет пробелы игнорироваться в шаблоне регулярного выражения (кроме символьного класса и некоторых других мест), а также позволяет использовать там обычные комментарии. Как можно догадаться, пробелы и комментарии очень полезны./xпозволяет преобразовать это:s{<(?:[^>'"]*|".*?"|'.*?')+>}{}gs;в это:
s{ < # opening angle bracket (?: # Non-backreffing grouping paren [^>'"] * # 0 or more things that are neither > nor ' nor " | # or else ".*?" # a section between double quotes (stingy match) | # or else '.*?' # a section between single quotes (stingy match) ) + # all occurring one or more times > # closing angle bracket }{}gsx; # replace with nothing, i.e. deleteЭто все еще не совсем так понятно, как проза, но очень полезно для описания значения каждой части шаблона.
- Разные разделители
-
Хотя мы обычно считаем, что шаблоны разделяются символами
/, они могут разделяться практически любым символом. perlre описывает это. Например, вs///выше используются фигурные скобки в качестве разделителей. Выбор другого разделителя может избежать цитирования разделителя внутри шаблона:s/\/usr\/local/\/usr\/share/g; # bad delimiter choice s#/usr/local#/usr/share#g; # betterИспользование логически парных разделителей может быть еще более удобочитаемым:
s{/usr/local/}{/usr/share}g; # better still
У меня проблемы с сопоставлением строк более чем из одной строки. В чем проблема?
Либо в строке, которую вы просматриваете, нет более чем одной строки (вероятно), либо вы не используете правильные модификаторы в своём шаблоне (возможно).
Существует множество способов ввода многострочных данных в строку. Если вы хотите, чтобы это происходило автоматически при чтении входных данных, вам нужно установить $/ (вероятно, в '' для абзацев или undef для всего файла), чтобы разрешить чтение более одной строки за раз.
Прочитайте perlre, чтобы определить, какой из /s и /m (или оба) вы можете захотеть использовать: /s позволяет точке включать новую строку, а /m позволяет символам «^» и «$» соответствовать рядом с новой строкой, а не только в конце строки. Вам нужно убедиться, что у вас есть многострочная строка.
Например, эта программа обнаруживает повторяющиеся слова, даже если они занимают несколько строк (но не абзацев). В этом примере нам не нужен /s , потому что мы не используем точку в регулярном выражении, которое мы хотим пересечь границ строк. Нам также не нужен /m , потому что мы не хотим, чтобы символ «^» или «$» совпадали в любом месте записи рядом с новой строкой. Но крайне важно, чтобы $/ был установлен на значение, отличное от значения по умолчанию, иначе у нас никогда не будет многострочной записи для чтения.
$/ = ''; # read in whole paragraph, not just one line
while ( <> ) {
while ( /\b([\w'-]+)(\s+\g1)+\b/gi ) { # word starts alpha
print "Duplicate $1 at paragraph $.\n";
}
} Вот код, который находит предложения, начинающиеся со «From » (которые многие почтовые клиенты испортят):
$/ = ''; # read in whole paragraph, not just one line
while ( <> ) {
while ( /^From /gm ) { # /m makes ^ match next to \n
print "leading From in paragraph $.\n";
}
} Вот код, который находит все, что находится между START и END в абзаце:
undef $/; # read in whole file, not just one line or paragraph
while ( <> ) {
while ( /START(.*?)END/sgm ) { # /s makes . cross line boundaries
print "$1\n";
}
} Как извлечь строки между двумя шаблонами, которые сами находятся на разных строках?
Вы можете использовать оператор Perl .. (документированный в perlop):
perl -ne 'print if /START/ .. /END/' file1 file2 ... Если вам нужна не текст, а строки, вы можете использовать
perl -0777 -ne 'print "$1\n" while /START(.*?)END/gs' file1 file2 ... Но если вам нужны вложенные вхождения START через END, вы столкнетесь с проблемой, описанной в вопросе в этом разделе о сопоставлении сбалансированного текста.
Вот еще один пример использования ..:
while (<>) {
my $in_header = 1 .. /^$/;
my $in_body = /^$/ .. eof;
# now choose between them
} continue {
$. = 0 if eof; # fix $.
} Как сопоставить XML, HTML или другие неприятные, уродливые вещи с помощью регулярного выражения?
Не используйте регулярные выражения. Используйте модуль и забудьте о регулярных выражениях. Модули XML::LibXML, HTML::TokeParser и HTML::TreeBuilder — хороший старт, хотя в каждом пространстве имен есть другие модули парсинга, специализированные для определённых задач и разных способов их выполнения. Начните с CPAN Search ( http://metacpan.org/ ) и удивитесь всей работе, которую люди уже сделали для вас! :)
Я поместил регулярное выражение в $/ — но оно не сработало. В чём проблема?
$/ должен быть строкой. Вы можете использовать эти примеры, если вам действительно нужно это сделать.
Если у вас есть File::Stream, это просто.
use File::Stream;
my $stream = File::Stream->new(
$filehandle,
separator => qr/\s*,\s*/,
);
print "$_\n" while <$stream>; Если у вас нет File::Stream, вам придётся поработать немного больше.
Вы можете использовать четырёхаргументную форму sysread для постоянного добавления в буфер. После добавления в буфер вы проверяете, есть ли у вас полная строка (используя ваше регулярное выражение).
local $_ = "";
while( sysread FH, $_, 8192, length ) {
while( s/^((?s).*?)your_pattern// ) {
my $record = $1;
# do stuff here.
}
} Вы можете сделать то же самое с foreach и сопоставлением, используя флаг c и якорь \G, если не возражаете, чтобы весь ваш файл был в памяти в конце.
local $_ = "";
while( sysread FH, $_, 8192, length ) {
foreach my $record ( m/\G((?s).*?)your_pattern/gc ) {
# do stuff here.
}
substr( $_, 0, pos ) = "" if pos;
} Как выполнить замену без учета регистра в левой части, сохраняя регистр в правой части?
Вот прекрасное решение Perl от Лэрри Рослера. Оно использует свойства побитового исключающего ИЛИ над строками ASCII.
$_= "this is a TEsT case";
$old = 'test';
$new = 'success';
s{(\Q$old\E)}
{ uc $new | (uc $1 ^ $1) .
(uc(substr $1, -1) ^ substr $1, -1) x
(length($new) - length $1)
}egi;
print; И вот оно как подпрограмма, моделирующая вышеприведённую:
sub preserve_case {
my ($old, $new) = @_;
my $mask = uc $old ^ $old;
uc $new | $mask .
substr($mask, -1) x (length($new) - length($old))
}
$string = "this is a TEsT case";
$string =~ s/(test)/preserve_case($1, "success")/egi;
print "$string\n"; Это выводит:
this is a SUcCESS case В качестве альтернативы, чтобы сохранить регистр слова замены, если оно длиннее исходного, вы можете использовать этот код Джеффа Пиньяна:
sub preserve_case {
my ($from, $to) = @_;
my ($lf, $lt) = map length, @_;
if ($lt < $lf) { $from = substr $from, 0, $lt }
else { $from .= substr $to, $lf }
return uc $to | ($from ^ uc $from);
} Это изменяет предложение на «this is a SUcCess case».
Просто для демонстрации, что программисты C могут писать на C на любом языке программирования, если вы предпочитаете более похожее на C решение, следующий скрипт заставляет замену иметь тот же регистр, что и исходный, символ за символом. (Он также работает примерно на 240% медленнее, чем Perlish решение). Если замена имеет больше символов, чем подлежащая замене строка, регистр последнего символа используется для остальной части замены.
# Original by Nathan Torkington, massaged by Jeffrey Friedl
#
sub preserve_case
{
my ($old, $new) = @_;
my $state = 0; # 0 = no change; 1 = lc; 2 = uc
my ($i, $oldlen, $newlen, $c) = (0, length($old), length($new));
my $len = $oldlen < $newlen ? $oldlen : $newlen;
for ($i = 0; $i < $len; $i++) {
if ($c = substr($old, $i, 1), $c =~ /[\W\d_]/) {
$state = 0;
} elsif (lc $c eq $c) {
substr($new, $i, 1) = lc(substr($new, $i, 1));
$state = 1;
} else {
substr($new, $i, 1) = uc(substr($new, $i, 1));
$state = 2;
}
}
# finish up with any remaining new (for when new is longer than old)
if ($newlen > $oldlen) {
if ($state == 1) {
substr($new, $oldlen) = lc(substr($new, $oldlen));
} elsif ($state == 2) {
substr($new, $oldlen) = uc(substr($new, $oldlen));
}
}
return $new;
}
Как можно сделать, чтобы \w соответствовал наборам национальных символов?
Вставьте use locale; в свой скрипт. Класс символов \w берётся из текущей локали.
Подробности см. в perllocale.
Как можно сопоставить локально-интеллектуальную версию /[a-zA-Z]/?
Вы можете использовать синтаксис POSIX для классов символов /[[:alpha:]]/, описанный в perlre.
Независимо от локали, буквенные символы — это символы в \w без цифр и нижнего подчёркивания. Как регулярное выражение, это выглядит так: /[^\W\d_]/. Его дополнение, небуквальные символы, — это всё в \W вместе с цифрами и нижним подчёркиванием, или /[\W\d_]/.
Как можно привести переменную в кавычки для использования в регулярном выражении?
Парсер Perl будет расширять ссылки $variable и @variable в регулярных выражениях, если разделитель не является одинарной кавычкой. Также помните, что правая часть s/// подстановки рассматривается как строка в двойных кавычках (см. perlop для получения дополнительной информации). Также помните, что все специальные символы регулярного выражения будут обрабатываться, если вы не предваряете подстановку \Q. Вот пример:
$string = "Placido P. Octopus";
$regex = "P.";
$string =~ s/$regex/Polyp/;
# $string is now "Polypacido P. Octopus" Поскольку . является специальным символом в регулярных выражениях и может сопоставляться с любым одиночным символом, регулярное выражение P. здесь сопоставило <Pl> в исходной строке.
Чтобы избежать специального значения ., мы используем \Q:
$string = "Placido P. Octopus";
$regex = "P.";
$string =~ s/\Q$regex/Polyp/;
# $string is now "Placido Polyp Octopus" Использование \Q заставляет . в регулярном выражении обрабатываться как обычный символ, так что P. соответствует P за которым следует точка.
Для чего же /o на самом деле?
(предложено brian d foy)
Опция /o для регулярных выражений (описанная в perlop и perlreref) сообщает Perl о том, что регулярное выражение должно компилироваться только один раз. Это полезно только тогда, когда шаблон содержит переменную. Perl 5.6 и более поздние версии обрабатывают это автоматически, если шаблон не изменяется.
Поскольку оператор сопоставления m//, оператор подстановки s/// и оператор цитирования регулярных выражений qr// являются конструкциями с двойными кавычками, вы можете интерполировать переменные в шаблон. Подробности см. в ответе на вопрос "Как привести переменную в кавычки для использования в регулярном выражении?".
Этот пример берёт регулярное выражение из списка аргументов и выводит строки входных данных, которые ему соответствуют:
my $pattern = shift @ARGV;
while( <> ) {
print if m/$pattern/;
} Версии Perl до 5.6 перекомпилировали регулярное выражение для каждой итерации, даже если $pattern не изменялся. Опция /o предотвращала это, сообщая Perl о компиляции шаблона в первый раз, а затем повторном использовании этого шаблона для последующих итераций:
my $pattern = shift @ARGV;
while( <> ) {
print if m/$pattern/o; # useful for Perl < 5.6
} В версиях 5.6 и более поздних Perl не перекомпилирует регулярное выражение, если переменная не изменилась, поэтому вам, вероятно, не нужна опция /o. Она не вредит, но и не помогает. Если вы хотите, чтобы любая версия Perl компилировала регулярное выражение только один раз, даже если переменная меняется (тем самым используя только её начальное значение), вам всё равно нужна опция /o.
Вы можете наблюдать за работой механизма регулярных выражений Perl, чтобы проверить самостоятельно, перекомпилирует ли Perl регулярное выражение. Предикат use re 'debug' (входит в Perl 5.005 и более поздние версии) показывает подробности. В Perl до 5.6 вы должны увидеть re сообщающее о компиляции регулярного выражения на каждой итерации. В Perl 5.6 или более поздних версиях вы должны увидеть, что re сообщает об этом только для первой итерации.
use re 'debug';
my $regex = 'Perl';
foreach ( qw(Perl Java Ruby Python) ) {
print STDERR "-" x 73, "\n";
print STDERR "Trying $_...\n";
print STDERR "\t$_ is good!\n" if m/$regex/;
} Как использовать регулярное выражение для удаления комментариев в стиле C из файла?
Хотя это действительно можно сделать, это намного сложнее, чем вы думаете. Например, эта однострочная команда
perl -0777 -pe 's{/\*.*?\*/}{}gs' foo.c будет работать во многих, но не во всех случаях. Видите ли, она слишком примитивна для некоторых видов программ C, в частности, для тех, у которых, похоже, комментарии находятся в строках с кавычками. Для этого вам понадобится что-то вроде этого, созданного Джеффри Фридлом и позже изменённого Фредом Картисом.
$/ = undef;
$_ = <>;
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $2 ? $2 : ""#gse;
print; Конечно, это можно было бы более наглядно написать с модификатором /x добавив пробелы и комментарии. Вот расширенный вариант, предоставленный Фредом Картисом.
s{
/\* ## Start of /* ... */ comment
[^*]*\*+ ## Non-* followed by 1-or-more *'s
(
[^/*][^*]*\*+
)* ## 0-or-more things which don't start with /
## but do end with '*'
/ ## End of /* ... */ comment
| ## OR various things which aren't comments:
(
" ## Start of " ... " string
(
\\. ## Escaped char
| ## OR
[^"\\] ## Non "\
)*
" ## End of " ... " string
| ## OR
' ## Start of ' ... ' string
(
\\. ## Escaped char
| ## OR
[^'\\] ## Non '\
)*
' ## End of ' ... ' string
| ## OR
. ## Anything other char
[^/"'\\]* ## Chars which doesn't start a comment, string or escape
)
}{defined $2 ? $2 : ""}gxse; Небольшое изменение также удаляет комментарии C++, возможно, занимающие несколько строк с помощью символа продолжения:
s#/\*[^*]*\*+([^/*][^*]*\*+)*/|//([^\\]|[^\n][\n]?)*?\n|("(\\.|[^"\\])*"|'(\\.|[^'\\])*'|.[^/"'\\]*)#defined $3 ? $3 : ""#gse; Можно ли использовать регулярные выражения Perl для сопоставления сбалансированного текста?
(предложено brian d foy)
Ваш первый вариант — модуль Text::Balanced, который входит в стандартную библиотеку Perl начиная с Perl 5.8. Он имеет различные функции для работы со сложным текстом. Модуль Regexp::Common также может помочь, предоставив готовые шаблоны, которые вы можете использовать.
Начиная с Perl 5.10, вы можете сопоставлять сбалансированный текст с помощью регулярных выражений с использованием рекурсивных шаблонов. До Perl 5.10 вам приходилось прибегать к различным ухищрениям, таким как использование кода Perl в последовательностях (??{}).
Вот пример использования рекурсивного регулярного выражения. Цель — захватить весь текст в угловых скобках, включая текст во вложенных угловых скобках. В этом примере текста есть две «главные» группы: группа с одним уровнем вложенности и группа с двумя уровнями вложенности. Всего в угловых скобках пять групп:
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it. Регулярное выражение для сопоставления сбалансированного текста использует две новые (для Perl 5.10) функции регулярных выражений. Эти функции рассматриваются в perlre, а этот пример — изменённая версия примера из этой документации.
Во-первых, добавление нового модификатора + к любому квантификатору находит самое длинное сопоставление и не возвращается назад. Это важно, так как вы хотите обработать все угловые скобки с помощью рекурсии, а не с помощью возврата. Группа [^<>]++ находит один или несколько символов, не являющихся угловыми скобками, без возврата.
Во-вторых, новый модификатор (?PARNO) ссылается на подшаблон в конкретной группе захвата, заданной PARNO. В следующем регулярном выражении первая группа захвата находит (и запоминает) сбалансированный текст, и вам нужно это же выражение внутри первого буфера, чтобы пройти вложенный текст. Это рекурсивная часть. (?1) использует шаблон в внешней группе захвата как независимую часть регулярного выражения.
Объединив всё вместе, получаем:
#!/usr/local/bin/perl5.10.0
my $string =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE
my @groups = $string =~ m/
( # start of capture group 1
< # match an opening angle bracket
(?:
[^<>]++ # one or more non angle brackets, non backtracking
|
(?1) # found < or >, so recurse to capture group 1
)*
> # match a closing angle bracket
) # end of capture group 1
/xg;
$" = "\n\t";
print "Found:\n\t@groups\n"; Вывод показывает, что Perl нашёл две основные группы:
Found:
<brackets in <nested brackets> >
<another group <nested once <nested twice> > > При небольших доработках можно получить все группы в угловых скобках, даже если они находятся в других угловых скобках. Каждый раз, когда вы получаете сбалансированное соответствие, удалите его внешние разделители (это тот, который вы только что сопоставили, поэтому не сопоставляйте его снова) и добавьте его в очередь строк для обработки. Продолжайте делать это до тех пор, пока не получите никаких совпадений:
#!/usr/local/bin/perl5.10.0
my @queue =<<"HERE";
I have some <brackets in <nested brackets> > and
<another group <nested once <nested twice> > >
and that's it.
HERE
my $regex = qr/
( # start of bracket 1
< # match an opening angle bracket
(?:
[^<>]++ # one or more non angle brackets, non backtracking
|
(?1) # recurse to bracket 1
)*
> # match a closing angle bracket
) # end of bracket 1
/x;
$" = "\n\t";
while( @queue ) {
my $string = shift @queue;
my @groups = $string =~ m/$regex/g;
print "Found:\n\t@groups\n\n" if @groups;
unshift @queue, map { s/^<//; s/>$//; $_ } @groups;
} Вывод показывает все группы. Совпадения внешнего уровня появляются первыми, а вложенные совпадения — позже:
Found:
<brackets in <nested brackets> >
<another group <nested once <nested twice> > >
Found:
<nested brackets>
Found:
<nested once <nested twice> >
Found:
<nested twice> Что означает, что регулярные выражения жадные? Как можно обойти это?
Большинство людей подразумевают, что жадные регулярные выражения сопоставляют как можно больше. Технически говоря, это на самом деле квантификаторы (?, *, +, {}), а не всё выражение; Perl предпочитает локальную жадность и немедленное удовлетворение потребностям по сравнению с общей жадностью. Чтобы получить нежадные версии тех же квантификаторов, используйте (??, *?, +?, {}?).
Пример:
my $s1 = my $s2 = "I am very very cold";
$s1 =~ s/ve.*y //; # I am cold
$s2 =~ s/ve.*?y //; # I am very cold Заметьте, как вторая подстановка перестала сопоставляться, как только встретила "y ". Квантификатор *? фактически сообщает двигателю регулярных выражений о том, чтобы найти сопоставление как можно быстрее и передать управление тому, что стоит дальше, как будто вы играете в «горячую картошку».
Как обработать каждое слово в каждой строке?
Используйте функцию split:
while (<>) {
foreach my $word ( split ) {
# do something with $word here
}
} Обратите внимание, что это не совсем слово в английском смысле; это просто куски последовательных символов, не являющихся пробелами.
Чтобы работать только с алфавитно-цифровыми последовательностями (включая символы подчёркивания), вы можете рассмотреть
while (<>) {
foreach $word (m/(\w+)/g) {
# do something with $word here
}
} Как вывести сводку частоты слов или строк?
Для этого нужно разобрать каждое слово в потоке ввода. Предположим, что под словом вы подразумеваете фрагмент букв, дефисов или апострофов, а не кусочек символов, не являющихся пробелами, как в предыдущем вопросе:
my (%seen);
while (<>) {
while ( /(\b[^\W_\d][\w'-]+\b)/g ) { # misses "`sheep'"
$seen{$1}++;
}
}
while ( my ($word, $count) = each %seen ) {
print "$count $word\n";
} Если вы хотите сделать то же самое для строк, вам не понадобится регулярное выражение:
my (%seen);
while (<>) {
$seen{$_}++;
}
while ( my ($line, $count) = each %seen ) {
print "$count $line";
} Если вы хотите вывести результаты в отсортированном порядке, см. perlfaq4: "Как отсортировать хэш (по значениям вместо ключей)?".
Как выполнить приблизительное сопоставление?
См. модуль String::Approx, доступный из CPAN.
Как эффективно сопоставить сразу много регулярных выражений?
(предложено brian d foy)
Если у вас Perl 5.10 или более поздняя версия, это почти тривиально. Вы просто умно сопоставляете массив объектов регулярных выражений:
my @patterns = ( qr/Fr.d/, qr/B.rn.y/, qr/W.lm./ );
if( $string ~~ @patterns ) {
...
}; Умное сопоставление останавливается, когда находит сопоставление, поэтому не нужно пробовать каждое выражение.
До Perl 5.10 вам нужно немного поработать. Вы хотите избежать компиляции регулярного выражения каждый раз, когда хотите с ним сопоставить. В этом примере Perl должен перекомпилировать регулярное выражение для каждой итерации цикла foreach, так как он не знает, каким будет $pattern:
my @patterns = qw( foo bar baz );
LINE: while( <DATA> ) {
foreach $pattern ( @patterns ) {
if( /\b$pattern\b/i ) {
print;
next LINE;
}
}
} Оператор qr// появился в perl 5.005. Он компилирует регулярное выражение, но не применяет его. При использовании предварительно скомпилированной версии регулярного выражения Perl выполняет меньше работы. В этом примере я вставил map для преобразования каждого шаблона в его предварительно скомпилированную форму. Остальная часть скрипта остаётся той же, но быстрее:
my @patterns = map { qr/\b$_\b/i } qw( foo bar baz );
LINE: while( <> ) {
foreach $pattern ( @patterns ) {
if( /$pattern/ ) {
print;
next LINE;
}
}
} В некоторых случаях вы можете объединить несколько шаблонов в одно регулярное выражение. Однако будьте осторожны в ситуациях, требующих возврата.
my $regex = join '|', qw( foo bar baz );
LINE: while( <> ) {
print if /\b(?:$regex)\b/i;
} Дополнительные сведения об эффективности регулярных выражений см. в книге Mastering Regular Expressions Джеффри Фридла. Он объясняет, как работает движок регулярных выражений, и почему некоторые шаблоны оказываются неожиданно неэффективными. Понимание того, как Perl применяет регулярные выражения, позволит настроить их для конкретных ситуаций.
Почему поиск границ слов с \b не работает для меня?
(предложено brian d foy)
Убедитесь, что вы понимаете, что \b на самом деле делает: это граница между символом слова, \w, и символом, который не является символом слова. Этот символ, который не является символом слова, может быть \W, но это также может быть начало или конец строки.
Это не (не!) граница между пробелом и не-пробелом, и это не то, что находится между словами, используемыми для создания предложений.
В терминах регулярных выражений, граница слова (\b) — это «утверждение нулевой ширины», означающее, что она не представляет символ в строке, а условие в определенной позиции.
Для регулярного выражения /\bPerl\b/ должна быть граница слова перед "P" и после "l". Пока что-то, кроме символа слова, предшествует "P" и следует за "l", шаблон будет соответствовать. Эти строки соответствуют /\bPerl\b/.
"Perl" # no word char before "P" or after "l"
"Perl " # same as previous (space is not a word char)
"'Perl'" # the "'" char is not a word char
"Perl's" # no word char before "P", non-word char after "l" Эти строки не соответствуют /\bPerl\b/.
"Perl_" # "_" is a word char!
"Perler" # no word char before "P", but one after "l" Вам не обязательно использовать \b для поиска слов. Вы можете искать символы, не являющиеся символами слова, окружённые символами слова. Эти строки соответствуют шаблону /\b'\b/.
"don't" # the "'" char is surrounded by "n" and "t"
"qep'a'" # the "'" char is surrounded by "p" and "a" Эти строки не соответствуют /\b'\b/.
"foo'" # there is no word char after non-word "'" Вы также можете использовать дополнение к \b, \B, чтобы указать, что границы слова быть не должно.
В шаблоне /\Bam\B/ должен быть символ слова перед "a" и после "m". Эти шаблоны соответствуют /\Bam\B/:
"llama" # "am" surrounded by word chars
"Samuel" # same Эти строки не соответствуют /\Bam\B/
"Sam" # no word boundary before "a", but one after "m"
"I am Sam" # "am" surrounded by non-word chars Почему использование $&, $`, или $' замедляет мою программу?
(внесён Anno Siegel)
Как только Perl видит, что вам нужен один из этих переменных где-то в программе, он предоставляет их при каждом и каждом соответствии шаблона. Это означает, что при каждом соответствии шаблона вся строка будет скопирована, часть в $`, часть в $&, и часть в $'. Таким образом, штраф наиболее суров с длинными строками и шаблонами, которые часто соответствуют. Избегайте $&, $', и $` если можете, но если не можете, после того, как вы их вообще использовали, используйте их по желанию, так как вы уже заплатили за это. Помните, что некоторые алгоритмы действительно ценят их. Начиная с версии 5.005, переменная $& больше не является «дорогостоящей» так, как другие две.
Начиная с Perl 5.6.1, специальные переменные @- и @+ могут функционально заменить $`, $& и $'. Эти массивы содержат указатели на начало и конец каждого соответствия (подробнее смотрите в perlvar), поэтому они дают вам в сущности ту же информацию, но без риска избыточного копирования строк.
Perl 5.10 добавил три специальных элемента, ${^MATCH}, ${^PREMATCH}, и ${^POSTMATCH}, чтобы выполнить ту же работу, но без глобального штрафа за производительность. Perl 5.10 устанавливает эти переменные только если вы скомпилировали или выполнили регулярное выражение с /p модификатором.
Для чего нужен \G в регулярном выражении?
Вы используете якорь \G для того, чтобы начать следующее соответствие в той же строке, где последнее соответствие закончилось. Двигатель регулярных выражений не может пропустить символы, чтобы найти следующее соответствие с этим якорем, поэтому \G аналогичен якорю начала строки, ^. Якорь \G обычно используется с модификатором g. Он использует значение pos() в качестве позиции для начала следующего соответствия. По мере того, как оператор соответствия производит последовательные соответствия, он обновляет pos() с позицией следующего символа после последнего соответствия (или первого символа следующего соответствия, в зависимости от того, как вы предпочитаете это видеть). Каждая строка имеет своё значение pos().
Предположим, вы хотите найти все последовательные пары цифр в строке, такой как "1122a44", и прекратить поиск, когда встретите нецифру. Вы хотите найти 11 и 22, но буква a появляется между 22 и 44, и вы хотите остановиться на a. Просто поиск пар цифр пропускает a и всё ещё находит 44.
$_ = "1122a44";
my @pairs = m/(\d\d)/g; # qw( 11 22 44 ) Если вы используете якорь \G, вы заставляете соответствие после 22 начинаться с a. Регулярное выражение не может соответствовать там, так как не находит цифру, поэтому следующее соответствие терпит неудачу, и оператор соответствия возвращает найденные им пары.
$_ = "1122a44";
my @pairs = m/\G(\d\d)/g; # qw( 11 22 ) Вы также можете использовать якорь \G в скалярном контексте. Вам всё ещё нужен модификатор g.
$_ = "1122a44";
while( m/\G(\d\d)/g ) {
print "Found $1\n";
} После того, как соответствие терпит неудачу на букве a, perl сбрасывает pos() и следующее соответствие в той же строке начинается с начала.
$_ = "1122a44";
while( m/\G(\d\d)/g ) {
print "Found $1\n";
}
print "Found $1 after while" if m/(\d\d)/g; # finds "11" Вы можете отключить сбросы pos() при неудаче с помощью модификатора c, описанного в perlop и perlreref. Последующие соответствия начинаются там, где закончилось последнее успешное соответствие (значение pos()), даже если соответствие в той же строке потерпело неудачу тем временем. В этом случае, соответствие после цикла while() начинается на a (где остановилось последнее соответствие), и так как оно не использует никаких якорей, оно может пропустить a и найти 44.
$_ = "1122a44";
while( m/\G(\d\d)/gc ) {
print "Found $1\n";
}
print "Found $1 after while" if m/(\d\d)/g; # finds "44" Обычно якорь \G используется с модификатором c при желании попробовать другое соответствие, если одно потерпит неудачу, например, в токенизаторе. Джеффри Фридл предлагает этот пример, который работает в 5.004 или более поздних версиях.
while (<>) {
chomp;
PARSER: {
m/ \G( \d+\b )/gcx && do { print "number: $1\n"; redo; };
m/ \G( \w+ )/gcx && do { print "word: $1\n"; redo; };
m/ \G( \s+ )/gcx && do { print "space: $1\n"; redo; };
m/ \G( [^\w\d]+ )/gcx && do { print "other: $1\n"; redo; };
}
} Для каждой строки, цикл PARSER сначала пытается найти серию цифр, за которыми следует граница слова. Это соответствие должно начинаться в том месте, где остановилось последнее соответствие (или в начале строки при первом соответствии). Так как m/ \G( \d+\b )/gcx использует модификатор c, если строка не соответствует этому регулярному выражению, perl не сбрасывает pos() и следующее соответствие начинается в той же позиции, чтобы попробовать другой шаблон.
Являются ли Perl регулярные выражения DFA или NFA? Совместимы ли они с POSIX?
Хотя верно, что регулярные выражения Perl напоминают DFA (детерминированные конечные автоматы) программы egrep(1), они на самом деле реализованы как NFA (недетерминированные конечные автоматы), чтобы позволить обратное отслеживание и обратные ссылки. И они также не являются POSIX-стиля, потому что они гарантируют худшее поведение во всех случаях. (Кажется, что некоторые люди предпочитают гарантии согласованности, даже когда гарантируется медленность.) Смотрите книгу «Мастерство регулярных выражений» (из O'Reilly) Джеффри Фридла, чтобы узнать обо всём этом (полная ссылка указана в perlfaq2).
В чём недостаток использования grep в пустом контексте?
Проблема в том, что grep создаёт список возвращаемых значений, независимо от контекста. Это означает, что вы заставляете Perl тратить время на создание списка, который вы затем просто выбрасываете. Если список большой, вы теряете и время, и место. Если ваша цель — перебрать список, используйте для этого цикл for.
В версиях Perl, более ранних чем 5.8.1, map также страдает от этой проблемы. Но с 5.8.1 это было исправлено, и map учитывает контекст — в пустом контексте списки не создаются.
Как я могу сопоставить строки с многобайтовыми символами?
Начиная с Perl 5.6, Perl имеет некоторую поддержку многобайтовых символов. Рекомендуется использовать Perl 5.8 или более поздние версии. Поддерживаемые наборы многобайтовых символов включают Unicode и устаревшие кодировки через модуль Encode. Смотрите perluniintro, perlunicode и Encode.
Если вы застряли с более старыми версиями Perl, вы можете использовать Unicode с модулем Unicode::String, а преобразования символов с модулями Unicode::Map8 и Unicode::Map. Если вы используете японские кодировки, попробуйте использовать jperl 5.005_03.
Наконец, следующее множество подходов было предложено Джеффри Фридлом, чья статья в выпуске #5 The Perl Journal говорит об этом вопросе.
Допустим, у вас есть какая-то странная марсианская кодировка, где пары прописных букв ASCII кодируют отдельные марсианские буквы (например, два байта "CV" образуют одну марсианскую букву, как и два байта "SG", "VS", "XX" и т. д.). Другие байты представляют отдельные символы, как и ASCII.
Итак, строка марсианской "I am CVSGXX!" использует 12 байтов для кодировки девяти символов 'I', ' ', 'a', 'm', ' ', 'CV', 'SG', 'XX', '!'.
Теперь, скажем, вы хотите найти одиночный символ /GX/. Perl не знает о марсианском, поэтому он найдёт два байта "GX" в строке "I am CVSGXX!", хотя этого символа нет: он просто выглядит так, потому что "SG" находится рядом с "XX", но нет реального "GX". Это большая проблема.
Вот несколько способов, все болезненные, чтобы с этим справиться:
# Make sure adjacent "martian" bytes are no longer adjacent.
$martian =~ s/([A-Z][A-Z])/ $1 /g;
print "found GX!\n" if $martian =~ /GX/; Или так:
my @chars = $martian =~ m/([A-Z][A-Z]|[^A-Z])/g;
# above is conceptually similar to: my @chars = $text =~ m/(.)/g;
#
foreach my $char (@chars) {
print "found GX!\n", last if $char eq 'GX';
} Или так:
while ($martian =~ m/\G([A-Z][A-Z]|.)/gs) { # \G probably unneeded
if ($1 eq 'GX') {
print "found GX!\n";
last;
}
} Вот ещё один, немного менее болезненный способ сделать это от Бенджамина Голдберга, который использует утверждение отрицательного вложенного поиска нулевой ширины.
print "found GX!\n" if $martian =~ m/
(?<![A-Z])
(?:[A-Z][A-Z])*?
GX
/x; Это даёт успех, если марсианский символ GX есть в строке, и терпит неудачу в противном случае. Если вы не хотите использовать (?<!), утверждение отрицательного вложенного поиска нулевой ширины, вы можете заменить (?<![A-Z]) на (?:^|[^A-Z]).
Это имеет недостаток, что неправильно помещает что-то в $-[0] и $+[0], но это обычно можно обойти.
Как я могу сопоставить регулярное выражение, которое находится в переменной?
(внесён brian d foy)
Нам не нужно жёстко кодировать шаблоны в оператор match (или в чём-то другом, что работает с регулярными выражениями). Мы можем поместить шаблон в переменную для последующего использования.
Оператор match — это контекст двойных кавычек, поэтому вы можете интерполировать свою переменную так же, как и строку в двойных кавычках. В этом случае, вы читаете регулярное выражение как пользовательский ввод и сохраняете его в $regex. После того, как у вас есть шаблон в $regex, вы используете эту переменную в операторе match.
chomp( my $regex = <STDIN> );
if( $string =~ m/$regex/ ) { ... } Любые символы регулярного выражения в $regex всё ещё являются специальными, и шаблон всё ещё должен быть правильным, иначе Perl выдаст сообщение об ошибке. Например, в этом шаблоне есть непарная скобка.
my $regex = "Unmatched ( paren";
"Two parens to bind them all" =~ m/$regex/; Когда Perl компилирует регулярное выражение, он рассматривает скобку как начало соответствия памяти. Когда он не находит закрывающую скобку, он выдаёт сообщение об ошибке:
Unmatched ( in regex; marked by <-- HERE in m/Unmatched ( <-- HERE paren/ at script line 3. Вы можете обойти эту проблему несколькими способами в зависимости от ситуации. Во-первых, если вы не хотите, чтобы какие-либо символы в строке были специальными, вы можете экранировать их с помощью quotemeta перед использованием строки.
chomp( my $regex = <STDIN> );
$regex = quotemeta( $regex );
if( $string =~ m/$regex/ ) { ... } Вы также можете сделать это непосредственно в операторе match, используя \Q и \E последовательности. \Q сообщает Perl, где начинать экранирование специальных символов, а \E — где заканчивать (см. perlop для более подробной информации).
chomp( my $regex = <STDIN> );
if( $string =~ m/\Q$regex\E/ ) { ... } В качестве альтернативы вы можете использовать qr//, оператор экранирования регулярного выражения (см. perlop для получения более подробной информации). Он экранирует и, возможно, компилирует шаблон, и вы можете применить флаги регулярных выражений к шаблону.
chomp( my $input = <STDIN> );
my $regex = qr/$input/is;
$string =~ m/$regex/ # same as m/$input/is; Вы также можете перехватить любые ошибки, обернув всё это в eval блок.
chomp( my $input = <STDIN> );
eval {
if( $string =~ m/\Q$input\E/ ) { ... }
};
warn $@ if $@; Или...
my $regex = eval { qr/$input/is };
if( defined $regex ) {
$string =~ m/$regex/;
}
else {
warn $@;
} АВТОР И АВТОРСКИЕ ПРАВА
Авторские права (c) 1997-2010 Tom Christiansen, Nathan Torkington и другие авторы, как указано. Все права защищены.
Данная документация бесплатна; вы можете перераспределять её и/или изменять её в соответствии с теми же условиями, что и Perl сам по себе.
Независимо от его распространения, все примеры кода в этом файле объявляются всеобщей собственностью. Вам разрешено и рекомендуется использовать этот код в своих программах для развлечения или с целью получения прибыли, как вам заблагорассудится. Простая ссылка в коде на авторов была бы вежливой, но не является обязательной.
© 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/perlfaq6