Регулярные выражения и сопоставление

Мощь Perl в обработке текста проистекает из использования им регулярных выражений. Регулярное выражение (regular expression, regex или regexp) — это шаблон, описывающий свойства куска текста. Механизм регулярных выражений интерпретирует шаблоны и применяет их для сопоставления или изменения кусков текста.

Базовая документация Perl 5 по регулярным выражениям включает туториал (perldoc perlretut), справочное руководство (perldoc perlreref) и полную документацию (perldoc perlre). Книга Джефри Фридла (Jeffrey Friedl) Освоение регулярных выражений (Mastering Regular Expressions) объясняет теорию и механику того, как работают регулярные выражения. Хотя овладение регулярными выражениями — непростой процесс, даже небольшое знание даст вам большие возможности.

Литералы

Регулярные выражения могут быть простыми шаблонами подстрок:

    my $name = 'Chatfield';
    say 'Found a hat!' if $name =~ /hat/;

Оператор сопоставления (match, m//, сокращёно //) распознаёт регулярное выражение, в этом примере — hat. Этот шаблон — не слово. Он означает «символ h, за которым следует символ a, за которым следует символ t». Каждый символ в шаблоне — неделимый элемент, или атом. Он либо совпадает, либо нет.

Оператор связывания с регулярным выражением (=~) — инфиксный оператор (Фиксность), который применяет регулярное выражение, являющееся его вторым операндом к строке, указанной в качестве первого операнда. При использовании в скалярном контексте, сопоставление возвращает истинное значение в случае успеха. Отрицательная форма оператора связывания (!~) возвращает истинное значение в случае неудачного сопоставления.

Встроенная функция index тоже может осуществлять поиск литеральной подстроки в строке. Использование для этого механизма регулярных выражений — это как полёт на автономном боевом вертолёте в магазин на углу за сыром, но Perl позволяет вам решать, что вы находите более поддерживаемым.

Оператор замены, s///, это, в некотором смысле, циркумфиксный оператор (Фиксность) с двумя операндами. Его первый операнд — регулярное выражение для поиска соответствия при использовании с оператором связывания с регулярным выражением. Второй операнд — подстрока, используемая для замены сопоставленной части первого операнда. Например, вылечить надоедливые летние аллергии можно так:

    my $status = 'I feel ill.';
    $status    =~ s/ill/well/;
    say $status;

Оператор qr// и объединения регулярных выражений

Оператор qr// создаёт регулярные выражения первого класса. Для использования интерполируйте их в оператор сопоставления:

    my $hat = qr/hat/;
    say 'Found a hat!' if $name =~ /$hat/;

…или объедините несколько регулярных выражений в составные шаблоны:

    my $hat   = qr/hat/;
    my $field = qr/field/;

    say 'Found a hat in a field!'
        if $name =~ /$hat$field/;

    like( $name, qr/$hat$field/,
                   'Found a hat in a field!' );

Функция like модуля Test::More проверяет, что первый аргумент соответствует регулярному выражению, указанному как второй аргумент.

Квантификаторы

Регулярные выражения становятся более мощными благодаря использованию квантификаторов регулярных выражений, который позволяют вам указать, сколько раз компонент регулярного выражения может появляться в строке. Самый простой квантификатор — квантификатор «ноль или один», или ?:

    my $cat_or_ct = qr/ca?t/;

    like( 'cat', $cat_or_ct, "'cat' matches /ca?t/" );
    like( 'ct',  $cat_or_ct, "'ct' matches /ca?t/"  );

Любой атом в регулярном выражении, за которым следует символ ?, означает «найти ноль или одни таких атомов». Это регулярное выражение проходит сопоставление, если один или более символов a следуют сразу за символом c и непосредственно предшествуют символу t, будь это литеральная подстрока cat или ct.

Квантификатор «один или более», или +, проходит сопоставление только если есть как минимум одно вхождение квантифицируемого атома:

    my $some_a = qr/ca+t/;

    like( 'cat',    $some_a, "'cat' matches /ca+t/" );
    like( 'caat',   $some_a, "'caat' matches/"      );
    like( 'caaat',  $some_a, "'caaat' matches"      );
    like( 'caaaat', $some_a, "'caaaat' matches"     );

    unlike( 'ct',   $some_a, "'ct' does not match"  );

Нет теоретического ограничения максимального количества квантифицированных атомов, которые могут пройти сопоставление.

Квантификатор «ноль или более», *, находит соответствие ноля или более экземпляров квантифицируемого атома:

    my $any_a = qr/ca*t/;

    like( 'cat',    $any_a, "'cat' matches /ca*t/" );
    like( 'caat',   $any_a, "'caat' matches"       );
    like( 'caaat',  $any_a, "'caaat' matches"      );
    like( 'caaaat', $any_a, "'caaaat' matches"     );
    like( 'ct',     $any_a, "'ct' matches"         );

Как бы глупо это ни выглядело, это позволяет вам указать необязательные компоненты регулярного выражения. Всё же, используйте это экономно: это грубый и дорогой инструмент. Большинство регулярных выражений получают гораздо больше выгоды от использования ? и +, чем от *. Точность намерений зачастую увеличивает ясность.

Количественные квантификаторы выражают конкретное количество возможных повторений атома. {n} означает, что совпадение должно быть найдено ровно n раз.

    # эквивалентно qr/cat/;
    my $only_one_a = qr/ca{1}t/;

    like( 'cat', $only_one_a, "'cat' matches /ca{1}t/" );

{n,} требует как минимум n повторений:

    # эквивалентно qr/ca+t/;
    my $some_a = qr/ca{1,}t/;

    like( 'cat',    $some_a, "'cat' matches /ca{1,}t/" );
    like( 'caat',   $some_a, "'caat' matches"          );
    like( 'caaat',  $some_a, "'caaat' matches"         );
    like( 'caaaat', $some_a, "'caaaat' matches"        );

{n,m} означает, что должно быть как минимум n, но не более m совпадений:

    my $few_a = qr/ca{1,3}t/;

    like( 'cat',    $few_a, "'cat' matches /ca{1,3}t/" );
    like( 'caat',   $few_a, "'caat' matches"           );
    like( 'caaat',  $few_a, "'caaat' matches"          );

    unlike( 'caaaat', $few_a, "'caaaat' doesn't match" );

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

Жадность

Квантификаторы + и *жадные, они пытаются сопоставить настолько большую часть входной строки, насколько можно. Это особенно пагубное поведение. Рассмотрите наивное использование шаблона «ноль или более символов, не являющихся символами перевода строки» .*:

    # плохое регулярное выражение
    my $hot_meal = qr/hot.*meal/;

    say 'Found a hot meal!'
        if 'I have a hot meal' =~ $hot_meal;

    say 'Found a hot meal!'
         if 'one-shot, piecemeal work!' =~ $hot_meal;

Жадные квантификаторы начинают с того, что сопоставляют всё, а затем возвращаются на один символ за раз только если очевидно, что сопоставление не успешно.

Модификатор ?, добавленный к квантификатору, делает жадный квантификатор бережливым:

    my $minimal_greedy = qr/hot.*?meal/;

При использовании нежадного квантификатора, механизм регулярных выражений предпочтёт по возможности наикратчайшее потенциальное совпадение и будет увеличивать количество символов, идентифицируемых токеном .*?, только если текущее количество не проходит сопоставление. Поскольку * требует нуля или более повторений, минимальное потенциальное совпадение для этого токена — ноль символов:

    say 'Found a hot meal'
    if 'ilikeahotmeal' =~ /$minimal_greedy/;

Для нежадной проверки одного или более элементов используйте +?:

    my $minimal_greedy_plus = qr/hot.+?meal/;

    unlike( 'ilikeahotmeal', $minimal_greedy_plus );

    like( 'i like a hot meal', $minimal_greedy_plus );

Модификатор ? применим также и к квантификатору ? (ноль или более совпадений), как и к квантификаторам диапазона. В каждом случае он заставляет регулярное выражение захватывать настолько мало входных данных, насколько возможно.

Жадные шаблоны .+ и .* соблазнительны, но опасны. Кроссвордист (footnote: Любитель разгадывания кроссвордов.), которому нужно заполнить 7 по вертикали («Богатая почва») найдёт слишком много неверных кандидатов со следующим шаблоном:

    my $seven_down   = qr/l$letters_only*m/;

Придётся отбросить Alabama, Belgium и Bethlehem задолго до того, как программа предложит loam. Эти слова не только слишком длинные, но и соответствие начинается с середины слов. Рабочее понимание жадности поможет, но ничто не заменит обширное тестирование на реальных рабочих данных.

Якоря регулярных выражений

Якоря регулярных выражений заставляют механизм регулярных выражений начинать или заканчивать сопоставление на абсолютной позиции. Якорь начала строки (\A) требует, чтобы любое соответствие начиналось с начала строки:

    # также соответствует "lammed", "lawmaker" и "layman"
    my $seven_down = qr/\Al${letters_only}{2}m/;

Якорь конца строки (\Z) требует, чтобы соответствие заканчивалось в конце строки.

    # также соответствует "loom", но уже очевидное улушение
    my $seven_down = qr/\Al${letters_only}{2}m\Z/;

Якорь границы слова (\b) находит соответствие только на границе между символом слова (\w) и несловарным символом (\W). Используйте регулярное выражение с якорями, чтобы найти loam, отбросив Belgium:

    my $seven_down = qr/\bl${letters_only}{2}m\b/;

Метасимволы

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

Метасимвол . означает «соответствие любому символу, исключая перевод строки». Запомните это пояснение; многие новички его забывают. Простой поиск по регулярному выражению — не принимая в счёт явное улучшение с использованием якорей — для 7 по вертикали может быть /l..m/. Конечно, всегда есть более чем один способ получить правильный ответ:

    for my $word (@words)
    {
        next unless length( $word ) == 4;
        next unless $word =~ /l..m/;
        say "Possibility: $word";
    }

Если потенциальные совпадения в @words — более, чем простые английские слова, вы получите ложноположительные результаты. . также соответствует знакам пунктуации, пробельным символам и числам. Будьте точны! Метасимвол \w представляет все буквенно-числовые символы (Юникод и строки) и символ подчёркивания:

        next unless $word =~ /l\w\wm/;

Метасимвол \d соответствует цифрам (тоже в смысле Юникод):

    # ненадёжная проверка телефонных номеров
    next unless $number =~ /\d{3}-\d{3}-\d{4}/;
    say "I have your number: $number";

Используйте метасимвол \s для сопоставления пробельным символам, будь это литеральный пробел, символ табуляции, возврат каретки, подача страницы или перевод строки:

    my $two_three_letter_words = qr/\w{3}\s\w{3}/;

Эти метасимволы имеют отрицательные формы. Используйте \W для поиска любого символа, исключая словарные символы. Используйте \D для поиска нецифровых символов. Используйте \S для поиска чего угодно, кроме пробельных символов. Используйте \B для сопоставления где угодно, кроме границы слова.

Классы символов

Если все эти метасимволы недостаточно точны, определите свой собственный класс символов, заключив их в квадратные скобки:

    my $ascii_vowels = qr/[aeiou]/;
    my $maybe_cat    = qr/c${ascii_vowels}t/;

Без фигурных скобок парсер Perl интерпретирует имя переменной как $ascii_vowelst, что либо вызовет ошибку времени компиляции о неизвестной переменной, или интерполирует в регулярное выражение содержимое существующей переменной $ascii_vowelst.

Символ дефиса (-) позволяет указывать в классе непрерывный диапазон символов, как в регулярном выражении $ascii_letters_only:

    my $ascii_letters_only = qr/[a-zA-Z]/;

Чтобы использовать дефис как член класса, переместите его в начало или в конец:

    my $interesting_punctuation = qr/[-!?]/;

…или экранируйте его:

    my $line_characters = qr/[|=\-_]/;

Используйте символ каре (^) как первый элемент класса символов, чтобы сказать «любые символы кроме этих»:

    my $not_an_ascii_vowel = qr/[^aeiou]/;

Используйте символ каре в любом месте, кроме первой позиции, чтобы сделать его членом класса символов. Чтобы включить в класс символов дефис, поместите его после знака вставки, или в конце класса, или экранируйте его.

Захват

Регулярные выражения позволяют вам группировать и захватывать части совпадения для дальнейшего использования. Извлечь из строки американский телефонный номер вида (202) 456-1111 можно следующим образом:

    my $area_code    = qr/\(\d{3}\)/;
    my $local_number = qr/\d{3}-?\d{4}/;
    my $phone_number = qr/$area_code\s?$local_number/;

Обратите особое внимание на экранирование круглых скобок в $area_code. Скобки имеют особое значение для регулярных выражений Perl 5. Они группируют атомы в единицы большего размера, а также захватывают части сопоставленных строк. Для поиска литеральных скобок, экранируйте их с помощью обратных слешей, как сделано в $area_code.

Именованные захваты

В Perl 5.10 были добавлены именованные захваты, которые позволяют вам захватывать части совпадения при применении регулярного выражения и получать к ним доступ позже, как например при нахождении телефонного номера в строке, содержащей контактную информацию:

    if ($contact_info =~ /(?<phone>$phone_number)/)
    {
        say "Found a number $+{phone}";
    }

Регулярные выражения имеют тенденцию выглядеть как пунктуационный суп, пока вы не соберёте разные части вместе, как кусочки. Синтаксис именованного захвата имеет следующий вид:

    (?<capture name> ... )

Скобки окружают захват. Конструкция ?< name > именует этот конкретный захват, и должна следовать сразу же за левой скобкой. Оставшаяся часть захвата — регулярное выражение.

Когда будет найдено соответствие заключённому в конструкцию шаблону, Perl сохранит совпадающую с шаблоном часть строки в магическую переменную %+. В этом хеше ключ — это имя захвата, а значение — соответствующая часть найденной строки.

Нумерованные захваты

С давних пор Perl поддерживает нумерованные захваты:

    if ($contact_info =~ /($phone_number)/)
    {
        say "Found a number $1";
    }

Эта форма захвата не предоставляет идентифицирующего имени и не сохраняет в %+. Вместо этого, Perl сохраняет захваченную подстроку в последовательности магических переменных. Первый найденный захват попадает в $1, второй — в $2 и так далее. Подсчёт захватов начинается с открывающей скобки захвата; так что первая левая скобка начинает захват в $1, вторая — в $2 и т. д.

Хотя синтаксис именованных захватов длиннее, чем нумерованных, он предоставляет дополнительную ясность. Подсчёт левых скобок — утомительный труд, и объединение регулярных выражений, содержащих нумерованные захваты, слишком сложно. Именованные захваты улучшают поддерживаемость регулярных выражений — хотя коллизии имён возможны, они относительно редки. Минимизируйте риск путём использования нумерованных захватов только в регулярных выражениях верхнего уровня.

В списочном контексте сопоставление регулярному выражению возвращает список захваченных подстрок:

    if (my ($number) = $contact_info =~ /($phone_number)/)
    {
        say "Found a number $number";
    }

Нумерованные захваты также полезны в простых заменах, где именованные захваты могут быть слишком громоздкими:

    my $order = 'Vegan brownies!';

    $order =~ s/Vegan (\w+)/Vegetarian $1/;
    # или
    $order =~ s/Vegan (?<food>\w+)/Vegetarian $+{food}/;

Группировка и выбор

Все предыдущие примеры применяли квантификаторы к простым атомам. Вы можете применять их к любому элементу регулярного выражения:

    my $pork  = qr/pork/;
    my $beans = qr/beans/;

    like( 'pork and beans', qr/\A$pork?.*?$beans/,
         'maybe pork, definitely beans' );

Если вы расширите регулярное выражение вручную, результат может вас удивить:

    my $pork_and_beans = qr/\Apork?.*beans/;

    like( 'pork and beans', qr/$pork_and_beans/,
        'maybe pork, definitely beans' );
    like( 'por and beans', qr/$pork_and_beans/,
         'wait... no phylloquinone here!' );

Иногда определённость помогает аккуратности шаблона:

    my $pork  = qr/pork/;
    my $and   = qr/and/;
    my $beans = qr/beans/;

    like( 'pork and beans', qr/\A$pork? $and? $beans/,
        'maybe pork, maybe and, definitely beans' );

Некоторым регулярным выражениям нужно сопоставить одному варианту или другому. Метасимвол выбора (|) выражает это намерение:

    my $rice  = qr/rice/;
    my $beans = qr/beans/;

    like( 'rice',  qr/$rice|$beans/, 'Found rice'  );
    like( 'beans', qr/$rice|$beans/, 'Found beans' );

Метасимвол выбора указывает, что любой из предшествующих фрагментов может быть сопоставлен. Имейте ввиду, что выбор имеет приоритет (Приоритет) даже ниже, чем атомы:

    like(   'rice',  qr/rice|beans/, 'Found rice'   );
    like(   'beans', qr/rice|beans/, 'Found beans'  );
    unlike( 'ricb',  qr/rice|beans/, 'Found hybrid' );

Хотя легко воспринять rice|beans как ric, за которым следует либо e, либо b, и далее eans, выбор всегда включает весь фрагмент до ближайшего ограничителя регулярного выражения, будь это начало или конце шаблона, круглые скобки, другой символ выбора или квадратная скобка.

Чтобы уменьшить путаницу, используйте именованные фрагменты в переменных ($rice|$beans) или группируйте альтернативные варианты в незахватывающие группы:

    my $starches = qr/(?:pasta|potatoes|rice)/;

Последовательность (?:) группирует серию атомов, не осуществляя захват.

Преобразованное в строку регулярное выражение включает окружающую его незахватывающую группировку; qr/rice|beans/ преобразуется в (?^u:rice|beans).

Другие экранированные последовательности

Для поиска литерального экземпляра метасимвола, экранируйте его с помощью обратного слеша (\). Вы встречали это раньше, где \( обозначало одну левую круглую скобку, а \] — одну правую квадратную скобку. \. обозначает литеральный символ точки вместо атома, захватывающего что угодно, кроме символа перевода строки.

Вам, вероятно, понадобится экранировать метасимвол выбора (|), так же как метасимвол конца строки ($) и квантификаторы (+, ?, *).

Символы отключения метасимволов (\Q and \E) отключают интерпретацию метасимволов в своих границах. Это особенно полезно при получении текста для сопоставления из источника, который вы не контролируете при написании программы:

    my ($text, $literal_text) = @_;

    return $text =~ /\Q$literal_text\E/;

Аргумент $literal_text может содержать всё что угодно — например, строку ** ALERT **. С фрагментом, заключённым в \Q и \E, Perl воспримет регулярное выражение как \*\* ALERT \*\* и попытается найти соответствие литеральным символам звёздочки, а не жадным квантификаторам.

Будьте осторожны при обработке регулярных выражений из недоверенного пользовательского ввода. Злонамеренный мастер регулярных выражений может провести DOS-атаку (denial-of-service) на вашу программу.

Проверки

Якоря регулярных выражений, такие как \A, \b, \B и \Z — форма проверок регулярных выражений, которые требуют, чтобы строка удовлетворяла определённым условиям. Эти проверки не сопоставляются отдельным символам строки. Независимо от того, что содержит строка, регулярное выражение qr/\A/ всегда будет совпадать.

Проверки нулевой ширины соответствуют шаблону. Что ещё более важно, они при этом не потребляют часть шаблона, которой сопоставляются. Например, чтобы найти кота самого по себе, вы можете использовать проверку границы слова:

    my $just_a_cat = qr/cat\b/;

…но если хотите найти животное, не вызывающее бедствий, вы можете использовать проверку нулевой ширины отрицательного заглядывания вперёд:

    my $safe_feline = qr/cat(?!astrophe)/;

Конструкция (?!...) соответствует фразе cat, только если сразу за ней не следует фраза astrophe.

Проверка нулевой ширины положительного заглядывания вперёд:

    my $disastrous_feline = qr/cat(?=astrophe)/;

…соответствует фразе cat, только если сразу за ней следует фраза astrophe. Хотя того же самого можно добиться с помощью обычного регулярного выражения, рассмотрите регулярное выражение для поиска всех некатастрофических слов в словаре, начинающихся с cat:

    my $disastrous_feline = qr/cat(?!astrophe)/;

    while (<$words>)
    {
        chomp;
        next unless /\A(?<cat>$disastrous_feline.*)\Z/;
        say "Found a non-catastrophe '$+{cat}'";
    }

Утверждение нулевой ширины не потребляет ничего из исходной строки, оставляя для сопоставления фрагмент с якорем .*\Z. В противном случае захват захватил бы только часть cat из исходной строки.

Чтобы проверить, что ваше животное никогда не появляется в начале строки, вы можете использовать утверждение нулевой ширины отрицательного заглядывания назад. Эти проверки должны иметь фиксированный размер; вы не можете использовать квантификаторы:

    my $middle_cat = qr/(?<!\A)cat/;

Конструкция (? содержит шаблон фиксированной ширины. Также вы можете выразить, что cat всегда появляется сразу после символа пробела, с помощью утверждения нулевой ширины положительного заглядывания назад:

    my $space_cat = qr/(?<=\s)cat/;

Конструкция (?<=...) содержит шаблон фиксированной ширины. Этот подход может быть полезен при объединении глобального сопоставления регулярному выражению с модификатором \G, но это продвинутая возможность, которую вы, вероятно, не будете использовать часто.

Новейшая возможность регулярных выражений Perl 5 — проверка сохранения \K. Это проверка нулевой ширины положительного заглядывания назад может иметь переменную длину:

    my $spacey_cat = qr/\s+\Kcat/;

    like( 'my cat has been to space', $spacey_cat );
    like( 'my  cat  has  been  to  doublespace',
         $spacey_cat );

\K на удивление полезна для некоторых замен, которые удаляют конец шаблона:

    my $exclamation = 'This is a catastrophe!';
    $exclamation    =~ s/cat\K\w+!/./;

    like( $exclamation, qr/\bcat\./,
                          "That wasn't so bad!" );

Модификаторы регулярных выражений

Некоторые модификаторы изменяют поведение операторов регулярных выражений. Эти модификаторы располагаются на конце операторов сопоставления, замены или qr//. Например, так можно включить регистронезависимое сопоставление:

    my $pet = 'CaMeLiA';

    like( $pet, qr/Camelia/,  'Nice butterfly!'  );
    like( $pet, qr/Camelia/i, 'shift key br0ken' );

Первый like() провалится, потому что строка содержит другие буквы. Второй like() будет успешен, потому что модификатор /i заставляет регулярное выражение игнорировать различия в регистре. M и m во втором регулярном выражении эквивалентны, благодаря модификатору.

Также вы можете включать модификаторы в шаблон:

    my $find_a_cat = qr/(?<feline>(?i)cat)/;

Синтаксис (?i) включает регистронезависимый поиск только внутри включающей его группы: в данном случае, в именованном захвате. Вы можете использовать несколько модификаторов в этой форме. Отключите определённые модификаторы, предварив их знаком минуса (-):

    my $find_a_rational = qr/(?<number>(?-i)Rat)/;

Многострочный оператор, /m, позволяет якорям \A и \Z соответствовать любому переводу строки, входящему в строку.

Модификатор /s воспринимает исходную строку как единую строку, так что метасимвол . будет соответствовать в том числе символу перевода строки. Демьен Конвей (Damian Conway) предлагает мнемонику: /m модифицирует поведение нескольких (multiple) метасимволов регулярного выражения, а /s модифицирует поведение одного (single) метасимвола.

Модификатор /r заставляет операцию замены возвращать результат замены, оставляя исходную строку как есть. Если замена успешна, результатом будет модифицированная копия оригинала. Если замена проваливается (потому что шаблон не соответствует), результатом будет немодифицированная копия оригинала:

    my $status     = 'I am hungry for pie.';
    my $newstatus  = $status =~ s/pie/cake/r;
    my $statuscopy = $status
                   =~ s/liver and onions/bratwurst/r;

    is( $status, 'I am hungry for pie.',
        'original string should be unmodified' );

    like( $newstatus,    qr/cake/,      'cake wanted' );
    unlike( $statuscopy, qr/bratwurst/, 'wurst not'   );

Модификатор /x позволяет вам включать в шаблон дополнительные пробельные символы и комментарии. При использовании этого модификатора, механизм регулярных выражений игнорирует пробельные символы и комментарии. Результат зачастую получается намного более читабельный:

    my $attr_re = qr{
        \A                    # начало строки

        (?:
          [;\n\s]*            # пробелы и двоеточия
          (?:/\*.*?\*/)?      # комментарии в стиле C
        )*

        ATTR

        \s+
        (   U?INTVAL
          | FLOATVAL
          | STRING\s+\*
        )
    }x;

Это регулярное выражение не простое, но комментарии и пробельные символы улучшают его читабельность. Даже если вы составляете регулярные выражения из компилированных фрагментов, модификатор /x всё ещё может улучшить ваш код.

Модификатор /g проверяет соответствие регулярному выражению глобально по всей строке. Это имеет смысл при использовании в замене:

    # успокоить Mitchell estate
    my $contents = slurp( $file );
    $contents    =~ s/Scarlett O'Hara/Mauve Midway/g;

При использовании с сопоставлением — не заменой — метасимвол \G позволяет вам обработать строку в цикле по одному куску за раз. \G соответствует позиции, на которой завершилось самое последнее соответствие. Чтобы обработать плохо закодированный файл, полный американских телефонных номеров в логических кусках, вы можете написать:

    while ($contents =~ /\G(\w{3})(\w{3})(\w{4})/g)
    {
        push @numbers, "($1) $2-$3";
    }

Имейте ввиду, что якорь \G будет занимать последнюю точку в строке, где произошла предыдущаю итерация совпадения. Если предыдущее совпадение закончилось жадным совпадением, таким как .*, следующее соответствие будет иметь меньше доступной строки для сопоставления. Проверки заглядывания вперёд тоже могут помочь.

Модификатор /e позволяет вам писать произвольный код Perl 5 на правой стороне операции замены. Если сопоставление будет успешным, механизм регулярных выражений выполнит код и использует возвращаемое им значение как значение для замены. Приведённый ранее пример глобальной замены может быть выполнен проще с помощью такого кода:

    # успокоить Mitchell estate
    $sequel  =~ s{Scarlett( O'Hara)?}
                 {
                    'Mauve' . defined $1
                            ? ' Midway'
                            : ''
                 }ge;

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

Умное сопоставление

Оператор умного сопоставления, ~~, сравнивает два операнда и возвращает истинное значение, если они успешно сопоставлены. Расплывчатость определения демонстрирует умность оператора: тип сравнения зависит от типов обоих операндов. given (given/when) выполняет неявное умное сопоставление.

Оператор умного сопоставления — инфиксный оператор:

    say 'They match (somehow)' if $loperand ~~ $roperand;

Тип сравнения обычно зависит в первую очередь от типа правого операнда, а затем — от левого. Например, если правый операнд — скаляр с числовой составляющей, сравнение будет использовать числовое равенство. Если правый операнд — регулярное выражение, сравнение будет использовать grep или сопоставление шаблону. Если правый операнд — массив, сравнение будет выполнять grep или рекурсивное умное сопоставление Если правый операнд — хеш, сравнение будет проверять существование одного или более ключей. Большая и устрашающая диаграмма в perldoc perlsyn даёт гораздо больше деталей обо всех сравнениях, которые может выполнять оператор умного сопоставления.

Серьёзное предложение для 5.16 предполагает существенное упрощение умного сопоставления. Чем более сложны ваши операнды, тем больше у вас шансов получить сбивающий с толку результат. Избегайте сравнения объектов и придерживайтесь простых операций между двумя скалярами или одним скаляром и одной агрегатной переменной для достижения наилучших результатов.

С учётом вышесказанного, умное сопоставление может быть полезным:

    my ($x, $y) = (10, 20);
    say 'Not equal numerically' unless $x ~~ $y;

    my $z = '10 little endians';
    say 'Equal numeric-ishally' if $x ~~ $z;

    # сопоставление регулярному выражению
    my $needle = qr/needle/;

    say 'Pattern match' if 'needle' ~~ $needle;

    say 'Grep through array' if @haystack ~~ $needle;

    say 'Grep through hash keys' if %hayhash ~~ $needle;

    say 'Grep through array' if $needle ~~ @haystack;

    say 'Array elements exist as hash keys'
        if %hayhash    ~~ @haystack;

    say 'Smart match elements' if @straw ~~ @haystack;

    say 'Grep through hash keys' if $needle ~~ %hayhash;

    say 'Array elements exist as hash keys'
        if @haystack  ~~ %hayhash;

    say 'Hash keys identical' if %hayhash ~~ %haymap;

Умный поиск соответствия работает даже если один из операндов — ссылка на заданный тип данных:

    say 'Hash keys identical' if %hayhash ~~ \%hayhash;