perlfaq6
СОДЕРЖАНИЕ
- ИМЯ
- ВЕРСИЯ
- ОПИСАНИЕ
- Как можно использовать регулярные выражения, не создавая нечитаемый и плохо поддерживаемый код?
- У меня проблемы с сопоставлением строк, занимающих более одной строки. В чём дело?
- Как можно извлечь строки между двумя шаблонами, которые сами находятся на разных строках?
- Как сопоставить XML, HTML или другие неприятные, уродливые вещи с помощью регулярного выражения?
- Я ввёл регулярное выражение в $/ , но оно не сработало. В чём проблема?
- Как выполнить замену, не учитывая регистр в левой части, сохраняя регистр в правой части?
- Как заставить \w соответствовать национальным наборам символов?
- Как сопоставить локально-чувствительную версию /[a-zA-Z]/?
- Как привести переменную к цитируемому виду для использования в регулярном выражении?
- Что на самом деле делает /o?
- Как использовать регулярное выражение для удаления комментариев в стиле C из файла?
- Можно ли использовать регулярные выражения Perl для сопоставления сбалансированного текста?
- Что означает, что регулярные выражения жадные? Как этого избежать?
- Как обработать каждое слово в каждой строке?
- Как вывести сводку частоты слов или строк?
- Как выполнить приблизительное соответствие?
- Как эффективно сопоставить несколько регулярных выражений одновременно?
- Почему поиск границ слов с \b не работает?
- Почему использование $&, $`, или $' замедляет программу?
- Что полезного в \G в регулярном выражении?
- Являются ли регулярные выражения Perl DFA или NFA? Они соответствуют стандарту POSIX?
- В чём проблема использования grep в контексте ничего не делающего?
- Как сопоставить строки с многобайтовыми символами?
- Как сопоставить регулярное выражение, находящееся в переменной?
- АВТОР И АВТОРСКИЕ ПРАВА
ИМЯ
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 и соответствием с помощью флага 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;
} Как выполнить замену, не учитывая регистр в левой части, сохраняя регистр в правой части?
Вот замечательное решение от Лэрри Рослера. Оно использует свойства побитового 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% медленнее, чем перльское решение.) Если замена содержит больше символов, чем подлежащая замене строка, регистр последнего символа используется для остальной части замены.
# 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 DFA или NFA? Соответствуют ли они стандарту POSIX?
Хотя верно, что регулярные выражения Perl напоминают DFA (детерминированные конечные автоматы) программы egrep(1), они фактически реализованы как NFA (недетерминированные конечные автоматы), чтобы позволить обратный откат и обратные ссылки. И они не являются стилизованными по POSIX, потому что они гарантируют худшее поведение во всех случаях. (Похоже, что некоторые люди предпочитают гарантии согласованности, даже когда гарантируется медлительность.) См. книгу «Мастерство регулярных выражений» (издательство O'Reilly) Джеффри Фридла за всеми подробностями, о которых вы только могли мечтать узнать об этих вопросах (полная ссылка приведена в perlfaq2).
Что не так с использованием grep в пустом контексте?
Проблема в том, что grep строит список возврата независимо от контекста. Это означает, что вы заставляете Perl тратить усилия на создание списка, который вы затем просто выбрасываете. Если список большой, вы тратите и время, и память. Если вашей целью является итерация по списку, используйте для этой цели цикл for.
В версиях Perl, более ранних, чем 5.8.1, map страдает от той же проблемы. Но начиная с 5.8.1, эта проблема была решена, и map учитывает контекст — в пустом контексте списки не создаются.
Как я могу соответствовать строкам с многобайтовыми символами?
Начиная с Perl 5.6, Perl имеет некоторую поддержку многобайтовых символов. Рекомендуется использовать Perl 5.8 или более позднюю версию. Поддерживаемые многобайтовые наборы символов включают Unicode и устаревшие кодировки через модуль Encode. См. perluniintro, perlunicode и Encode.
Если вы используете более старые версии Perl, вы можете использовать Unicode с помощью модуля Unicode::String и преобразования символов с помощью модулей Unicode::Map8 и Unicode::Map. Если вы используете японские кодировки, вы можете попробовать использовать jperl 5.005_03.
Наконец, следующий набор подходов предложил Джеффри Фридл, чья статья в номере 5 журнала The Perl Journal посвящена этому вопросу.
Допустим, у вас есть какая-то странная марсианская кодировка, где пары прописных букв ASCII кодируют один марсианский символ (например, два байта "CV" образуют один марсианский символ, так же как и два байта "SG", "VS", "XX" и т. д.). Другие байты представляют отдельные символы, как и ASCII.
Итак, строка марсианского текста "I am CVSGXX!" использует 12 байтов для кодирования девяти символов 'I', ' ', 'a', 'm', ' ', 'CV', 'SG', 'XX', '!'.
Теперь, скажем, вы хотите найти один символ /GX/. Perl не знает о марсианской кодировке, поэтому он найдёт два байта "GX" в строке "I am CVSGXX!", даже если такого символа там нет: он просто выглядит так, как будто он есть, потому что "SG" находится рядом с "XX", но на самом деле "GX" нет. Это большая проблема.
Вот несколько способов, все болезненные, для работы с этим:
# Make sure adjacent "martian" bytes are no longer adjacent.
$martian =~ s/([A-Z][A-Z])/ $1 /g;
print "found GX!\n" if $martian =~ /GX/; Или так:
my @chars = $martian =~ m/([A-Z][A-Z]|[^A-Z])/g;
# above is conceptually similar to: my @chars = $text =~ m/(.)/g;
#
foreach my $char (@chars) {
print "found GX!\n", last if $char eq 'GX';
} Или так:
while ($martian =~ m/\G([A-Z][A-Z]|.)/gs) { # \G probably unneeded
if ($1 eq 'GX') {
print "found GX!\n";
last;
}
} Вот ещё один, немного менее болезненный, способ сделать это от Бенджамина Гольдберга, который использует отрицательное утверждение о поиске назад с нулевой шириной.
print "found GX!\n" if $martian =~ m/
(?<![A-Z])
(?:[A-Z][A-Z])*?
GX
/x; Это выполняется, если символ "марсианский" GX есть в строке, и терпит неудачу в противном случае. Если вам не нравится использование (?<!), утверждения о поиске назад с нулевой шириной, вы можете заменить (?<![A-Z]) на (?:^|[^A-Z]).
Это имеет недостаток в том, что в $-[0] и $+[0] записывается не то, что нужно, но это обычно можно обойти.
Как мне сопоставить регулярное выражение, которое находится в переменной?
(предложено brian d foy)
Нам не нужно жёстко кодировать шаблоны в оператор соответствия (или в чём-то ещё, что работает с регулярными выражениями). Мы можем поместить шаблон в переменную для последующего использования.
Оператор соответствия — это контекст двойных кавычек, поэтому вы можете интерполировать свою переменную, как и строку с двойными кавычками. В этом случае вы считываете регулярное выражение как пользовательский ввод и сохраняете его в $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–2023 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.38.0/perlfaq6