≪
≫

Лекция 7. Интерполяция переменных и метасимволы U, u, L, l, Q, Е

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

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

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

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

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

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

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

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

ту @а=([1,2],[3,4]);

$_=”ааа$а[1][0]ааа";

print

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

аааЗааа

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

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

ту @а=(1,2,3,4);

$_=”abc@{a}def”;

print $_;

На печати окажется

abc1 2 3 4def

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

ту @а=([1,2],[3,4]);

$_=”abc@{a}def”;

print $_;

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

abcARRAY(0x224ea4) ARRAY(0x224 f88)def

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

ту @а=([1,2],[3,4]);

$_-”abc@{$a[0]$а[1]}def”;

print $_;

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

abc1 23 4def

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

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

$»=’, ’;

my @а=([1.2].[3,4]);

$_=”abo@{$a[0]}l@{$a[1]}def”;

print $ ;

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

abc1,2,3,4def

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

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

#!/usr/bln/perl -w

use strict;

no strict 'refs’;

sub subr() { return ’abc’ }

our $abc=123;

$_=,,${&subr}”;

print

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

123

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

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

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

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

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

ту @а=([1,2],[3,4]);

$_=”abc${(join 1,’,(@{$а[0]},$а[1]}))}def”; print $_;

Результат получается тем же:

abc1,2,3,4def

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

ту 0»а=([1,2], [3,4]);

$_=”abc${(join »,»,(@{$а[0]},@{$а[1]}))}def”;

print $_;

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

my @а=([1,2],[3,4]);

$_=”abc@{[join *,(@]$а[0]},@{$а[1]})]}def”;

print $_;

Результат тот же:

abcl.2,3,4def

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

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

$_=”abc${(&subr(‘Bill'))}def”;

print

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

На печати получим:

abcHello Bill!def

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

гпу $а=3;

$_=”abc${($a == 3 ? l$a == З1 : ‘$а != 3’)}def

print

Получаем:

abc$a == 3def

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

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

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

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

ту @ар(‘abc’,'def');

$_='def’;

print $& if /"$a[1]$/;

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

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

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

my $a=2;

$_='21';

print $& if /~$a[1]$/;

Global symbol «@а» requires explicit package name at a.pl line 6.

Execution of a.pl aborted due to compilation errors.

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

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

my $a=2;

$_='21';

print $& if /”$a [1]$/x;

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

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

ту $а=2;

$_='21’;

print $& if /~$а(?#)[1]$/;

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

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

ту $а=2;

$_=’2Т;

print $& if Г$а(?=)[1]$/;

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

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

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

ту $а=2;

$_=’21’;

print if /~${а}а{0}[1]$/;

Опять 21.

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

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

ту @а=([1,2],[3,4]);

$_=’2’;

print $& if /~$а[0][1]$/;

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

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

$»=’,’;

ту @а=(1,2,3);

$_=’1,2,3’;

print $& if /~<§га$/;

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

1.2.3,4

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

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

my $а='abc’;

$_=1abc’;

for ту $count (0..I) { print “$& ” if $a=’123';

$_=’123';

}

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

abc

123

и

my $a='abc’;

$_='abc’;

for my $count (0..1)

{ print “$& ” if /"$a$/o;

$a=’123';

$_=’123';

}

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

abc

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

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

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

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

ту @а=([1,2],[3,4]);

$_=’23’;

print if Л$а[0][1]$а[1][0]]{2}$/;

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

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

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

$_=’123abc';

ту $а=’ ДсІ';

print $& if /[$а]+/:

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

После интерполяции регулярное выражение будет иметь вид/f

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

#!/usr/bin/perl -w

use strict;

$_='z’;

my $a-undef;

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

Use of uninitialized value in concatenation (.) or string at a.pl line 6.

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

$_='а’;

ту $п=1;

/"(а{$п})$/;

print $1:

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

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

аа ;

ту $п=1;

/”(а{${($п+1)}})$/;

print $1;

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

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

Если вы вставляете в регулярное выражение литерал из ввода пользователя или какую-либо переменную и хотите, чтобы переменная интерпретировалась как текст для поиска, а не как часть регулярного выражения.

то для этого имеется эскейп-последователыюсть Q, которая включает режим экранирования метасимволов регулярных выражений, таких как [, *, +, {, л, 5, /Q$userinputE/. Экранирование осуществляется до эскейп-последовательности Е, а при ее отсутствии — до конца регулярного выражения.

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

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

$_=’[а]Ьс$

print if /"Q[a]bc$ Е$/;

Напечатается ‘ [ a ]bc$ 1. После символа 5 внутри строки 5 и регулярного выражения стоит пробел. Если бы между символами $ и не было пробела, то внутри регулярного выражения возникла бы специальная переменная 5, которая стандартно содержит неопределенное значение. Она бы заменилась на пустой фрагмент, символ был бы отнят от буквы ?и вместо завершителя метапоследовательности Q...En метасимвола 5 — конца текста — мы бы получили букву Е, за которой стоит простой символ 5:

ft!/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$

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

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

$_='[a]bc?E$';

print $& if /”Q[a]bc$E$/;

Совпадение найдется без предупреждений, и на печати окажется

[а]Ьс?Е$?

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

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

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

$_='[a]Qbc$';

rny $а=* [a]Qbc$*;

print $& if /"Q$aE$/;

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

[a]Qbc$

Также внутри блоков (Л.. Е работают метасимволы литералов регулярных выражений U, и, L и /:

• L означает, что все буквы до эскейп-последовательности Е или до конца литерала регулярного выражения будут прописными.

  • • / означает, что следующий символ, если он буква, будет прописным.
  • • U означает, что все буквы до эскейп-последовательности ? или до конца литерала регулярного выражения будут заглавными.
  • • w означает, что следующий символ, если он буква, будет заглавным. Например:

$=’abc';

ту $а-’АВС ;

print $& if /~І_$а/;

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

Или

$_=’abc';

print if /ALABC/;

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

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

$_-’а’;

print “Found $&" if /"QLAE$/;

He найдено!

$_=’a’;

print “Fount $&" if /"QLAEE$/;

Напечатает

Found a

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

$_=’ а ’;

print “Found $&" if /~QLAE/;

Напечатает

Found a

Мы видим, что в первом примере, где ничего не было найдено, метапоследовательность ? соответствовала мегапоследовательности L, а дія О не было своего завершителя Е. поэтом}' символ 5 стоя,! внутри экранируемого литерала регулярного выражения и он уже не означат конца текста, а означал просто символ доллара Следующий пример это наглядно демонстрирует:

$_= а$ ;

print “Found $&” if /~QLAE$/;

Печатает

Found a$

Замечу еше. что пустая метапоследователность QE вызывает ошибку' синтаксиса, так же как и ЦЕ и LE. Комбинации UL и LUтакже почему-то вызывают ошибки синтаксиса.

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

$_='А’;

print $& if /[ua]/;

Напечатает А.

$_='АВ]’;

print $& if /[Ua]bE]/;

Напечатает АВ].

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

Вот пример полезной идиомы: в переменной $пате задано имя человека буквами произвольного регистра. Мыс помощью последовательности uL в любом случае делаем первую букву7 заглавной, а остальные — строчными:

$_='Andrey’;

my $name=’aNDreY';

print if AuL$nameE/;

Напечатает And геу.

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

$_=’Andrey';

my $name=’aNDreY';

print $& if /[uL$nameE]{6}/;

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

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

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

$_=’ А’;

my $name=’ua';

print if /Snaine/;

Возникает сообщение:

Unrecognized escape u passed through in regex; marked by <--HERE in m/u <-- HERE a/ at a.pl line 6.

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

Сделаю еще одно замечание насчет использования w, /, С...?и L... Е. В документации алгоритм их работы не разъяснен, не написано, какую ассоциативность имеют эти операторы — левую или правую. Ведь их действия могут конфликтовать друг с другом. К примеру, какой напечатается строка “UalAa”? Эскейп-последовательность 1 говорит, что следующая буква А будет прописной, а U.говорит, что все после нее до конца строки будет заглавным. В итоге напечатается ААА. Здесь мы видим, что эти операторы имеют правую ассоциативность, т.е. выполняются справа налево. То же справедливо, когда рядом стоят символы 1и и и1. Однако, если вместе стоят символы Ul, IU, Lu и w?. то префиксы / и і/ имеют приоритет перед метасимволами L и U. Сравните результаты:

print “LuaA ";

print “LaAuaA ”;

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

Аа

аааа

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

И в конце отмечу, что операторы {7, u, . /, если установлена ло-каль, работают с учетом локальных установок, т.е. также с буквами национальных алфавитов.

Если регулярное выражение ограничено апострофами, то в нем метапоследовательности для литерального текста Q... Е, операторы интервального изменения регистра букв (/... ?и префиксы изменения регистра букв w, / не работают. А также внутри такого регулярного выражения не интерполируются переменные.

 
Посмотреть оригинал