perlfaq6
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ВЕРСИЯ
- ОПИСАНИЕ
- Как можно использовать регулярные выражения, не создавая нечитаемый и не поддерживаемый код?
- У меня проблемы с сопоставлением больше, чем одной строки. В чем проблема?
- Как извлечь строки между двумя шаблонами, которые сами находятся на разных строках?
- Как сопоставить XML, HTML или другие неприятные, уродливые вещи с помощью регулярного выражения?
- Я вставил регулярное выражение в $/, но оно не сработало. В чем проблема?
- Как выполнить замену без учета регистра в левой части, сохраняя регистр в правой части?
- Как заставить \w соответствовать национальным наборам символов?
- Как выполнить локально-чувствительное сопоставление с /[a-zA-Z]/?
- Как привести переменную к кавычкам для использования в регулярном выражении?
- Для чего действительно нужен модификатор /o?
- Как использовать регулярное выражение для удаления комментариев в стиле C из файла?
- Можно ли использовать регулярные выражения Perl для сопоставления сбалансированного текста?
- Что означает, что регулярные выражения жадные? Как этого избежать?
- Как обработать каждое слово в каждой строке?
- Как вывести сводку частоты слов или частоты строк?
- Как выполнить приблизительное сопоставление?
- Как эффективно сопоставить сразу несколько регулярных выражений?
- Почему поиск границ слов с \b не работает для меня?
- Почему использование $&, $`, или $' замедляет мою программу?
- Для чего нужен \G в регулярном выражении?
- Являются ли регулярные выражения Perl DFA или NFA? Соответствуют ли они стандарту POSIX?
- В чем проблема использования grep в контексте void?
- Как сопоставить строки с многобайтовыми символами?
- Как сопоставить регулярное выражение, находящееся в переменной?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq6 - Регулярные выражения
ВЕРСИЯ
версия 5.20190126
ОПИСАНИЕ
Этот раздел удивительно короткий, поскольку остальная часть 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 ( 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 и match с флагом 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 от Ларри Рослера. Оно использует свойства побитового XOR на строках 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% медленнее, чем решение на Perl.) Если замена имеет больше символов, чем подставляемая строка, регистр последнего символа используется для остальной части замены.
# 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-стилевыми, потому что те гарантируют худшее поведение во всех случаях. (По-видимому, некоторые люди предпочитают гарантии согласованности, даже когда гарантируется медлительность.) См. книгу «Mastering Regular Expressions» (издательство 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)
Нам не нужно жёстко кодировать шаблоны в операторе совпадения (или в чём-то другом, что работает с регулярными выражениями). Мы можем поместить шаблон в переменную для последующего использования.
Оператор совпадения — это контекст двойных кавычек, поэтому вы можете интерполировать свою переменную так же, как строку в двойных кавычках. В этом случае вы читаете регулярное выражение как ввод пользователя и храните его в $regex. После того, как шаблон в $regex, вы используете эту переменную в операторе совпадения.
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/ ) { ... } Вы также можете сделать это непосредственно в операторе совпадения с помощью последовательностей \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.30.3/perlfaq6