Еще одно интересный вопрос для собеседования.
Дано регулярное выражение, с которым сопоставляется строка:
$str =~ /^\w+.(\w+)$/
Что окажется в переменной $1 при таких значениях $str?
- ab.cd
- abcd
Еще одно интересный вопрос для собеседования.
Дано регулярное выражение, с которым сопоставляется строка:
$str =~ /^\w+.(\w+)$/
Что окажется в переменной $1 при таких значениях $str?
Бывает, что какой-то возможностью либо настолько редко пользуешься, либо она настолько неудобна, что про нее забываешь напрочь.
Сегодня меня заново познакомили (hsw++) с модификатором регулярных выражений /m.
В дизайне регулярных выражений Perl 5 есть аж пять метасимволов, совпадающие с границами строк (физических или логических):
^ $ \A \Z \z
И на все это наслаивается модификатор /m, изменяющий действие первых двух из списка. И, до кучи, пара символов для переноса строк (\n и \R). Полное безобразие. Иными словами, отказ от /m в Perl 6 — очень правильное решение.
Я погрепал свои старые файлы, и обнаружил, что этот модификатор, даже когда и используется, ставился или по инерции, или, в совсем старом коде, из-за трепетного отношения к магическим в то время регулярным выражениям.
Некоторое время назад в файле perl5110delta появился странный раздел о том, что метасимволы \w и \d в регулярных выражениях должны совпадать только с ASCII-символами:
digit \d [0-9]
word \w [0-9A-Z_a-z]
К счастью, это пока не перекочевало в Perl 5.12, причем независимо от того, определен ли при компиляции символ PERL_LEGACY_UNICODE_CHARCLASS_MAPPINGS или нет.
Программа, от которой ожидается совпадение \w с русскими буквами, по-прежнему работает:
use v5.10;
use utf8;
use open qw(:utf8 :std);
"123abcабв" ~~ /(\w+)/;
say $1;
На печать выводится 123abcабв.
Когда именованные сохраняющие скобки встречаются в регулярных выражениях вместе с обычными, может возникнуть вопрос о том, как перл будет эти скобки нумеровать.
use v5.10;
use strict;my $string = "Fri Mar 19 13:59:06 MSK 2010";
$string =~ /(?<tz>([A-Z]{3}))/;
say $+{tz};
say $1;
say $2;
Программа трижды напечатает MSK.
Итого, ответ на вопроса простой: учитываются все скобки.
Себастьян Виллинг затронул в своем блоге вечную тему о том, как отрезать от строки пробелы с обеих сторон, сделав это в одно действие.
Вместо двух замен
$str =~ s/^\s+//;
$str =~ s/\s+$//;
Себастьян указывает на такой вариант:
$str =~ s/^\s*(.*?)\s*$/$1/;
Интересно почитать и комментарии, где предлагают несколько похожих вариантов с альтернативами в регулярном выражении:
$str =~ s/(^\s+|\s+$)//g;
$str =~ s/(?:^\s+|\s+$)//g;
$str =~ s/^\s*|\s*$//g;
$str =~ s/^\s+|\s+$//g;
P. S. В комментариях, кстати, видно, как почти одно и то же выражение разные люди записывают по-разному.
В магазинах появился русский перевод книги O’Reilly «Регулярные выражения. Сборник рецептов» (Regular Expressions Cookbook), написанная Яном Гойвертсом и Стивеном Левитаном.

(На оригинальной книге, которую я получил от O’Reilly, тоже есть черная полоса с надписью Includes a Regular Expressions Tutorial.)
Содержание
Все примеры в рецептах даны на семи диалектах регулярных выражений: .NET, Java, JavaScript, PCRE, Perl, Python и Ruby.
Цена на books.ru вдвое меньше (650 руб.), чем в «Московском доме книги» (1135 руб.).
Метасимвол \K в регулярных выражениях Perl 5.10 не только полезен, но и, как выясняется на реальном проекте, меняет мысли разработчика :-)
Время от времени возникает задача выделить из текста одно или несколько предложений, чтобы сформировать сниппет записи. Неправильный подход — обрезать строку на заданном числе символов и поставить многоточие (Фейсбук в транслящиях вообще обрезает строку по байтам, и от русских букв иногда отрезается половинка юникодной последовательности). Чуть более приятный результат (но подход настолько же неправилен) дает отсечение по границе слова (как делает Яндекс в поисках по блогам).
Обрезать же текст на границе предложения (ну или по крайней мере, после точки) одним регулярным выражением без \K не очень удобно (приходится задумываться о том, чтобы угодить коротким предложениям).
С метасимволом \K это проще:
$snippet =~ s{^.{150}[^\.]+\.\K.*$}{};
В регулярных выражениях, доступных в Perl 5.10, появился метасимвол \K, который устанавливает точку «невозврата», до которой строка не должна измениться даже после замен.
Мой коллега порадовал еще раз, применив эту фичу в работе.
my $uri = $ENV{REQUEST_URI};
$uri =~ s{^.+?\K\?.*$}{};
Здесь в переменной $uri оказывается адрес без строки запроса, если она была.
Без использования метасимвола \K подобные замены приходилось делать явно, применяя захват и соответствующие переменные для подстановки:
$uri =~ s{^(.+?)\?.*$}{$1};
В документации замечено, что использование \K намного эффективнее (much more efficient), чем явная замена.
До Perl 5.10 аналогичная функциональность была возможна при подключенном модуле Regexp::Keep.
В Perl 5.10 появилась возможность давать имена сохраняемым совпадениям в регулярных выражениях. Чтобы эффективно пользоваться этим нововведением, необходимо разобраться в том, как работают хеши %+ и %-, куда попадают совпавшие фрагменты.
Именованные скобки в регулярных выражениях — это конструкция вида (?<name>...). Если сопоставление оказалось успешным, то к искомому фрагменту возможно обратиться через элементы хешей %+ и %-. Например, в этом примере совпадение будет доступно в переменной %+{name}.
Сложности начинаются, если регулярное выражение составлено таким образом, что в нем оказывается несколько скобок с одинаковым именем. Такое (если это сделано намерено) может произойти в том случае, когда автор выражения, например, предполагает, что на реальных данных должен совпадать только один именованный фрагмент. В другом случае регулярное выражение может быть составлено из отдельных подвыражений: здесь возможны как многократные вхождения, так и многократное сопоставление в цикле.
Вот пример, где из строки 120 EUR in USD, please извлекают названия валют.
use v5.10;
use strict;
use Data::Dumper;
my $string = '120 EUR in USD, please';
my @currencies = $string =~ /(?<currencyName>[A-Z]{3})/g;
say Dumper(\@currencies);
say Dumper(\%+);
say Dumper(\%-);
Именованное выражение ?<currencyName> встречается один раз, но обрабатывается в цикле. В массиве @currencies окажется два элемента:
$VAR1 = [
'EUR',
'USD'
];
А в хешах %+ и %- будет лишь по одному:
$VAR1 = {
'currencyName' => 'USD'
};
$VAR1 = {
'currencyName' => [
'USD'
]
};
Важно обратить внимание на то, что в эти хеши попала последняя совпавшая подстрока.
Теперь вынесем часть регулярного выражения в отдельную переменную и дважды применим ее в сохраняющих скобках с разными именами:
my $re = '[A-Z]{3}';
$string =~ /(?<from>$re).*(?<to>$re)/;
say Dumper(\%+);
say Dumper(\%-);
Поскольку сейчас имена не пересекаются, все совпадения окажутся доступными:
$VAR1 = {
'to' => 'USD',
'from' => 'EUR'
};
$VAR1 = {
'to' => [
'USD'
],
'from' => [
'EUR'
]
};
Обратить внимание, кстати, стоит на то, что в хеше %- поименованные элементы всегда содержат списки, даже если они состоят из одного элемента.
Наличие списков, однако, помогает, когда одноименных элементов становится больше:
$string =~ /(?<currencyName>$re).*(?<currencyName>$re)/;
say Dumper(\%+);
say Dumper(\%-);
В этом примере хеш %+ вновь содержит один элемент — последнее совпадение, но в ключе $-{currencyName} теперь список элементов из двух строк:
$VAR1 = {
'currencyName' => 'EUR'
};
$VAR1 = {
'currencyName' => [
'EUR',
'USD'
]
};
Ничто не мешает вынести и само имя фрагмента в отдельную переменную:
my $named = '?<currencyName>[A-Z]{3}';
$string =~ /($named).*($named)/;
say Dumper(\%+);
say Dumper(\%-);
Результат будет таким же, как и в предыдущем случае:
$VAR1 = {
'currencyName' => 'EUR'
};
$VAR1 = {
'currencyName' => [
'EUR',
'USD'
]
};Важно помнить, что если именованное выражение было необязательным и не совпало, его следы все равно будут присутствовать в хеше %-:
$string = "120 EUR, please";
$string =~ /($named).*($named)?/;
say Dumper(\%-);
Этот пример вернет хеш со списком из строки и значения undef:
$VAR1 = {
'currencyName' => [
'EUR',
undef
]
};
Порядок элементов списка совпадает с порядком именованных скобок в регулярном выражении.