Регулярные выражения Perl и их применение

Создание регулярных выражений

Разбить на страницы
Показывать лекцию целиком

1.1. Общее знакомство с регулярными выражениями

Если на компьютере у читателя еще не установлена система программирования Perl, то самое время это сделать. Дистрибутив Perl под Windows можно скачать с сайта http://www.activestate.com. Это все дается бесплатно. Поставка осуществляется в дистрибутиве MSI (MicroSoft Installer). Вы можете запустить его, найдя этот файл через "Мой компьютер" и дважды щелкнув на нем. Также можно использовать инсталлятор msiexec.exe, находящийся в подкаталоге system32 каталога Windows. Если запустить его без параметров, он в окне выдаст справку на русском языке.

Регулярные выражения обычно используются как операнды операторов поиска m/…/ и замены s/…/…/. Слово регулярные означает "составленные по правилам". То, что стоит вместо многоточия в операторе m и вместо первого многоточия в операторе s, - это и есть регулярное выражение. Буква m означает match (соответствие), а буква s означает substitution (замена) .

Предположим, в программе проверяется ввод пользователя, чтобы выяснить, хочет ли он завершить программу, введя слова stop, quit, exit или abort. Без регулярных выражений вам пришлось бы использовать ряд сравнений с этими образцами, предварительно преобразовав ввод к нижнему регистру. С оператором m эта проверка делается просто:

if ($input =~ m/stop|quit|exit|abort/i) { exit }

Вы можете также проверить, что пользователь кроме ключевого слова больше ничего не вводил. Для этого оператор m надо записать так:

if ($input =~ m/^(stop|quit|exit|abort)$/i) { exit }

Мы предполагаем, что ввод пользователя содержится в переменной $input. Символы " =~ " надо рассматривать как единый символ, это оператор связывания переменной $input с данным оператором m. Этот оператор связывания возвратит число 1, если оператор поиска m найдет в $input текст, соотнесенный с шаблоном, иначе вернется пустая строка, которая в Perl трактуется как ложь. Поэтому оператор поиска удобно использовать в условных операторах и заголовках операторов цикла.

Символы " / " не принадлежат к регулярному выражению, а лишь ограничивают его подобно скобкам. Транслятор по ним определяет, где начинается и заканчивается регулярное выражение, которое может быть очень большим и сложным. Если символ-ограничитель используется в любом месте регулярного выражения, то он должен быть замаскирован обратным слэшем: \/. Это может порождать частокол из обратных и прямых слэшей внутри регулярного выражения, что часто встречается у новичков. Синтаксис Perl позволяет выбирать в качестве ограничителей почти любые символы, кроме алфавитно-цифровых и пробельных: #, !, ,, :, % и т.д. Например, оператор

print ':' =~ m:[abc\:def]:;

напечатает единицу. Символы-ограничители лучше выбирать по возможности такими, которые не встречаются в регулярном выражении, чтобы не усложнять его вид. Также лучше не использовать символы " *, +, -, |, (, ), [, ], {, } ", потому что в регулярном выражении они играют особую роль.

Символы-ограничители могут быть парными: это все виды скобок (), <>, [] и {}. Имеются в виду символы ASCII, т.к. существуют угловые скобки, не принадлежащие к 7-битным символам. В этом случае перед регулярным выражением ставится открывающая скобка, а после него - закрывающая.

Если в качестве ограничителей выступают слэши " / ", то букву m можно не писать. Здесь еще следует добавить, что если ограничителями выступают вопросительные знаки, то букву m также можно не ставить, но эти ограничители включают довольно экзотический режим поиска, которые относится к "подозрительным" экспериментам и может не войти в будущие версии Perl.

Если в качестве целевого текста для оператора поиска или замены выступает переменная $_, то ее можно опустить вместе со связкой

=~: if (/stop|quit|exit|abort/i) { exit }

Буква i после завершителя регулярного выражения называется модификатором и включает режим поиска без учета регистра букв (case Insensitive). При этом шаблон /StOp/i будет соответствовать целевой строке 'stop', 'sTOp' и т.д.

Понятие буквы зависит от локальных установок среды выполнения программы. В русской Windows к буквам также будут относиться все русские буквы в кодировке Windows. Во французской Windows результаты будут другими. Существуют специальные директивы и операторы установки локали ( use locale и setlocale ), но аргумент setlocale может отличаться для разных операционных систем. Как установить локаль в Perl на сервере, надо уточнять у его администратора.

1.1.1 Альтернативные шаблоны

Символ " | ", напоминающий операцию " или ", играет в регулярных выражениях роль, схожую с этой логической связкой. Это конструкция выбора ( альтернативы ). Дойдя до нее, система поиска соответствия начинает с текущей позиции целевой строки сопоставлять ей все шаблоны из конструкции выбора ( альтернативные шаблоны ) в порядке их написания (т.е. слева направо). Используется первый совпавший шаблон, после чего оставшиеся шаблоны пропускаются и управление передается за конструкцию выбора. В качестве шаблонов могут выступать регулярные выражения любой сложности, а не только такие, как в нашем примере. Слова шаблон и регулярное выражение для нас являются синонимами.

Оператор " | " имеет очень низкий приоритет, поэтому, если перед или после конструкции выбора имеется еще что-то, то ее надо заключить в скобки.

Во втором примере символ " ^ " означает начало строки, а символ " $ " - ее конец. Точный смысл этих символов мы обсудим позже. Эти символы также называют мнимыми символами, т.к. они совпадают не с текстом, а с позицией в целевой строке. Также их еще называют условиями, якорными метасимволами или просто якорями.

Если бы во втором примере регулярное выражение было записано без скобок:

^stop|quit|exit|abort$

то оно бы означало следующее: либо stop в начале строки, либо слова quit или exit в любом месте строки, либо abort, стоящее в конце строки. А это было бы не то, что нам нужно.

1.1.2 Модификаторы и якоря

А теперь рассмотрим другие модификаторы регулярных выражений. Модификатор m (Multiline) меняет смысл якорей ^ и $: в зоне его действия метасимвол ^ соответствует не только началу текста, но также началу каждой логической строки в этом тексте, т.е совпадает в начале текста и после каждого символа новой строки \n, который не стоит в самом конце текста. Метасимвол $ в зоне действия модификатора m совпадает не только в самом конце текста, но также и перед каждым символом \n. Но тогда необходимо иметь еще пару якорей, которые совпадают лишь в начале и конце текста. И такие якоря есть, их даже три.

  • Условие \A совпадает только в самом начале текста.
  • Условие \Z совпадает только в самом конце текста и перед символом \n, который стоит в самом конце текста. (Например, на случай, если к этому тексту не применили функцию chomp ).
  • Условие \z совпадает исключительно в самом конце текста. Модификаторы на эти три якоря не действуют.
  • Еще имеется мнимый символ \b, который соответствует границе слова (word Boundary). Он соответствует позиции, с одной стороны которой находится буква, цифра или знак подчерка, а с другой стороны такого символа нет. Мнимый символ \B имеет противоположный смысл: он соответствует позиции внутри слова, т.е. с одной и другой стороны к нему примыкает буква, цифра или знак подчерка. Заметим, что эти якоря чувствительны к локальной установке (локали).

    В регулярном выражении точка " ." является метасимволом и соответствует одному любому символу за исключением символа новой строки \n. Но в зоне действия модификатора s точка соответствует одному любому символу. Такое различие бывает нужно, чтобы операция поиска не вышла за пределы логической строки.

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

    use bytes;

    которая заставит Perl рассматривать символы как однобайтовые. Это, а также замена метасимвола точка на \C, даст некоторое ускорение работы регулярных выражений.

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

    Как быть, если в данной позиции целевой строки могут стоять (ожидаются) разные символы? Например, параметры тега HTML могут заключаться в апострофы, а также двойные кавычки. Здесь конструкцию выбора применять неудобно. Для этого существуют классы символов. Класс - это последовательность символов, которая заключена в квадратные скобки. Например, класс [""] совпадает с апострофом и с двойной кавычкой.

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

    Классы имеют свои метасимволы, а некоторые метасимволы регулярных выражений внутри классов не действуют. Например, символ ] является метасимволом лишь внутри класса и поэтому должен быть в нем замаскирован: \]. А символ [ является метасимволом в регулярном выражении, но не внутри класса символов.

    Внутри класса символов можно использовать диапазоны символов, например, класс [a-f0-9] - это то же, что [abcdef0123456789], но первая запись короче. Диапазон включает все промежуточные символы, чьи коды расположены между кодами крайних символов. Если вы захотите включить знак минус в класс символов, то его надо либо замаскировать обратным слэшем, либо поставить в самом начале или конце класса.

    Символ " ^ " внутри класса уже не означает начала строки, мнимые символы внутри классов не имеют смысла и не используются. Символ " ^ ", который стоит в самом начале класса, инвертирует этот класс, и такой инвертированный класс соответствует любому из символов, кроме перечисленных в этом классе.

    Некоторые классы так часто используются, что для их обозначения придумали специальные эскейп-последовательности.

  • \d соответствует десятичной цифре: [0-9].
  • \D соответствует одному символу, не являющемуся цифрой: [^0-9].
  • \w соответствует символу, входящему в слово: [a-zA-Z0-9_]. Но вы должны помнить, что этот класс чувствителен к локальной установке и может включать символы букв национального алфавита.
  • \W соответствует любому символу, не входящему в слово: [^a-zA-Z0-9_]. Он тоже чувствителен к локали.
  • \s соответствует одному "пробельному" символу. Пробельными символами считаются:
  • пробел с десятичным кодом 32 ;
  • горизонтальная табуляция \t с кодом 9 ;
  • перевод строки \n с кодом 10 ;
  • возврат каретки \r с кодом 13 ;
  • перевод формата \f с кодом 12.
  • Таким образом, \s - это класс [ \t\r\n\f].

    \S соответствет непробельному символу: [^ \t\r\n\f].

    Еще заметим, что если класс символов находится в зоне действия модификатора i, то в этот класс неявно включаются соответствующие буквы также другого регистра, чтобы он соответствовал символу без учета регистра букв. Например, класс [a-c] в зоне действия модификатора i соответствует символу из диапазонов a-cA-C, а класс [^a] в зоне действия модификатора i уже не соответствует символу A.

    Для задания символа по его коду можно воспользоваться эскейп-последовательностью вида \xHH, где HH - две шестнадцатеричные цифры кода символа. Например, \x20 - это символ пробела, а \xFF - код буквы "я" в кодировке Windows (это число 255). Шестнадцатеричные цифры можно набирать в любом регистре, а буква x должна быть в нижнем регистре.

    Имеется возможность использовать для этого также восьмеричную систему, например, \040 - код пробела и \377 - код буквы "я". После обратной косой должно быть ровно три восьмеричных цифры. Но я не советую пользоваться восьмеричной системой, т.к. это вступает в конфликт с обозначением обратных ссылок, о которых речь еще впереди.

    1.1.4 Квантификаторы, их "жадность" и ее ограничение

    Ознакомимся с квантификаторами, также именуемыми числителями или повторителями. Если после подшаблона стоит квантификатор, то этот шаблон может повторяться столько раз, сколько указывает этот квантификатор. Например, шаблон a{0,4} соответствует нулю, одной, двум, трем и четырем буквам " a ", идущим подряд.

    Соответствие нулю каких-то символов означает соответствие пустой подстроке. Если максимальный параметр не ограничен, то его можно опустить: шаблон a{1,} соответствует последовательности из одной и более букв " а ". Шаблон a{3} соответствует ровно трем подряд буквам a, это то же, что и шаблон aaa.

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

    Некоторые числители используются так часто, что для них сделали краткие формы записи:

  • a* - это нуль или больше символов a: a{0,} ;
  • a+ - это один или больше символов a: a{1,} ;
  • a? - это нуль или один символ a: a{0,1}.
  • Квантификаторы имеют высокий приоритет, поэтому шаблон bil+ing соответствует символам bi, после которых идет одна или больше букв l и далее последовательность ing. Если вы хотите, чтобы квантификатор соответствовал последовательности символов или группе подшаблонов, то эту конструкцию надо взять в скобки:

    (bil)+ing
    соответстсвует строкам
    biling
    bilbiling
    bilbilbiling
    и т.д.

    По умолчанию, алгоритм работы квантификаторов таков, что они пытаются захватить как можно больше текста, который им соответствует. Например, в переменной $text имеем текст

    <b>Текст выделен жирным шрифтом.</b> Простой текст <b>Это опять жирный</b>

    Если применить к этому тексту регулярное выражение <b>.+</b>: $text =~ /<b>.+</b>/ то оно будет соответствовать всему этому тексту, а не фрагменту <b>Текст выделен жирным шрифтом.</b>

    Аналогично, в шаблоне

    \w*a

    примененном к строке

    abcabcaaaaa

    подшаблон \w* будет соответствовать подстроке

    abcabcaaaa

    (без последней буквы a ), а не подстроке

    abca

    или

    abcabca

    Имеется способ заставить квантификаторы захватывать как можно меньше текста, для этого после такого квантификатора надо поставить знак вопроса: " ?". Например:

    a+?

    соответствует минимуму из одной и более букв " a ". Если этот шаблон применить к строке

    bbbaaaabbb

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

    В шаблоне

    a*?

    минимальный квантификатор удовлетворится нулем символов a, а это значит, что совпадение будет найдено в любом месте, даже после истинного конца строки! Оператор

    print "a" =~ /\za*/;

    напечатает 1.

    Вот другие примеры минимальных квантификаторов:

    \w?
    (abc){3,5}?

    1.1.5 Захватывающие и незахватывающие скобки

    Поиск соответствия давал бы мало пользы, если бы нельзя было извлекать из текста интересующие нас фрагменты. Для извлечения фрагмента текста часть шаблона (или весь шаблон ), который ему соответствует, надо заключить в круглые скобки. Всего в регулярном выражении может быть 99 захватывающих пар скобок. Такие скобки также называют сохраняющими. Если вы не хотите сохранять часть текста, а только группируете подшаблоны, то для этого существуют обычные скобки, которые не сохраняют текст; таких скобок в регулярном выражении может быть 200 пар. Чтобы сделать пару скобок обычной ( несохраняющей ), надо сразу после открывающей скобки поставить вопросительный знак и двоеточие. Сохраняющие и несохраняющие скобки могут иметь какой угодно уровень вложенности. Сохраняющие скобки нумеруются в порядке появления открывающей скобки от 1 до 99, чтобы за пределами оператора поиска иметь сохраненными нужные фрагменты текста. Текст, сопоставленный подшаблону в первой паре захватывающих скобок, окажется в специальной переменной $1, сопоставленный второй паре захватывающих скобок - в переменной $2 и т.д. до $99.

    Разумеется, не обязательно иметь 99 пар скобок, - незадействованные специальные переменные будут иметь неопределенное значение. Обратите внимание, что нумерация начинается не с нуля и что переменная $0 не имеет отношения к регулярным выражениям, а хранит имя файла выполняемого сценария.

    Рассмотрим такой пример: пусть в переменной $text хранится текст для тега a

    <a href="http://www.intuit.ru/">Internet-обучение</a>

    Нам надо написать регулярное выражение, которое соответствует тегу a и извлекает из него ссылку. Можно написать так:

    $text =~ m#<a href="([^"]+)">[^<]+</a>#;
    print $1;

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

    http://www.intuit.ru/

    Сначала в регулярном выражении идет литеральный текст

    <a href="

    который один к одному соответствует своему двойнику в целевой строке $text. Затем в целевой строке стоит ссылка, которую и надо получить. Поэтому мы открываем захватывающую скобку. Подшаблон

    [^"]+

    означает "любое число символов от 1 и более, которые не содержат кавычек". Поэтому скобки сохранят нам весь текст до следующих кавычек, а это и есть ссылка. Дальше в шаблоне опять идет литерал >, а подшаблон

    [^<]+

    соответствует всему до открывающей угловой скобки, затем вновь идет литеральный текст, который соответствует такому же тексту в переменной $text.

    Записанное регулярное выражение несовершенно. Если после <a будет не один пробел, а больше, или будет стоять перевод строки, то оператор поиска не найдет соответствия и переменная $1 будет иметь неопределенное значение (или значение, которое осталось от предыдущего оператора поиска или замены). Имейте в виду, что нумерованные переменные $1, …, $99 изменяются только при успешном поиске! Кроме того, надежнее вести поиск без учета регистра символов. С учетом этих требований, получаем такую версию нашей программы:

    $text='<a href="http://www.intuit.ru/">Internet-обучение</a>';
    $text =~ m#<a\s+href="([^"]+)">[^<]+</a>#i;
    print $1;

    После <a вместо пробела теперь стоит подшаблон \s+, он берет на себя все пробельные символы, один из которых обязательно должен встретиться по правилам HTML. Кроме того, появился модификатор i. Но правила не запрещают иметь пробельные символы перед и после знака равенства, это тоже надо учесть и вставить вокруг него конструкции \s*. Надо также что-то придумать насчет кавычек, ведь на их месте могут быть апострофы или вообще ничего. Модернизированный вариант выглядит так:

    $text='<a href="http://www.intuit.ru/">Internet-обучение</a>';
    if ($text =~ m#<a\s+href\s*=\s*["']?([^"'> ]+)["']?>[^<]+</a>#i) { print $1 }

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

    Теперь подшаблон ["']? возьмет на себя кавычку или апостоф только в случае, если этот символ присутствует. Внутри захватывающих скобок надо захватывать все символы кроме кавычки, апострофа, закрывающей угловой скобки и пробела, затем опять может идти кавычки или апостроф (а может, и нет), а далее все по-старому.

    Это уже лучше, но в теге a могут быть параметры, например, target=_blank, как быть тогда? Тогда наш оператор поиска закончится неудачей, ведь в регулярном выражении после ссылки предусмотрены апостроф или кавычка и затем сразу идет закрывающая скобка. Параметру target=_blank ничего не будет соответствовать.

    Это легко обойти, вставив перед закрывающей скобкой подшаблон

    [^>]*

    который поглотит все, что будет стоять до этой скобки.

    Это хорошо, но ведь параметры тега a ключевые, а не позиционные, и параметр href не обязан стоять первым. Тег может быть оформлен так:

    <a target="_blank" href="http://www.intuit.ru/">Internet-обучение</a>

    В этом случае наш оператор поиска не найдет соответствия. Надо пропускать символы, пока не встретится href. Это можно сделать с помощью конструкции

    .*?

    и не забыть поставить модификатор s, потому что тег может располагаться на нескольких строках (после target="_blank" может быть перевод строки), а метасимвол "точка" без этого модификатора не соответствует символу перевода строки (new line).

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

    [^>]*?

    Теперь модификатор s можно не ставить.

    Еще я советую использовать директиву

    use strict;

    чтобы транслятор проверял, все ли переменные определены, и параметр w для выдачи предупреждающих сообщений транслятора.

    Если вы запускаете скрипт на Web-сервере из браузера, то вставьте также директиву

    use CGI::Carp qw(fatalsToBrowser);

    чтобы Perl выводил ошибки в браузер, иначе вы будете долго гадать, почему скрипт не работает.

    Вот законченная программа, которая "железобетонно" выводит ссылку из тега a:

    #!/usr/bin/perl -w
    use strict;
    
    my $text='<a target="_blank" href="http://www.intuit.ru/">Internet-обучение</a>';
    if ($text =~ m#<a\s+[^>]*?href\s*=\s*["']?([^"'> ]+)["']?[^>]*>[^<]+</a>#i) { print $1 }

    Если вы будете запускать Perl -программу на сервере Unix, то запоминайте текст в файл без символов возврата каретки \r. В редакторе Far это можно сделать по клавишам <Shift>+<F2>. Если вывод скрипта будет направлен веб-серверу и от него браузеру, то перед первым выводом (оператором print ) должна идти команда

    print "Content-Type: text/html\n\n";

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

    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 больше двух раз.

    Страницы:

    1.1. Общее знакомство с регулярными выражениями

    Если на компьютере у читателя еще не установлена система программирования Perl, то самое время это сделать. Дистрибутив Perl под Windows можно скачать с сайта http://www.activestate.com. Это все дается бесплатно. Поставка осуществляется в дистрибутиве MSI (MicroSoft Installer). Вы можете запустить его, найдя этот файл через "Мой компьютер" и дважды щелкнув на нем. Также можно использовать инсталлятор msiexec.exe, находящийся в подкаталоге system32 каталога Windows. Если запустить его без параметров, он в окне выдаст справку на русском языке.

    Регулярные выражения обычно используются как операнды операторов поиска m/…/ и замены s/…/…/. Слово регулярные означает "составленные по правилам". То, что стоит вместо многоточия в операторе m и вместо первого многоточия в операторе s, - это и есть регулярное выражение. Буква m означает match (соответствие), а буква s означает substitution (замена) .

    Предположим, в программе проверяется ввод пользователя, чтобы выяснить, хочет ли он завершить программу, введя слова stop, quit, exit или abort. Без регулярных выражений вам пришлось бы использовать ряд сравнений с этими образцами, предварительно преобразовав ввод к нижнему регистру. С оператором m эта проверка делается просто:

    if ($input =~ m/stop|quit|exit|abort/i) { exit }

    Вы можете также проверить, что пользователь кроме ключевого слова больше ничего не вводил. Для этого оператор m надо записать так:

    if ($input =~ m/^(stop|quit|exit|abort)$/i) { exit }

    Мы предполагаем, что ввод пользователя содержится в переменной $input. Символы " =~ " надо рассматривать как единый символ, это оператор связывания переменной $input с данным оператором m. Этот оператор связывания возвратит число 1, если оператор поиска m найдет в $input текст, соотнесенный с шаблоном, иначе вернется пустая строка, которая в Perl трактуется как ложь. Поэтому оператор поиска удобно использовать в условных операторах и заголовках операторов цикла.

    Символы " / " не принадлежат к регулярному выражению, а лишь ограничивают его подобно скобкам. Транслятор по ним определяет, где начинается и заканчивается регулярное выражение, которое может быть очень большим и сложным. Если символ-ограничитель используется в любом месте регулярного выражения, то он должен быть замаскирован обратным слэшем: \/. Это может порождать частокол из обратных и прямых слэшей внутри регулярного выражения, что часто встречается у новичков. Синтаксис Perl позволяет выбирать в качестве ограничителей почти любые символы, кроме алфавитно-цифровых и пробельных: #, !, ,, :, % и т.д. Например, оператор

    print ':' =~ m:[abc\:def]:;

    напечатает единицу. Символы-ограничители лучше выбирать по возможности такими, которые не встречаются в регулярном выражении, чтобы не усложнять его вид. Также лучше не использовать символы " *, +, -, |, (, ), [, ], {, } ", потому что в регулярном выражении они играют особую роль.

    Символы-ограничители могут быть парными: это все виды скобок (), <>, [] и {}. Имеются в виду символы ASCII, т.к. существуют угловые скобки, не принадлежащие к 7-битным символам. В этом случае перед регулярным выражением ставится открывающая скобка, а после него - закрывающая.

    Если в качестве ограничителей выступают слэши " / ", то букву m можно не писать. Здесь еще следует добавить, что если ограничителями выступают вопросительные знаки, то букву m также можно не ставить, но эти ограничители включают довольно экзотический режим поиска, которые относится к "подозрительным" экспериментам и может не войти в будущие версии Perl.

    Если в качестве целевого текста для оператора поиска или замены выступает переменная $_, то ее можно опустить вместе со связкой

    =~: if (/stop|quit|exit|abort/i) { exit }

    Буква i после завершителя регулярного выражения называется модификатором и включает режим поиска без учета регистра букв (case Insensitive). При этом шаблон /StOp/i будет соответствовать целевой строке 'stop', 'sTOp' и т.д.

    Понятие буквы зависит от локальных установок среды выполнения программы. В русской Windows к буквам также будут относиться все русские буквы в кодировке Windows. Во французской Windows результаты будут другими. Существуют специальные директивы и операторы установки локали ( use locale и setlocale ), но аргумент setlocale может отличаться для разных операционных систем. Как установить локаль в Perl на сервере, надо уточнять у его администратора.

    1.1.1 Альтернативные шаблоны

    Символ " | ", напоминающий операцию " или ", играет в регулярных выражениях роль, схожую с этой логической связкой. Это конструкция выбора ( альтернативы ). Дойдя до нее, система поиска соответствия начинает с текущей позиции целевой строки сопоставлять ей все шаблоны из конструкции выбора ( альтернативные шаблоны ) в порядке их написания (т.е. слева направо). Используется первый совпавший шаблон, после чего оставшиеся шаблоны пропускаются и управление передается за конструкцию выбора. В качестве шаблонов могут выступать регулярные выражения любой сложности, а не только такие, как в нашем примере. Слова шаблон и регулярное выражение для нас являются синонимами.

    Оператор " | " имеет очень низкий приоритет, поэтому, если перед или после конструкции выбора имеется еще что-то, то ее надо заключить в скобки.

    Во втором примере символ " ^ " означает начало строки, а символ " $ " - ее конец. Точный смысл этих символов мы обсудим позже. Эти символы также называют мнимыми символами, т.к. они совпадают не с текстом, а с позицией в целевой строке. Также их еще называют условиями, якорными метасимволами или просто якорями.

    Если бы во втором примере регулярное выражение было записано без скобок:

    ^stop|quit|exit|abort$

    то оно бы означало следующее: либо stop в начале строки, либо слова quit или exit в любом месте строки, либо abort, стоящее в конце строки. А это было бы не то, что нам нужно.

    1.1.2 Модификаторы и якоря

    А теперь рассмотрим другие модификаторы регулярных выражений. Модификатор m (Multiline) меняет смысл якорей ^ и $: в зоне его действия метасимвол ^ соответствует не только началу текста, но также началу каждой логической строки в этом тексте, т.е совпадает в начале текста и после каждого символа новой строки \n, который не стоит в самом конце текста. Метасимвол $ в зоне действия модификатора m совпадает не только в самом конце текста, но также и перед каждым символом \n. Но тогда необходимо иметь еще пару якорей, которые совпадают лишь в начале и конце текста. И такие якоря есть, их даже три.

  • Условие \A совпадает только в самом начале текста.
  • Условие \Z совпадает только в самом конце текста и перед символом \n, который стоит в самом конце текста. (Например, на случай, если к этому тексту не применили функцию chomp ).
  • Условие \z совпадает исключительно в самом конце текста. Модификаторы на эти три якоря не действуют.
  • Еще имеется мнимый символ \b, который соответствует границе слова (word Boundary). Он соответствует позиции, с одной стороны которой находится буква, цифра или знак подчерка, а с другой стороны такого символа нет. Мнимый символ \B имеет противоположный смысл: он соответствует позиции внутри слова, т.е. с одной и другой стороны к нему примыкает буква, цифра или знак подчерка. Заметим, что эти якоря чувствительны к локальной установке (локали).

    В регулярном выражении точка " ." является метасимволом и соответствует одному любому символу за исключением символа новой строки \n. Но в зоне действия модификатора s точка соответствует одному любому символу. Такое различие бывает нужно, чтобы операция поиска не вышла за пределы логической строки.

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

    use bytes;

    которая заставит Perl рассматривать символы как однобайтовые. Это, а также замена метасимвола точка на \C, даст некоторое ускорение работы регулярных выражений.

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

    Как быть, если в данной позиции целевой строки могут стоять (ожидаются) разные символы? Например, параметры тега HTML могут заключаться в апострофы, а также двойные кавычки. Здесь конструкцию выбора применять неудобно. Для этого существуют классы символов. Класс - это последовательность символов, которая заключена в квадратные скобки. Например, класс [""] совпадает с апострофом и с двойной кавычкой.

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

    Классы имеют свои метасимволы, а некоторые метасимволы регулярных выражений внутри классов не действуют. Например, символ ] является метасимволом лишь внутри класса и поэтому должен быть в нем замаскирован: \]. А символ [ является метасимволом в регулярном выражении, но не внутри класса символов.

    Внутри класса символов можно использовать диапазоны символов, например, класс [a-f0-9] - это то же, что [abcdef0123456789], но первая запись короче. Диапазон включает все промежуточные символы, чьи коды расположены между кодами крайних символов. Если вы захотите включить знак минус в класс символов, то его надо либо замаскировать обратным слэшем, либо поставить в самом начале или конце класса.

    Символ " ^ " внутри класса уже не означает начала строки, мнимые символы внутри классов не имеют смысла и не используются. Символ " ^ ", который стоит в самом начале класса, инвертирует этот класс, и такой инвертированный класс соответствует любому из символов, кроме перечисленных в этом классе.

    Некоторые классы так часто используются, что для их обозначения придумали специальные эскейп-последовательности.

  • \d соответствует десятичной цифре: [0-9].
  • \D соответствует одному символу, не являющемуся цифрой: [^0-9].
  • \w соответствует символу, входящему в слово: [a-zA-Z0-9_]. Но вы должны помнить, что этот класс чувствителен к локальной установке и может включать символы букв национального алфавита.
  • \W соответствует любому символу, не входящему в слово: [^a-zA-Z0-9_]. Он тоже чувствителен к локали.
  • \s соответствует одному "пробельному" символу. Пробельными символами считаются:
  • пробел с десятичным кодом 32 ;
  • горизонтальная табуляция \t с кодом 9 ;
  • перевод строки \n с кодом 10 ;
  • возврат каретки \r с кодом 13 ;
  • перевод формата \f с кодом 12.
  • Таким образом, \s - это класс [ \t\r\n\f].

    \S соответствет непробельному символу: [^ \t\r\n\f].

    Еще заметим, что если класс символов находится в зоне действия модификатора i, то в этот класс неявно включаются соответствующие буквы также другого регистра, чтобы он соответствовал символу без учета регистра букв. Например, класс [a-c] в зоне действия модификатора i соответствует символу из диапазонов a-cA-C, а класс [^a] в зоне действия модификатора i уже не соответствует символу A.

    Для задания символа по его коду можно воспользоваться эскейп-последовательностью вида \xHH, где HH - две шестнадцатеричные цифры кода символа. Например, \x20 - это символ пробела, а \xFF - код буквы "я" в кодировке Windows (это число 255). Шестнадцатеричные цифры можно набирать в любом регистре, а буква x должна быть в нижнем регистре.

    Имеется возможность использовать для этого также восьмеричную систему, например, \040 - код пробела и \377 - код буквы "я". После обратной косой должно быть ровно три восьмеричных цифры. Но я не советую пользоваться восьмеричной системой, т.к. это вступает в конфликт с обозначением обратных ссылок, о которых речь еще впереди.

    1.1.4 Квантификаторы, их "жадность" и ее ограничение

    Ознакомимся с квантификаторами, также именуемыми числителями или повторителями. Если после подшаблона стоит квантификатор, то этот шаблон может повторяться столько раз, сколько указывает этот квантификатор. Например, шаблон a{0,4} соответствует нулю, одной, двум, трем и четырем буквам " a ", идущим подряд.

    Соответствие нулю каких-то символов означает соответствие пустой подстроке. Если максимальный параметр не ограничен, то его можно опустить: шаблон a{1,} соответствует последовательности из одной и более букв " а ". Шаблон a{3} соответствует ровно трем подряд буквам a, это то же, что и шаблон aaa.

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

    Некоторые числители используются так часто, что для них сделали краткие формы записи:

  • a* - это нуль или больше символов a: a{0,} ;
  • a+ - это один или больше символов a: a{1,} ;
  • a? - это нуль или один символ a: a{0,1}.
  • Квантификаторы имеют высокий приоритет, поэтому шаблон bil+ing соответствует символам bi, после которых идет одна или больше букв l и далее последовательность ing. Если вы хотите, чтобы квантификатор соответствовал последовательности символов или группе подшаблонов, то эту конструкцию надо взять в скобки:

    (bil)+ing
    соответстсвует строкам
    biling
    bilbiling
    bilbilbiling
    и т.д.

    По умолчанию, алгоритм работы квантификаторов таков, что они пытаются захватить как можно больше текста, который им соответствует. Например, в переменной $text имеем текст

    <b>Текст выделен жирным шрифтом.</b> Простой текст <b>Это опять жирный</b>

    Если применить к этому тексту регулярное выражение <b>.+</b>: $text =~ /<b>.+</b>/ то оно будет соответствовать всему этому тексту, а не фрагменту <b>Текст выделен жирным шрифтом.</b>

    Аналогично, в шаблоне

    \w*a

    примененном к строке

    abcabcaaaaa

    подшаблон \w* будет соответствовать подстроке

    abcabcaaaa

    (без последней буквы a ), а не подстроке

    abca

    или

    abcabca

    Имеется способ заставить квантификаторы захватывать как можно меньше текста, для этого после такого квантификатора надо поставить знак вопроса: " ?". Например:

    a+?

    соответствует минимуму из одной и более букв " a ". Если этот шаблон применить к строке

    bbbaaaabbb

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

    В шаблоне

    a*?

    минимальный квантификатор удовлетворится нулем символов a, а это значит, что совпадение будет найдено в любом месте, даже после истинного конца строки! Оператор

    print "a" =~ /\za*/;

    напечатает 1.

    Вот другие примеры минимальных квантификаторов:

    \w?
    (abc){3,5}?

    1.1.5 Захватывающие и незахватывающие скобки

    Поиск соответствия давал бы мало пользы, если бы нельзя было извлекать из текста интересующие нас фрагменты. Для извлечения фрагмента текста часть шаблона (или весь шаблон ), который ему соответствует, надо заключить в круглые скобки. Всего в регулярном выражении может быть 99 захватывающих пар скобок. Такие скобки также называют сохраняющими. Если вы не хотите сохранять часть текста, а только группируете подшаблоны, то для этого существуют обычные скобки, которые не сохраняют текст; таких скобок в регулярном выражении может быть 200 пар. Чтобы сделать пару скобок обычной ( несохраняющей ), надо сразу после открывающей скобки поставить вопросительный знак и двоеточие. Сохраняющие и несохраняющие скобки могут иметь какой угодно уровень вложенности. Сохраняющие скобки нумеруются в порядке появления открывающей скобки от 1 до 99, чтобы за пределами оператора поиска иметь сохраненными нужные фрагменты текста. Текст, сопоставленный подшаблону в первой паре захватывающих скобок, окажется в специальной переменной $1, сопоставленный второй паре захватывающих скобок - в переменной $2 и т.д. до $99.

    Разумеется, не обязательно иметь 99 пар скобок, - незадействованные специальные переменные будут иметь неопределенное значение. Обратите внимание, что нумерация начинается не с нуля и что переменная $0 не имеет отношения к регулярным выражениям, а хранит имя файла выполняемого сценария.

    Рассмотрим такой пример: пусть в переменной $text хранится текст для тега a

    <a href="http://www.intuit.ru/">Internet-обучение</a>

    Нам надо написать регулярное выражение, которое соответствует тегу a и извлекает из него ссылку. Можно написать так:

    $text =~ m#<a href="([^"]+)">[^<]+</a>#;
    print $1;

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

    http://www.intuit.ru/

    Сначала в регулярном выражении идет литеральный текст

    <a href="

    который один к одному соответствует своему двойнику в целевой строке $text. Затем в целевой строке стоит ссылка, которую и надо получить. Поэтому мы открываем захватывающую скобку. Подшаблон

    [^"]+

    означает "любое число символов от 1 и более, которые не содержат кавычек". Поэтому скобки сохранят нам весь текст до следующих кавычек, а это и есть ссылка. Дальше в шаблоне опять идет литерал >, а подшаблон

    [^<]+

    соответствует всему до открывающей угловой скобки, затем вновь идет литеральный текст, который соответствует такому же тексту в переменной $text.

    Записанное регулярное выражение несовершенно. Если после <a будет не один пробел, а больше, или будет стоять перевод строки, то оператор поиска не найдет соответствия и переменная $1 будет иметь неопределенное значение (или значение, которое осталось от предыдущего оператора поиска или замены). Имейте в виду, что нумерованные переменные $1, …, $99 изменяются только при успешном поиске! Кроме того, надежнее вести поиск без учета регистра символов. С учетом этих требований, получаем такую версию нашей программы:

    $text='<a href="http://www.intuit.ru/">Internet-обучение</a>';
    $text =~ m#<a\s+href="([^"]+)">[^<]+</a>#i;
    print $1;

    После <a вместо пробела теперь стоит подшаблон \s+, он берет на себя все пробельные символы, один из которых обязательно должен встретиться по правилам HTML. Кроме того, появился модификатор i. Но правила не запрещают иметь пробельные символы перед и после знака равенства, это тоже надо учесть и вставить вокруг него конструкции \s*. Надо также что-то придумать насчет кавычек, ведь на их месте могут быть апострофы или вообще ничего. Модернизированный вариант выглядит так:

    $text='<a href="http://www.intuit.ru/">Internet-обучение</a>';
    if ($text =~ m#<a\s+href\s*=\s*["']?([^"'> ]+)["']?>[^<]+</a>#i) { print $1 }

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

    Теперь подшаблон ["']? возьмет на себя кавычку или апостоф только в случае, если этот символ присутствует. Внутри захватывающих скобок надо захватывать все символы кроме кавычки, апострофа, закрывающей угловой скобки и пробела, затем опять может идти кавычки или апостроф (а может, и нет), а далее все по-старому.

    Это уже лучше, но в теге a могут быть параметры, например, target=_blank, как быть тогда? Тогда наш оператор поиска закончится неудачей, ведь в регулярном выражении после ссылки предусмотрены апостроф или кавычка и затем сразу идет закрывающая скобка. Параметру target=_blank ничего не будет соответствовать.

    Это легко обойти, вставив перед закрывающей скобкой подшаблон

    [^>]*

    который поглотит все, что будет стоять до этой скобки.

    Это хорошо, но ведь параметры тега a ключевые, а не позиционные, и параметр href не обязан стоять первым. Тег может быть оформлен так:

    <a target="_blank" href="http://www.intuit.ru/">Internet-обучение</a>

    В этом случае наш оператор поиска не найдет соответствия. Надо пропускать символы, пока не встретится href. Это можно сделать с помощью конструкции

    .*?

    и не забыть поставить модификатор s, потому что тег может располагаться на нескольких строках (после target="_blank" может быть перевод строки), а метасимвол "точка" без этого модификатора не соответствует символу перевода строки (new line).

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

    [^>]*?

    Теперь модификатор s можно не ставить.

    Еще я советую использовать директиву

    use strict;

    чтобы транслятор проверял, все ли переменные определены, и параметр w для выдачи предупреждающих сообщений транслятора.

    Если вы запускаете скрипт на Web-сервере из браузера, то вставьте также директиву

    use CGI::Carp qw(fatalsToBrowser);

    чтобы Perl выводил ошибки в браузер, иначе вы будете долго гадать, почему скрипт не работает.

    Вот законченная программа, которая "железобетонно" выводит ссылку из тега a:

    #!/usr/bin/perl -w
    use strict;
    
    my $text='<a target="_blank" href="http://www.intuit.ru/">Internet-обучение</a>';
    if ($text =~ m#<a\s+[^>]*?href\s*=\s*["']?([^"'> ]+)["']?[^>]*>[^<]+</a>#i) { print $1 }

    Если вы будете запускать Perl -программу на сервере Unix, то запоминайте текст в файл без символов возврата каретки \r. В редакторе Far это можно сделать по клавишам <Shift>+<F2>. Если вывод скрипта будет направлен веб-серверу и от него браузеру, то перед первым выводом (оператором print ) должна идти команда

    print "Content-Type: text/html\n\n";

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

    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 больше двух раз.

    Вернуться к учебному плану