Spec-Zone.ru › Perl 5.28

perlfaq6

СОДЕРЖАНИЕ

  • НАЗВАНИЕ
  • ВЕРСИЯ
  • ОПИСАНИЕ
    • Как можно использовать регулярные выражения, не создавая нечитаемый и не поддерживаемый код?
    • У меня проблемы с сопоставлением строк более чем на одну строку. В чем проблема?
    • Как извлечь строки между двумя шаблонами, которые сами находятся на разных строках?
    • Как сопоставить XML, HTML или другие неприятные, уродливые вещи с помощью регулярного выражения?
    • Я поместил регулярное выражение в $/ , но это не сработало. В чем проблема?
    • Как выполнить замену без учета регистра в левой части, сохраняя регистр в правой части?
    • Как заставить \w соответствовать национальным наборам символов?
    • Как сопоставить локально-чувствительную версию /[a-zA-Z]/?
    • Как привести переменную в кавычки для использования в регулярном выражении?
    • Для чего на самом деле нужен /o?
    • Как использовать регулярное выражение для удаления комментариев в стиле C из файла?
    • Можно ли использовать регулярные выражения Perl для сопоставления сбалансированного текста?
    • Что означает, что регулярные выражения жадные? Как можно этого избежать?
    • Как обработать каждое слово в каждой строке?
    • Как вывести сводку частоты слов или строк?
    • Как можно выполнить приближенное сопоставление?
    • Как эффективно сопоставить сразу много регулярных выражений?
    • Почему поиск границ слов с \b не работает?
    • Почему использование $&, $`, или $' замедляет мою программу?
    • Что дает \G в регулярном выражении?
    • Являются ли регулярные выражения Perl ДКА или НКА? Они соответствуют стандарту POSIX?
    • В чем проблема использования grep в контексте void?
    • Как сопоставить строки с многобайтовыми символами?
    • Как сопоставить регулярное выражение, которое находится в переменной?
  • АВТОР И АВТОРСКИЕ ПРАВА

НАЗВАНИЕ

perlfaq6 - Регулярные выражения

ВЕРСИЯ

версия 5.021011

ОПИСАНИЕ

Этот раздел удивительно мал, потому что остальная часть часто задаваемых вопросов содержит ответы, связанные с регулярными выражениями. Например, декодирование 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;
}

Как выполнить замену без учета регистра в левой части, сохраняя регистр в правой части?

Вот прекрасное решение от Ларри Розлера. Оно использует свойства побитового исключающего ИЛИ на строках 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 и более поздних версиях) показывает подробности. В Perls до 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 5.10 или более поздняя версия, это почти тривиально. Вы просто сопоставляете «умно» с массивом объектов регулярных выражений:

my @patterns = ( qr/Fr.d/, qr/B.rn.y/, qr/W.lm./ );

if( $string ~~ @patterns ) {
    ...
};

Умное сопоставление останавливается, когда находит совпадение, поэтому ему не нужно проверять каждое выражение.

Если версия Perl старше 5.10, вам придётся немного поработать. Вы хотите избежать компиляции регулярного выражения каждый раз, когда хотите выполнить сопоставление. В этом примере perl должен перекомпилировать регулярное выражение для каждой итерации цикла foreach, так как у него нет способа узнать, каким будет $pattern:

my @patterns = qw( foo bar baz );

LINE: while( <DATA> ) {
    foreach $pattern ( @patterns ) {
        if( /\b$pattern\b/i ) {
            print;
            next LINE;
        }
    }
}

Оператор qr// появился в Perl 5.005. Он компилирует регулярное выражение, но не применяет его. При использовании предварительно скомпилированной версии регулярного выражения perl выполняет меньше работы. В этом примере я вставил map для преобразования каждого шаблона в его предварительно скомпилированную форму. Остальная часть скрипта такая же, но быстрее:

my @patterns = map { qr/\b$_\b/i } qw( foo bar baz );

LINE: while( <> ) {
    foreach $pattern ( @patterns ) {
        if( /$pattern/ ) {
            print;
            next LINE;
        }
    }
}

В некоторых случаях вы можете объединить несколько шаблонов в одно регулярное выражение. Однако будьте осторожны в ситуациях, требующих возвращения назад.

my $regex = join '|', qw( foo bar baz );

LINE: while( <> ) {
    print if /\b(?:$regex)\b/i;
}

Дополнительную информацию об эффективности регулярных выражений см. в книге «Мастерство регулярных выражений» Джеффри Фридла. Он объясняет, как работает движок регулярных выражений и почему некоторые шаблоны оказываются удивительно неэффективными. Понимание того, как Perl применяет регулярные выражения, позволит вам настраивать их для конкретных ситуаций.

Почему поиск границ слов с \b не работает для меня?

(прислано brian d foy)

Убедитесь, что вы понимаете, что \b фактически делает: это граница между символом слова \w и символом, который не является символом слова. Этот символ, который не является символом слова, может быть \W, но это также может быть начало или конец строки.

Это не (не!) граница между пробелом и не-пробелом, и это не то, что находится между словами, используемыми для создания предложений.

В языке регулярных выражений граница слова (\b) — это «утверждение нулевой ширины», означающее, что она не представляет символ в строке, а условие в определенной позиции.

Для регулярного выражения /\bPerl\b/ должна быть граница слова перед "P" и после "l". До тех пор, пока перед "P" и после "l" стоит что-то, кроме символа слова, шаблон будет соответствовать. Эти строки соответствуют /\bPerl\b/.

"Perl"    # no word char before P or after l
"Perl "   # same as previous (space is not a word char)
"'Perl'"  # the ' char is not a word char
"Perl's"  # no word char before P, non-word char after "l"

Эти строки не соответствуют /\bPerl\b/.

"Perl_"   # _ is a word char!
"Perler"  # no word char before P, but one after l

Вам не нужно использовать \b для сопоставления слов. Вы можете искать символы, не являющиеся символами слова, окружённые символами слова. Эти строки соответствуют шаблону /\b'\b/.

"don't"   # the ' char is surrounded by "n" and "t"
"qep'a'"  # the ' char is surrounded by "p" and "a"

Эти строки не соответствуют /\b'\b/.

"foo'"    # there is no word char after non-word '

Вы также можете использовать дополнение к \b, \B, чтобы указать, что границы слова не должно быть.

В шаблоне /\Bam\B/ должен быть символ слова перед "a" и после "m". Эти шаблоны соответствуют /\Bam\B/:

"llama"   # "am" surrounded by word chars
"Samuel"  # same

Эти строки не соответствуют /\Bam\B/

"Sam"      # no word boundary before "a", but one after "m"
"I am Sam" # "am" surrounded by non-word chars

Почему использование $&, $`, или $' замедляет мою программу?

(предложено Anno Siegel)

Как только Perl видит, что вам нужен один из этих переменных где-либо в программе, он предоставляет их в каждом и каждом соответствии шаблона. Это означает, что при каждом соответствии шаблона вся строка будет скопирована, часть в $`, часть в $&, и часть в $'. Таким образом, штраф наиболее суров при использовании длинных строк и шаблонов, которые часто соответствуют. Избегайте $&, $', и $` если это возможно, но если вы не можете, после того, как вы их использовали, используйте их по желанию, потому что вы уже заплатили за это. Помните, что некоторые алгоритмы действительно ценят их. Начиная с выпуска 5.005, переменная $& не является «дорогостоящей», в отличие от двух других.

Начиная с Perl 5.6.1, специальные переменные @- и @+ могут функционально заменить $`, $& и $'. Эти массивы содержат указатели на начало и конец каждого совпадения (см. perlvar для полной информации), поэтому они предоставляют вам в сущности ту же информацию, но без риска чрезмерного копирования строк.

Perl 5.10 добавил три специальные переменные, ${^MATCH}, ${^PREMATCH}, и ${^POSTMATCH} для выполнения той же работы, но без глобального штрафа за производительность. Perl 5.10 устанавливает эти переменные только в том случае, если вы компилируете или выполняете регулярное выражение с модификатором /p.

Для чего используется \G в регулярном выражении?

Вы используете якорь \G для начала следующего совпадения в той же строке, где закончилось предыдущее совпадение. Двигатель регулярных выражений не может пропустить символы, чтобы найти следующее совпадение с этим якорем, поэтому \G аналогичен якорю начала строки, ^. Якорь \G обычно используется со флагом g . Он использует значение pos() в качестве позиции для начала следующего совпадения. По мере того, как оператор совпадения находит последующие совпадения, он обновляет pos() значением позиции следующего символа после последнего совпадения (или первого символа следующего совпадения, в зависимости от того, как вы хотите это интерпретировать). У каждой строки есть своё значение pos().

Предположим, что вы хотите найти все последовательные пары цифр в строке, например, "1122a44", и прекратить поиск при обнаружении нецифры. Вы хотите найти 11 и 22 , но буква <a> появляется между 22 и 44 , и вы хотите остановиться на a. Простое сопоставление пар цифр пропускает a и всё ещё находит 44.

$_ = "1122a44";
my @pairs = m/(\d\d)/g;   # qw( 11 22 44 )

Если вы используете якорь \G , вы заставляете совпадение после 22 начинаться с a. Регулярное выражение не может сопоставиться там, так как оно не находит цифру, поэтому следующее совпадение терпит неудачу, и оператор совпадения возвращает уже найденные пары.

$_ = "1122a44";
my @pairs = m/\G(\d\d)/g; # qw( 11 22 )

Вы также можете использовать якорь \G в скалярном контексте. Вам всё ещё нужен флаг g.

$_ = "1122a44";
while( m/\G(\d\d)/g ) {
    print "Found $1\n";
}

После того, как совпадение терпит неудачу на букве a, Perl сбрасывает pos() , и следующее совпадение в той же строке начинается с начала.

$_ = "1122a44";
while( m/\G(\d\d)/g ) {
    print "Found $1\n";
}

print "Found $1 after while" if m/(\d\d)/g; # finds "11"

Вы можете отключить сброс pos() при неудаче с помощью флага c , описанного в perlop и perlreref. Последующие совпадения начинаются там, где закончилось последнее успешное совпадение (значение pos() ), даже если между тем неудача в совпадении в той же строке произошла. В этом случае совпадение после цикла while() начинается с a (где закончилось последнее совпадение), и поскольку оно не использует якорь, оно может пропустить a для поиска 44.

$_ = "1122a44";
while( m/\G(\d\d)/gc ) {
    print "Found $1\n";
}

print "Found $1 after while" if m/(\d\d)/g; # finds "44"

Обычно якорь \G используется со флагом c , когда вы хотите попробовать другое совпадение, если одно терпит неудачу, например, в токенайзере. Джеффри Фридл предлагает этот пример, который работает в 5.004 или более поздней версии.

while (<>) {
    chomp;
    PARSER: {
        m/ \G( \d+\b    )/gcx   && do { print "number: $1\n";  redo; };
        m/ \G( \w+      )/gcx   && do { print "word:   $1\n";  redo; };
        m/ \G( \s+      )/gcx   && do { print "space:  $1\n";  redo; };
        m/ \G( [^\w\d]+ )/gcx   && do { print "other:  $1\n";  redo; };
    }
}

Для каждой строки цикл PARSER сначала пытается найти серию цифр, за которыми следует граница слова. Это совпадение должно начинаться с места, где закончилось последнее совпадение (или с начала строки при первом совпадении). Поскольку m/ \G( \d+\b )/gcx использует флаг c , если строка не соответствует этому регулярному выражению, Perl не сбрасывает pos() и следующее совпадение начинается в той же позиции, чтобы попробовать другой шаблон.

Являются ли регулярные выражения Perl DFA или NFA? Они соответствуют стандарту POSIX?

Хотя верно, что регулярные выражения Perl напоминают DFA (детерминированные конечные автоматы) программы egrep(1), они фактически реализованы как NFA (недетерминированные конечные автоматы), чтобы позволить обратное отслеживание и обратные ссылки. И они также не соответствуют стилю POSIX, поскольку они гарантируют худшее поведение во всех случаях. (Похоже, некоторые люди предпочитают гарантии согласованности, даже когда гарантируется медлительность). Обратитесь к книге «Mastering Regular Expressions» (издательство O'Reilly) Джеффри Фридла, чтобы узнать все детали, которые вы могли бы захотеть узнать об этих вопросах (полная ссылка приведена в perlfaq2).

В чем проблема использования grep в пустом контексте?

Проблема в том, что grep создаёт список возвращаемых значений, независимо от контекста. Это означает, что вы заставляете Perl тратить время на построение списка, который вы затем просто отбрасываете. Если список большой, вы тратите и время, и память. Если ваша цель состоит в итерации по списку, используйте цикл for для этой цели.

В версиях Perl старше 5.8.1, map также страдал от этой проблемы. Но начиная с 5.8.1, это было исправлено, и map теперь учитывает контекст — в пустом контексте списки не создаются.

Как я могу сопоставить строки с многобайтовыми символами?

Начиная с Perl 5.6, Perl имеет некоторую поддержку многобайтовых символов. Рекомендуется использовать Perl 5.8 или более позднюю версию. Поддерживаемые наборы многобайтовых символов включают Unicode и устаревшие кодировки через модуль Encode. См. perluniintro, perlunicode и Encode.

Если вы работаете с более старыми версиями Perl, вы можете использовать Unicode с модулем Unicode::String и преобразования символов с модулями Unicode::Map8 и Unicode::Map. Если вы используете японские кодировки, вы можете попробовать использовать jperl 5.005_03.

Наконец, предлагается следующий набор подходов Джеффри Фридлом, чья статья в номере 5 журнала The Perl Journal посвящена именно этому вопросу.

Допустим, у вас есть какая-то странная марсианская кодировка, где пары символов ASCII в верхнем регистре кодируют отдельные марсианские символы (то есть два байта "CV" образуют один марсианский символ, как и два байта "SG", "VS", "XX" и т. д.). Другие байты представляют отдельные символы, как и ASCII.

Итак, строка марсианского текста "I am CVSGXX!" использует 12 байтов для кодировки девяти символов 'I', ' ', 'a', 'm', ' ', 'CV', 'SG', 'XX', '!'.

Теперь предположим, что вы хотите найти одиночный символ /GX/. Perl не знает о марсианском языке, поэтому он найдёт два байта "GX" в строке "I am CVSGXX!", даже если такого символа нет: он просто выглядит так, потому что "SG" находится рядом с "XX", но нет никакого реального "GX". Это большая проблема.

Вот несколько способов (все болезненные) решения этой проблемы:

# Make sure adjacent "martian" bytes are no longer adjacent.
$martian =~ s/([A-Z][A-Z])/ $1 /g;

print "found GX!\n" if $martian =~ /GX/;

Или так:

my @chars = $martian =~ m/([A-Z][A-Z]|[^A-Z])/g;
# above is conceptually similar to:     my @chars = $text =~ m/(.)/g;
#
foreach my $char (@chars) {
    print "found GX!\n", last if $char eq 'GX';
}

Или так:

while ($martian =~ m/\G([A-Z][A-Z]|.)/gs) {  # \G probably unneeded
    if ($1 eq 'GX') {
        print "found GX!\n";
        last;
    }
}

Вот ещё один, немного менее болезненный, способ, предложенный Бенджамином Голдбергом, использующий утверждение «отрицательное обратное отслеживание нулевой ширины».

print "found GX!\n" if    $martian =~ m/
    (?<![A-Z])
    (?:[A-Z][A-Z])*?
    GX
    /x;

Это выполняется, если символ "марсианский" GX присутствует в строке, и терпит неудачу в противном случае. Если вам не нравится использование (?<!), утверждения «отрицательное обратное отслеживание нулевой ширины», вы можете заменить (?<![A-Z]) на (?:^|[^A-Z]).

Это имеет недостаток, что неправильно устанавливает $-[0] и $+[0], но обычно это можно обойти.

Как я могу сопоставить регулярное выражение, хранящееся в переменной?

(предложено brian d foy)

Нам не нужно жестко кодировать шаблоны в операторе match (или в чём-то ещё, что работает с регулярными выражениями). Мы можем поместить шаблон в переменную для последующего использования.

Оператор match — это контекст двойных кавычек, поэтому вы можете интерполировать свою переменную так же, как строку с двойными кавычками. В данном случае вы считываете регулярное выражение как пользовательский ввод и сохраняете его в $regex. После того, как у вас есть шаблон в $regex, вы используете эту переменную в операторе match.

chomp( my $regex = <STDIN> );

if( $string =~ m/$regex/ ) { ... }

Любые специальные символы регулярных выражений в $regex остаются специальными, и шаблон должен по-прежнему быть корректным, иначе Perl выдаст сообщение об ошибке. Например, в этом шаблоне есть непарная скобка.

my $regex = "Unmatched ( paren";

"Two parens to bind them all" =~ m/$regex/;

Когда Perl компилирует регулярное выражение, он обрабатывает скобку как начало совпадения памяти. Когда он не находит закрывающую скобку, он выдаёт сообщение об ошибке:

Unmatched ( in regex; marked by <-- HERE in m/Unmatched ( <-- HERE  paren/ at script line 3.

Вы можете обойти это несколькими способами, в зависимости от ситуации. Во-первых, если вы не хотите, чтобы какие-либо символы в строке были специальными, вы можете экранировать их с помощью quotemeta перед использованием строки.

chomp( my $regex = <STDIN> );
$regex = quotemeta( $regex );

if( $string =~ m/$regex/ ) { ... }

Вы также можете сделать это непосредственно в операторе match, используя последовательности \Q и \E . \Q указывает Perl, где начинать экранирование специальных символов, а \E — где заканчивать (см. perlop для получения дополнительной информации).

chomp( my $regex = <STDIN> );

if( $string =~ m/\Q$regex\E/ ) { ... }

В качестве альтернативы можно использовать qr//, оператор кавычек регулярных выражений (см. perlop для получения более подробной информации). Он приводит выражение к кавычкам и, возможно, компилирует его, и к шаблону можно применять флаги регулярных выражений.

chomp( my $input = <STDIN> );

my $regex = qr/$input/is;

$string =~ m/$regex/  # same as m/$input/is;

Возможно, также стоит перехватить любые ошибки, заключив всё в блок eval.

chomp( my $input = <STDIN> );

eval {
    if( $string =~ m/\Q$input\E/ ) { ... }
};
warn $@ if $@;

Или...

my $regex = eval { qr/$input/is };
if( defined $regex ) {
    $string =~ m/$regex/;
}
else {
    warn $@;
}

АВТОР И АВТОРСКИЕ ПРАВА

Авторские права (c) 1997-2010 Tom Christiansen, Nathan Torkington и другие авторы, как указано. Все права защищены.

Данная документация распространяется свободно; вы можете перераспределять её и/или изменять её на тех же условиях, что и Perl сам по себе.

Независимо от его распространения, все примеры кода в этом файле объявляются общественным достоянием. Вы можете и должны использовать этот код в своих программах для развлечения или с целью получения прибыли, как вам заблагорассудится. Простой комментарий в коде, дающий автору признание, был бы вежливым, но не является обязательным.

© 1993–2020 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.28.3/perlfaq6

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API