perlfaq6
СОДЕРЖАНИЕ
- НАЗВАНИЕ
- ВЕРСИЯ
- ОПИСАНИЕ
- Как можно использовать регулярные выражения, не создавая нечитаемый и плохо поддерживаемый код?
- У меня проблемы с сопоставлением строк более чем на одной строке. В чем проблема?
- Как извлечь строки между двумя шаблонами, которые сами находятся на разных строках?
- Как сопоставить XML, HTML или другие неприятные, уродливые вещи с регулярным выражением?
- Я поместил регулярное выражение в $/ , но оно не сработало. В чем проблема?
- Как выполнить замену, не учитывая регистр слева, сохраняя регистр справа?
- Как заставить \w соответствовать национальным наборам символов?
- Как сопоставить локально-чувствительную версию /[a-zA-Z]/?
- Как привести переменную в кавычки для использования в регулярном выражении?
- Что на самом деле делает /o?
- Как использовать регулярное выражение для удаления комментариев в стиле C из файла?
- Можно ли использовать регулярные выражения Perl для сопоставления сбалансированного текста?
- Что означает, что регулярные выражения жадные? Как можно обойти это?
- Как обработать каждое слово в каждой строке?
- Как вывести сводку частоты слов или частоты строк?
- Как выполнить приблизительное сопоставление?
- Как эффективно сопоставить сразу много регулярных выражений?
- Почему поиск границ слов с \b не работает для меня?
- Почему использование $&, $`, или $' замедляет мою программу?
- Что хорошего в \G в регулярном выражении?
- Являются ли регулярные выражения Perl ДКА или НКА? Они совместимы с POSIX?
- Что не так с использованием grep в контексте без значения?
- Как сопоставить строки с многобайтовыми символами?
- Как сопоставить регулярное выражение, находящееся в переменной?
- АВТОР И АВТОРСКИЕ ПРАВА
НАЗВАНИЕ
perlfaq6 - Регулярные выражения
ВЕРСИЯ
версия 5.20210411
ОПИСАНИЕ
Этот раздел удивительно мал, потому что остальная часть 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 с версии 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 также может использоваться для создания одного регулярного выражения, которое соответствует списку литеральных строк (не регулярных выражений).
Для получения более подробной информации об эффективности регулярных выражений обратитесь к книге «Мастерство регулярных выражений» Джеффри Фридла. Он объясняет, как работает механизм регулярных выражений и почему некоторые шаблоны оказываются удивительно неэффективными. Понимание того, как 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 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–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.34.0/perlfaq6