Создание регулярных выражений
1.1.6 Обратные ссылки
В этом примере нас не интересовало, соответствует ли левый ограничитель у ссылки правому, но иногда подобные вещи нужно проверять. Например, кто-то по ошибке слева поставил апостроф, а справа - кавычку, или вообще забыл закрыть строку. Для подобных вещей в регулярных выражениях существуют обратные ссылки. Для каждой захватывающей пары скобок имеется метасимвол, который соответствует запомненному этой парой круглых скобок тексту. Для первой пары скобок это \1, для второй - \2 и т.д., для 99-й - \99. Обратные ссылки имеют смысл и значение только внутри регулярного выражения! За пределами оператора поиска и в части замены оператора s используйте нумерованные переменные.
Здесь надо отметить такую интересную деталь: если обратная ссылка стоит в зоне действия модификатора i, то она соответствует тексту, сохраненному соответствующей парой скобок, без учета регистра. О том, что модификаторы могут быть не только глобальными, мы поговорим позже.
Чтобы проверить, стоял ли ограничитель перед ссылкой, а если стоял, то соответствовал ли правый ограничитель левому, левый ограничитель надо захватить в скобки. Тогда ссылка окажется в нумерованной переменной $2, и оператор print надо исправить:
my $text='<a target="_blank" href="http://www.intuit.ru/">Internet-обучение</a>'; if ($text =~ m#<a\s+[^>]*?href\s*=\s*(["']?)([^"'> ]+)\1[^>]*>[^<]+</a>#i) { print $2 }
Теперь вместо второго подшаблона ["']? мы вставляем ссылку \1 на найденный текст. Если текст не найден (не было ограничителя), то \1 будет соответствовать пустому месту.
В применении захватывающих скобок и обратных ссылок есть еще один тонкий момент: это положение вопросительного знака. Мы могли бы поставить его не внутри, а снаружи скобок:
(["'])?
В чем была бы разница? В первом случае, когда знак вопроса стоит внутри захватывающих скобок, содержимое этих скобок обязательно (т.е. должно чему-то соответствовать в результирующей строке), а во втором случае, когда знак вопроса стоит за круглой скобкой, сами скобки являются необязательными, т.е. их содержимое может отсутствовать, и тогда \1 (и $1 тоже) получат неопределенное значение. Если ограничитель есть, то это не повлияет на работу оператора поиска. Но если ссылка не будет чем-то ограничена, поиск потерпит неудачу из-за того, что в шаблоне будет стоять обратная ссылка \1, которая ничему не будет соответствовать, потому что для нее нет соотнесенного фрагмента текста! Если же знак вопроса будет стоять внутри скобок:
(["']?)
то в отсутствие ограничителей у ссылки эта скобка будет существовать, просто она захватит пустой фрагмент текста. \1 также будет соответствовать пустому фрагменту (переменная $1 получит пустое значение), и все будет работать нормально. Вот такие тонкости иногда встречаются в регулярных выражениях!
1.1.7 Оператор замены
Кратко рассмотрим оператор замены s. Он отличается от оператора поиска тем, что за регулярным выражением для поиска имеется выражение для замены найденного:
s/…/…/
Это выражение может включать нумерованные переменные. Оно вычисляется в скалярном контексте и в случае успешного поиска замещает найденный фрагмент текста.
Если регулярное выражение для поиска заключено в парные ограничители (скобки), то операнд замены заключается в собственные ограничители. Тогда между ограничителями регулярного выражения и замены могут стоять пробельные символы. Вот примеры:
s<…>'…' s(…) {…}
Если ограничителями операнда замены выступают апострофы, то выражение для замены рассматривается как строка, заключенная в апострофы, и интерполяция переменных при замене не происходит.
Оператор s может иметь модификатор g (Global). (Впрочем, оператор m также может иметь этот модификатор, но об этом речь пойдет дальше.) В этом случае поиск и замена продолжаются как бы в цикле с того места, где закончилась предыдущая замена текста, и до тех пор, пока поиск находит фрагменты, соответствующие регулярному выражению.
Как результат, оператор замены возвращает число сделанных замен или пустую строку, если замен не было. Если целевой строкой выступает переменная $_, то ее и связку =~ писать не обязательно. Пример:
$_='aabbaa'; print s/a/c/g."\n".$_;
В результате получим вывод:
4 ccbbcc
Вот пример на использование нумерованных переменных:
$_='aa bb aa'; s/(\w+)(?:\s+\w+){2}/$1 $1/; print;
Получаем вывод:
aa aa
Куда делась третья группа букв? Она была удалена, т.к. оператор s заменяет всю часть текста, что соответствует шаблону поиска. Если бы мы хотели оставить третью группу букв нетронутой, то ее не надо было бы включать в поиск:
$_='aa bb aa'; s/(\w+)\s+\w+/$1 $1/; print;
Получаем вывод:
aa aa aa
А если в поиск включается что-то, что должно остаться без изменений, то это надо взять в захватывающие скобки и в операнде замены на соответствующем месте поставить нужную нумерованную переменную.
1.1.8 Модификатор e в операторе замены
Оператор замены поддерживает модификатор e (Evaluation), которого нет в операторе поиска. В этом случае операнд для замены рассматривается как фрагмент кода Perl, который каждый раз во время замены выполняется аналогично функции eval, а полученный в скалярном контексте результат подставляется вместо найденного фрагмента текста. Это дает большую гибкость при замене текста. Более того, модификатор e может повторяться несколько раз, что влечет многократное применение функции eval к результату (столько раз, сколько раз повторен модификатор e ). Заметим, что остальные модификаторы тоже могут повторяться, но это не влечет каких-либо последствий.
Рассмотрим такой пример на замену переменных их значениями.
my $a='a'; $_='This is $a'; s/(\$\w+)/$1/; print;
В результате будет напечатано
This is $a
В захватывающие скобки попала подстрока $a, операнд $1='$a' был интерполирован по правилам строк в кавычках и в результате интерполяции получился текст '$a', который и заменил найденный фрагмент текста '$a', т.е. сам себя.
Теперь к оператору замены добавим модификатор e:
my $a='a'; $_='This is $a'; s/(\$\w+)/$1/e; print;
В результате получается тот же вывод:
This is $a
Как это объяснить? Теперь операнд для замены $1='$a' был выполнен как код Perl, в результате получилась строка '$a', которая опять заменила саму себя.
Добавим еще один модификатор e:
my $a='a'; $_='This is $a'; s/(\$\w+)/$1/ee; print;
В результате получается текст
This is a
В этом случае после выполнения кода Perl $1 получается строка '$a', которая опять выполняется как код Perl, что и дает ее значение 'a'.
Теперь принцип ясен, мы можем продолжить эту аналогию и написать такую загадочную программу:
my $b='b'; my $a='$b'; $_='This is $a'; s/(\$\w+)/$1/eee; print;
В результате выводится текст
This is b
Но вряд ли кому-либо на практике придется применять модификатор e больше двух раз.