Perl регулярные выражения: интерполяция переменных и кода в регулярное выражение

Это глава из моей книги "Perl для профессиональных программистов. Регулярные выражения", которая вышла в изд-ве "Бином".

Рассматривается использование переменных внутри регулярных выражений, опасности, связанные с их применением, а также экранирование метасимволов, различие литералов и объектов регулярных выражений. Как мы уже знаем, если регулярное выражение ограничено не апострофами, то перед его трансляцией производится интерполяция скалярных переменных $scalar и массивов @array. Символы % и & не являются метасимволами в регулярном выражении, поэтому хеши и результаты вызовов подпрограмм напрямую не интерполируются.

7.1 Интерполяция переменных и кода в строку

Сначала рассмотрим примеры интерполяции значений в строки, которые ограничены двойными кавычками. Аналогично интерполяция происходит в строки, ограниченные обратными кавычками: ``.

7.1.1. Интерполяция скаляра

Интерполяция в строку простого скаляра: "Text $name text". Если нет разделителя после имени переменной, то это имя надо взять в фигурные скобки: "Text ${name}text".

Интерполяция в строку переменной с индексами (элемента массива): "Text $name[1] text" или "Text $name[$ind] text". Аналогично происходит интерполяция элемента массива массивов:

 my @a=([1,2],[3,4]);
 $_="aaa$a[1][0]aaa";
 print $_;

Будет напечатано:
aaa3aaa

7.1.2. Интерполяция массива

Массивы интерполируются всеми своими значениями:

 my @a=(1,2,3,4);
 $_="abc@{a}def";
 print $_;

На печати окажется
abc1 2 3 4def

Не проходит аналогичная интерполяция многомерного массива:

 my @a=([1,2],[3,4]);
 $_="abc@{a}def";
 print $_;

Напечатается
abcARRAY(0x224ea4) ARRAY(0x224f88)def

На самом деле в Perl нет многомерных массивов как в C или Pascal. @a является массивом из двух ссылок (размер массива @a равен двум). При его интерполяции выводятся эти две ссылки на два подмассива. Для интерполяции элементов массива надо подставлять массивы конечных элементов (не ссылок):

 my @a=([1,2],[3,4]);
 $_="abc@{$a[0]}@{$a[1]}def";
 print $_;

Напечатается
abc1 23 4def

Конструкция @{$a[0]} означает следующее: $a[0] является ссылкой на массив, а оператор @ ее разыменовывает, получая в результате сам этот массив.

При интерполяции массивов и при их выводе оператором print в качестве разделителя элементов массива используется значение специальной переменной $". По умолчанию это пробел. Но можно поменять это значение на другое:

 $"=',';
 my @a=([1,2],[3,4]);
 $_="abc@{$a[0]},@{$a[1]}def";
 print $_;

На печать выйдет
abc1,2,3,4def

7.1.3. Интерполяция вызова подпрограммы, возвращающей ссылку

Вот пример интерполяции результата вызова подпрограммы, которая возвращает ссылку:

#!/usr/bin/perl -w
use strict;
no strict 'refs';

sub subr() { return 'abc' }

our $abc=123;
$_="${&subr}";
print $_;

На печать выходит 123.

Здесь используется разыменование именной ссылки abc, поэтому переменная $abc должна быть глобальной, т.к. имена переменных my не находятся в глобальной таблице имен typeglob. Кроме того, если вы применяете директиву use strict, то надо разрешить разыменование именных ссылок: no strict 'refs'.

Подпрограмма subr возвращает строку abc, которая является именем переменной.

Конструкция ${&subr} разыменовывает эту ссылку и возвращает значение переменной $abc. Задавая разные значения переменной $abc или разные возвращаемые подпрограммой subr значения, будем получать разные результаты интерполяции.

Здесь обратите внимание на разыменовывающий префикс & перед именем подпрограммы.

Он здесь всегда обязателен. Этот способ интерполяции годится только для написанных вами подпрограмм.

7.1.4. Интерполяция кода Perl

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

 my @a=([1,2],[3,4]);
 $_="abc${\(join ',',(@{$a[0]},@{$a[1]}))}def";
 print $_;

Результат получается тем же:
abc1,2,3,4def

Конструкция \(join ',',(@{$a[0]},@{$a[1]}) обрабатывается как ссылка на анонимный список, элементы которого выдает функция join. Конструкция {:} выполняется как блок команд, она возвращает ссылку на этот анонимный список, которая оператором @ разыменовывается и получается сам список. Обратите внимание, что если бы мы внутри этой конструкции использовали ограничители строки ", то их надо было бы маскировать обратными слэшами:

 my @a=([1,2],[3,4]);
 $_="abc${\(join \",\",(@{$a[0]},@{$a[1]}))}def";
 print $_;

Этот пример можно переписать с использованием конструкции @{[ список ]}:

 my @a=([1,2],[3,4]);
 $_="abc@{[join ',',(@{$a[0]},@{$a[1]})]}def";
 print $_;

Результат тот же:
abc1,2,3,4def

Конструкция [ список ] является генератором анонимного массива, ссылка на который разыменовывается префиксом @.

Так же можно интерполировать результат выполнения собственной подпрограммы, но если эта подпрограмма объявлена после ее использования, то перед ее именем надо поставить разыменовывающий префикс &:

 $_="abc${\(&subr('Bill'))}def";
 print $_;

 sub subr($) { return "Hello $_[0]!" }

На печати получим:
abcHello Bill!def

Аналогично, в строку можно интерполировать другие операторы Perl, например:

 my $a=3;
 $_="abc${\($a == 3 ? '$a == 3' : '$a != 3')}def";
 print $_;

Получаем:
abc$a == 3def

Здесь обратите внимание на то, что хотя код интерполируется в строку, ограниченную двойными кавычками, маскировать $ внутри апострофов не надо, т.к. они обрабатываются внутри кода Perl. В ином случае внутри строки, ограниченной двойными кавычками, надо было бы маскировать $ и @, даже если бы они были внутри своей строки, заключенной в апострофы.

7.2 Интерполяция переменных и кода в регулярное выражение

Т.к. регулярное выражение предварительно обрабатывается как строка, заключенная в кавычки (если оно не было ограничено апострофами), то интерполяция значений переменных и результата выполнения кода Perl в регулярное выражение происходит по аналогичным правилам. Но здесь могут встретиться нюансы, которые связаны с тем, что регулярные выражения имеют свои метасимволы.

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

 my @a=('abc','def');
 $_='def';
 print $& if /^$a[1]$/;

Напечатается def.

Как видим, конструкция [:] после имени переменной интерпретируется как индекс. Если же интерполируется скалярная переменная, после которой идет класс, то транслятор выдает ошибку:

#!/usr/bin/perl -w
use strict;

my $a=2;
$_='21';
print $& if /^$a[1]$/;

Global symbol "@a" requires explicit package name at a.pl line 6.
Execution of a.pl aborted due to compilation errors.

Здесь не поможет заключение имени переменной в фигуный скобки: ${a}, ошибка останется той же. Если отделить имя переменной от квадратной скобки пробелом, то программа работает верно:

#!/usr/bin/perl -w
use strict;

my $a=2;
$_='21';
print $& if /^$a [1]$/x;

На выходе имеем 21.

А если мы не планируем использовать модификатор x? Тогда может помочь такое искусственное решение:

 my $a=2;
 $_='21';
 print $& if /^$a(?#)[1]$/;

Опять получаем 21.

Мы отделили имя переменной от скобки конструкцией комментария. Т.к. интерполяция переменных происходит раньше удаления комментариев, этот метод работает. Также можно было бы разделить имя со скобкой каким-либо нейтральным подшаблоном, например:

 my $a=2;
 $_='21';
 print $& if /^$a(?=)[1]$/;

И снова выводится 21.

Опережающая проверка (?=) всегда возвращает истину, т.к. пустой фрагмент в тексте можно встретить всюду.

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

 my $a=2;
 $_='21';
 print $& if /^${a}a{0}[1]$/;

Опять 21.

Мы разделили имя и класс подшаблоном a{0}, который равносилен пустому фрагменту.

Теперь рассмотрим интерполяцию переменной с двумя индексами:

 my @a=([1,2],[3,4]);
 $_='2';
 print $& if /^$a[0][1]$/;

На печати получаем 2. Отсюда вывод: конструкции [:] интепретируются как индексы переменной, если они идут непосредственно после ее имени. Отделение классов от индексов происходит аналогично уже рассмотренным случаям с одним индексом.

Массивы в регулярные выражения тоже интерполируются как в строку, ограниченную двойными кавычками:

 $"=',';
 my @a=(1,2,3);
 $_='1,2,3';
 print $& if /^@a$/;

На печать выйдет
1,2,3,4

После интерполяции массива @a с учетом разделителя $"=',' регулярное выражение стало эквивалентно такому: /^1,2,3,4$/.

Если интерпретатор видит, что регулярное выражение требует интерполяции чего-нибудь, то это выражение будет транслироваться всякий раз перед его выполнением. Если вы уверены, что интерполируемые переменные не меняют своего значения, то можете поставить ко всему регулярному выражению модификатор o, который отменит повторную компиляцию такого шаблона. Вместо этого будет использоваться его внутреннее скомпилированное представление, которое получилось перед первым его выполнением. А во внутреннем представлении переменные заменены их значениями.

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

Сравните:

 my $a='abc';
 $_='abc';
 for my $count (0..1)
  { print "$&\n" if /^$a$/;
    $a='123';
    $_='123';
  }

Напечатается
abc
123

и

 my $a='abc';
 $_='abc';
 for my $count (0..1)
  { print "$&\n" if /^$a$/o;
    $a='123';
    $_='123';
  }

Напечатается только
abc

Во втором случае в обоих повторах цикла совпадение отыскивается по тому же самому регулярному выражению /^abc$/, хотя переменная $a поменяла значение на '123'. В результате при втором повторе цикла совпадение с шаблоном не обнаруживается.

Модификатор o, как и модификаторы g и с, могут быть применены только ко всему регулярному выражению и не могут встречаться внутри него.

Как будут интерполироваться переменные, которые устанавливаются внутри регулярного выражения, в частности, нумерованные переменные $1, $2, :, $99? Так же, как и ваши переменные: своим значением, которое они имели до работы этого регулярного выражения. Поэтому не надейтесь, что в ходе выполнения регулярного выражения вы будете получать самые "свежие" их значения. Другое дело - встроенный код Perl и динамические регулярные выражения (которые мы рассмотрим в дальнейшем): здесь нет интерполяции переменных, а используются их текущие значения.

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

 my @a=([1,2],[3,4]);
 $_='23';
 print $& if /^[$a[0][1]$a[1][0]]{2}$/;

На печать выйдет 23.

После интерполяции регулярное выражение будет иметь вид /^[23]{2}$/.

Разумеется, в классы интерполируются также и переменные, содержащие метасимволы классов:

 $_='123abc';
 my $a='^\d';
 print $& if /[$a]+/;

Будет напечатано abc.

После интерполяции регулярное выражение будет иметь вид /[^\d]+/.

При попытке интерполировать неопределенное значение появится предупреждение об использовании неинициализированного значения:

#!/usr/bin/perl -w
use strict;

$_='z';
my $a=undef;
/^$a$/;

Будет напечатано:
Use of uninitialized value in concatenation (.) or string at a.pl line 6.

Если мы хотим интерполировать внутрь строки значение переменной $n, то просто напишем "\$n=$n text". А если надо интерполировать не число $n, а число $n+1 или другое выражение, тогда как? Можно, конечно, написать так: "\$n+1=".($n+1)." text", но это не будет интерполяция внутрь строки. Здесь можно прибегнуть к рассмотренному приему интерполяции кода внутрь строки: "\$n+1=${\($n+1)} text". Аналогично можно интерполировать выражения внутрь регулярных выражений. К примеру, если мы вместо квантификатора хотим использовать значение переменной $n, то напишем

 $_='a';
 my $n=1;
 /^(a{$n})$/;
 print $1;

Напечатается a.

Если хотим, к примеру, использовать квантификатор со значением $n+1, то можно написать так:

 $_='aa';
 my $n=1;
 /^(a{${\($n+1)}})$/;
 print $1;

Напечатается aa.

7.3. Экранирование метасимволов регулярных выражений

Если вы вставляете в регулярное выражение литерал из ввода пользователя или какую-либо переменную и хотите, чтобы переменная интерпретировалась как текст для поиска, а не как часть регулярного выражения, то для этого имеется эскейп-последовательность \Q, которая включает режим экранирования метасимволов регулярных выражений, таких, как [, *, +, {, ^, $, :: /\Q$userinput\E/. Экранирование осуществляется до эскейп-последовательности \E, а при ее отсутствии - до конца регулярного выражения.

Вставка ввода пользователя в регулярное выражение сопряжена с опасностью выполнения постороннего кода Perl, который может содержать вызов системных программ. Это является большой дырой в защите сервера. Поэтому переменную, содержащую ввод пользователя, надо заключать в специальную конструкцию \Q:\E. Вряд ли вы захотите искать или заменять что-то по регулярному выражению, которое вводит пользователь.

Результат аналогичен применению функции Perl quotemeta(). Но символы \ в неизвестных комбинациях (например, \F) не экранируются. Переменные типа $scalar и @array внутри метапоследовательности \Q:\E интерполируются, также интерполируются специальные переменные, которые должны интерполироваться внутри регулярных выражений.

Например:

 $_='[a]bc$ ';
 print "'$&'" if /^\Q[a]bc$ \E$/;

Напечатается '[a]bc$ '. После символа $ внутри строки $_ и регулярного выражения стоит пробел. Если бы между символами $ и \ не было пробела, то внутри регулярного выражения возникла бы специальная переменная $\, которая стандартно содержит неопределенное значение. Она бы заменилась на пустой фрагмент, символ \ был бы отнят от буквы E и вместо завершителя метапоследовательности \Q:\E и метасимвола $ - конца текста - мы бы получили букву E, за которой стоит простой символ $:

#!/usr/bin/perl -w
use strict;

$_='[a]bcE$';
print $& if /^\Q[a]bc$\E$/;

На печати оказывается следующее:
Use of uninitialized value in concatenation (.) or string at a.pl line 6.
[a]bcE$

Мы получили предупреждение об использовании неинициализированной переменной ( а именно, $\) внутри регулярного выражения. Но совпадение все же было найдено.

Чтобы яснее проиллюстрировать этот пример, присвоим переменной $\ какое-либо значение, а также вставим его в исходную строку после буквы c:

 $\='?';
 $_='[a]bc?E$';
 print $& if /^\Q[a]bc$\E$/;

Совпадение найдется без предупреждений, и на печати окажется
[a]bc?E$?

Теперь все ясно, только откуда взялся последний знак вопроса? Дело в том, что специальная переменная $\ содержит текст, который оператор print использует как завершитель выходных записей и добавляет после своего вывода, - вот он этот текст и добавил.

Получается, что символы $ и @ внутри последовательности \Q:\E будут интерполироваться, если за ними следуют символы, которые встречаются в именах переменных или специальных переменных. А если, как у нас, после $ будет пробел, то тогда символы $ и @ будут обозначать сами себя. Ничего не даст попытка экранировать их обратным слэшем: \Q\$a\E - этот обратный слэш будет сам экранирован внутри последовательности \Q:\E, и мы получим фрагмент текста $a.

Но внутри переменных интерполяция переменных не происходит, также в них не распознаются метасимволы \Q и \E литералов регулярных выражений:

 $_='[a]\\Qbc$\\';
 my $a='[a]\\Qbc$\\';
 print $& if /^\Q$a\E$/;

На печать выходит
[a]\Qbc$\

Также внутри блоков \Q:\E работают метасимволы литералов регулярных выражений \U, \u, \L и \l.

Например:

 $_='abc';
 my $a='ABC';
 print $& if /^\L$a/;

Будет напечатано abc.

Или

 $_='abc';
 print $& if /^\LABC/;

Здесь также напечатается abc.

Обратите внимание на такой нюанс:

 $_='a';
 print "Found $&" if /^\Q\LA\E$/;

Не найдено!

 $_='a';
 print "Founf $&" if /^\Q\LA\E\E$/;

Напечатает
Found a

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

 $_='a';
 print "Found $&" if /^\Q\LA\E/;

Напечатает
Found a

Мы видим, что в первом примере, где ничего не было найдено, метапоследовательность \E соответствовала метапоследовательности \L, а для \Q не было своего завершителя \E, поэтому символ $ стоял внутри экранируемого литерала регулярного выражения и он уже не означал конца текста, а означал просто символ доллара. Следующий пример это наглядно демонстрирует:

 $_='a$';
 print "Found $&" if /^\Q\LA\E$/;

Печатает
Found a$

Замечу еще, что пустая метапоследователность \Q\E вызывает ошибку синтаксиса, так же как и \U\E и \L\E. Комбинации \U\L и \L\U также почему-то вызывают ошибки синтаксиса.

Внутри классов все эти метапоследовательности \u, \l, \U:\E, \L:\E, \Q:\E также работают. Вот примеры:

 $_='A';
 print $& if /[\ua]/;

Напечатает A.

 $_='AB]';
 print $& if /[\Ua]b\E]/;

Напечатает AB].

Здесь обратите внимание на "сквозное" действие метапоследовательности \U:\E, которая продолжает действовать за пределами класса. Ведь рассматриваемые метапоследовательности применяются сразу после интерполяции переменных, поэтому механизм поиска соответствия получит регулярное выражение /[A]B]/.

Вот пример полезной идиомы: в переменной $name задано имя человека буквами произвольного регистра. Мы с помощью последовательности \u\L в любом случае делаем первую букву заглавной, а остальные - строчными:

 $_='Andrey';
 my $name='aNDreY';
 print $& if /\u\L$name\E/;

Напечатает Andrey.

Внутри классов это тоже работает:

 $_='Andrey';
 my $name='aNDreY';
 print $& if /[\u\L$name\E]{6}/;

Будет напечатано Andrey. Регулярное выражение будет иметь вид /[Andrey]{6}/.

Еще раз заметим, что внутри переменных эти метапоследовательности не распознаются.

Например:

#!/usr/bin/perl -w
use strict;

$_='A';
my $name='\\ua';
print $& if /$name/;

Возникает сообщение:
Unrecognized escape \u passed through in regex;
marked by <-- HERE in m/\u <-- HERE a/ at a.pl line 6.

В регулярное выражение мы передали строку \ua. Но в интерполируемых переменных рассматриваемые метапоследовательности не работают.

Сделаю еще одно замечание насчет использования \u, \l, \U:\E и \L:\E. В документации алгоритм их работы не разъяснен, не написано, какую ассоциативность имеют эти операторы - левую или правую. Ведь их действия могут конфликтовать друг с другом. К примеру, какой напечатается строка "\Ua\lAa"? Эскейп-последовательность \l говорит, что следующая буква A будет прописной, а \U говорит, что все после нее до конца строки будет заглавным. В итоге напечатается AAA. Зесь мы видим, что эти операторы имеют правую ассоциативность, т.е. выполняются справа налево. То же справедливо, когда рядом стоят символы \l\u и \u\l. Однако, если вместе стоят символы \U\l, \l\U, \L\u и \u\L, то префиксы \l и \u имеют приоритет перед метасимволами \L и \U. Сравните результаты:

 print "\L\uaA\n";
 print "\LaA\uaA\n";

Будет напечатано:
Aa
aaaa

В первом случае \u перебивает действие \L, а во втором случае, когда между \L и \u стоят символы, - нет.

И в конце отмечу, что операторы \U, \u, \L, \l, если установлена локаль, работают с учетом локальных установок, т.е. также с буквами национальных алфавитов.

Если регулярное выражение ограничено апострофами, то в нем метапоследовательности для литерального текста \Q:\E, операторы интервального изменения регистра букв \U:\E и префиксы изменения регистра букв \u, \l не работают. А также внутри такого регулярного выражения не интерполируются переменные.