perlfaq6
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ВЕРСИЯ
- ОПИСАНИЕ
- Как можно использовать регулярные выражения без создания нечитаемого и плохо поддерживаемого кода?
- У меня проблемы с сопоставлением строк более чем на одной строке. В чём проблема?
- Как извлечь строки между двумя шаблонами, которые сами находятся на разных строках?
- Как сопоставить XML, HTML или другие неприятные, некрасивые вещи с помощью регулярного выражения?
- Я поместил регулярное выражение в $/, но оно не сработало. В чём проблема?
- Как выполнить замену регистронезависимо на левой стороне, сохраняя регистр на правой?
- Как сделать, чтобы \w соответствовала национальным наборам символов?
- Как сопоставить регистронезависимую версию /[a-zA-Z]?
- Как привести переменную в цитируемый вид для использования в регулярном выражении?
- Для чего действительно используется /o?
- Как использовать регулярное выражение для удаления комментариев в стиле C из файла?
- Можно ли использовать регулярные выражения Perl для сопоставления сбалансированного текста?
- Что означает, что регулярные выражения жадные? Как этого избежать?
- Как обработать каждое слово в каждой строке?
- Как вывести сводку частоты слов или частоты строк?
- Как выполнить приблизительное сопоставление?
- Как эффективно сопоставить сразу много регулярных выражений?
- Почему поиск границ слов с \b не работает?
- Почему использование $&, $`, или $' замедляет мою программу?
- Что полезного в \G в регулярном выражении?
- Являются ли регулярные выражения Perl ДКА или НКА? Они соответствуют POSIX?
- Что не так с использованием grep в контексте void?
- Как сопоставить строки с многобайтовыми символами?
- Как сопоставить регулярное выражение, которое находится в переменной?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq6 - Регулярные выражения
ВЕРСИЯ
версия 5.20210520
ОПИСАНИЕ
Этот раздел удивительно мал, потому что остальная часть 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 от Ларри Розлера. Оно использует свойства побитового исключающего ИЛИ на строках 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 с версии 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 должен перекомпилировать регулярное выражение на каждой итерации цикла foreach, поскольку $pattern может изменяться:
my @patterns = qw( fo+ ba[rz] );
LINE: while( my $line = <> ) {
foreach my $pattern ( @patterns ) {
if( $line =~ m/\b$pattern\b/i ) {
print $line;
next LINE;
}
}
} Оператор qr// компилирует регулярное выражение, но не применяет его. При использовании предварительно скомпилированной версии регулярного выражения perl выполняет меньше работы. В этом примере я вставил map для преобразования каждого шаблона в его предварительно скомпилированную форму. Остальная часть скрипта остаётся такой же, но работает быстрее:
my @patterns = map { qr/\b$_\b/i } qw( fo+ ba[rz] );
LINE: while( my $line = <> ) {
foreach my $pattern ( @patterns ) {
if( $line =~ m/$pattern/ ) {
print $line;
next LINE;
}
}
} В некоторых случаях вы можете объединить несколько шаблонов в одно регулярное выражение. Однако следует быть осторожным в ситуациях, которые требуют обратного отката. В этом примере регулярное выражение компилируется только один раз, потому что $regex не меняется между итерациями:
my $regex = join '|', qw( fo+ ba[rz] );
while( my $line = <> ) {
print if $line =~ m/\b(?:$regex)\b/i;
} Функция "list2re" в Data::Munge в CPAN также может использоваться для создания одного регулярного выражения, которое соответствует списку строковых литералов (а не регулярных выражений).
Для получения более подробной информации об эффективности регулярных выражений см. книгу «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 в тех случаях, когда вы хотите попробовать другое сопоставление, если одно потерпит неудачу, например, в токенизаторе. Jeffrey Friedl предлагает этот пример, который работает в 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-регулярные выражения ДКА или НКА? Соответствуют ли они стандарту POSIX?
Хотя верно, что регулярные выражения Perl напоминают ДКА (детерминированные конечные автоматы) программы egrep(1), они фактически реализованы как НКА (недетерминированные конечные автоматы) для поддержки обратного следования и обратных ссылок. И они также не являются 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.
Наконец, Jeffrey Friedl предложил следующий набор подходов, в своей статье в выпуске № 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;
}
} Вот ещё один, немного менее болезненный способ, предложенный Benjamin Goldberg, который использует утверждение о нулевой ширине отрицательного взгляда назад.
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 Тома Кристиансена, Нейтана Торкинтона и других авторов, как указано. Все права защищены.
Данная документация бесплатна; вы можете перераспределять и/или изменять её на тех же условиях, что и сам Perl.
Независимо от способа распространения, все примеры кода в этом файле передаются в общественное достояние. Вы можете и должны использовать этот код в своих программах для развлечения или получения прибыли по своему усмотрению. Простой комментарий в коде, дающий заслуги, был бы вежливым, но не является обязательным.
© 1993–2021 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.36.0/perlfaq6