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

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

Символ "|", напоминающий операцию "или", играет в регулярных выражениях роль, схожую с этой логической связкой. Это конструкция выбора (альтернативы). Дойдя до нее, система поиска соответствия начинает с текущей позиции целевой строки сопоставлять ей все шаблоны из конструкции выбора (альтернативные шаблоны) в порядке их написания (т.е. слева направо). Используется первый совпавший шаблон, после чего оставшиеся шаблоны пропускаются и управление передается за конструкцию выбора. В качестве шаблонов могут выступать регулярные выражения любой сложности, а не только такие, как в нашем примере. Слова шаблон и регулярное выражение для нас являются синонимами.
Оператор "|" имеет очень низкий приоритет, поэтому, если перед или после конструкции выбора имеется еще что-то, то ее надо заключить в скобки.
Во втором примере символ "^" означает начало строки, а символ "$" - ее конец. Точный смысл этих символов мы обсудим позже. Эти символы также называют мнимыми символами, т.к. они совпадают не с текстом, а с позицией в целевой строке. Также их еще называют условиями, якорными метасимволами или просто якорями.
Если бы во втором примере регулярное выражение было записано без скобок:
^stop|quit|exit|abort$
то оно бы означало следующее: либо stop в начале строки, либо слова quit или exit в любом месте строки, либо abort, стоящее в конце строки. А это было бы не то, что нам нужно.

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

Как быть, если в данной позиции целевой строки могут стоять (ожидаются) разные символы? Например, параметры тега 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 - код буквы "я". После обратной косой должно быть ровно три восьмеричных цифры. Но я не советую пользоваться восьмеричной системой, т.к. это вступает в конфликт с обозначением обратных ссылок, о которых речь еще впереди.

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

    Ознакомимся с квантификаторами, также именуемыми числителями или повторителями. Если после подшаблона стоит квантификатор, то этот шаблон может повторяться столько раз, сколько указыват этот квантификатор. Например, шаблон 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 имеем текст
    Текст выделен жирным шрифтом. Простой текст Это опять жирный
    Если применить к этому тексту регулярное выражение .+: $text =~ /.+/ то оно будет соответствовать всему этому тексту, а не фрагменту Текст выделен жирным шрифтом.
    Аналогично, в шаблоне
    \w*a
    примененном к строке
    abcabcaaaaa
    подшаблон \w* будет соответствовать подстроке
    abcabcaaaa
    (без последней буквы a), а не подстроке
    abca
    или
    abcabca
    Имеется способ заставить квантификаторы захватывать как можно меньше текста, для этого после такого квантификатора надо поставить знак вопроса: "?". Например:
    a+?
    соответствует минимуму из одной и более букв "a". Если этот шаблон применить к строке
    bbbaaaabbb
    то такой минимальный квантификатор захватит первую попавшуюся букву а, после чего оператор поиска закончит работу, вернув число 1.
    В шаблоне
    a*?
    минимальный квантификатор удовлетворится нулем символов a, а это значит, что совпадение будет найдено в любом месте, даже после истинного конца строки! Оператор
    print "a" =~ /\za*/;
    напечатает 1.
    Вот другие примеры минимальных квантификаторов:
    \w? (abc){3,5}?

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

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

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

  • Еще имеется мнимый символ \b, который соответствует границе слова (word Boundary). Он соответствует позиции, с одной стороны которой находится буква, цифра или знак подчерка, а с другой стороны такого символа нет. Мнимый символ \B имеет противоположный смысл: он соответствует позиции внутри слова, т.е. с одной и другой стороны к нему примыкает буква, цифра или знак подчерка. Заметим, что эти якоря чувствительны к локальной установке (локали).
    В регулярном выражении точка "." является метасимволом и соответствует одному любому символу за исключением символа новой строки \n. Но в зоне действия модификатора s точка соответствует одному любому символу. Такое различие бывает нужно, чтобы операция поиска не вышла за пределы логической строки.
    В последней версии регулярных выражений появился метасимвол \C, который соответствует ровно одному байту независимо от того, является ли этот байт началом многобайтового символа или нет. Т.к. мы многобайтовые символы не рассматриваем, то можно в начале программы написать директиву
    use bytes;
    которая заставит Perl рассматривать символы как однобайтовые. Это, а также замена метасимвола точка на \C, даст некоторое ускорение работы регулярных выражений.

    Обратные ссылки

    В этом примере нас не интересовало, соответствует ли левый ограничитель у ссылки правому, но иногда подобные вещи нужно проверять. Например, кто-то по ошибке слева поставил апостроф, а справа - кавычку, или вообще забыл закрыть строку. Для подобных вещей в регулярных выражениях существуют обратные ссылки. Для каждой захватывающей пары скобок имеется метасимвол, который соответствует запомненному этой парой круглых скобок тексту. Для первой пары скобок это \1, для второй - \2 и т.д., для 99-й - \99. Обратные ссылки имеют смысл и значение только внутри регулярного выражения! За пределами оператора поиска и в части замены оператора s используйте нумерованные переменные.
    Здесь надо отметить такую интересную деталь: если обратная ссылка стоит в зоне действия модификатора i, то она соответствует тексту, сохраненному соответствующей парой скобок, без учета регистра. О том, что модификаторы могут быть не только глобальными, мы поговорим позже.
    Чтобы проверить, стоял ли ограничитель перед ссылкой, а если стоял, то соответствовал ли правый ограничитель левому, левый ограничитель надо захватить в скобки. Тогда ссылка окажется в нумерованной переменной $2, и оператор print надо исправить:
    my $text='Internet-обучение'; if ($text =~ m#]*?href\s*=\s*(["']?)([^"'> ]+)\1[^>]*>[^<]+#i) { print $2 }
    Теперь вместо второго подшаблона ["']? мы вставляем ссылку \1 на найденный текст. Если текст не найден (не было ограничителя), то \1 будет соответствовать пустому месту.
    В применении захватывающих скобок и обратных ссылок есть еще один тонкий момент: это положение вопросительного знака. Мы могли бы поставить его не внутри, а снаружи скобок:
    (["'])?
    В чем была бы разница? В первом случае, когда знак вопроса стоит внутри захватывающих скобок, содержимое этих скобок обязательно (т.е. должно чему-то соответствовать в результирующей строке), а во втором случае, когда знак вопроса стоит за круглой скобкой, сами скобки являются необязательными, т.е. их содержимое может отсутствовать, и тогда \1 (и $1 тоже) получат неопределенное значение. Если ограничитель есть, то это не повлияет на работу оператора поиска. Но если ссылка не будет чем-то ограничена, поиск потерпит неудачу из-за того, что в шаблоне будет стоять обратная ссылка \1, которая ничему не будет соответствовать, потому что для нее нет соотнесенного фрагмента текста! Если же знак вопроса будет стоять внутри скобок:
    (["']?)
    то в отсутствие ограничителей у ссылки эта скобка будет существовать, просто она захватит пустой фрагмент текста. \1 также будет соответствовать пустому фрагменту (переменная $1 получит пустое значение), и все будет работать нормально. Вот такие тонкости иногда встречаются в регулярных выражениях!

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

    Если на компьютере у читателя еще не установлена система программирования Perl, то самое время это сделать. Дистрибутив Perl под Windows можно скачать с сайта www.activestate.com. Это все дается бесплатно. Поставка осуществляется в дистрибутиве MSI (MicroSoft Installer). Вы можете запустить его, найдя этот файл через "Мой компьютер" и дважды щелкнув на нем. Также можно использовать инсталлятор msiexec.exe, находящийся в подкаталоге system32 каталога Windows. Если запустить его без параметров, он в окне выдаст справку на русском языке.
    Регулярные выражения обычно используются как операнды операторов поиска m/…/ и замены s/…/…/. Слово регулярные означает "составленные по правилам". То, что стоит вместо многоточия в операторе m и вместо первого многоточия в операторе s, - это и есть регулярное выражение. Буква m означает match (соответствие), а буква s означает search (поиск).
    Предположим, в программе проверяется ввод пользователя, чтобы выяснить, хочет ли он завершить программу, введя слова 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 на сервере, надо уточнять у его администратора.

    Оператор замены

    Кратко рассмотрим оператор замены 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
    А если в поиск включается что-то, что должно остаться без изменений, то это надо взять в захватывающие скобки и в операнде замены на соответствующем месте поставить нужную нумерованную переменную.

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

    Поиск соответствия давал бы мало пользы, если бы нельзя было извлекать из текста интересующие нас фрагменты. Для извлечения фрагмента текста часть шаблона (или весь шаблон), который ему соответствует, надо заключить в круглые скобки. Всего в регулярном выражении может быть 99 захватывающих пар скобок. Такие скобки также называют сохраняющими. Если вы не хотите сохранять часть текста, а только группируете подшаблоны, то для этого существуют обычные скобки, которые не сохраняют текст; таких скобок в регулярном выражении может быть 200 пар. Чтобы сделать пару скобок обычной (несохраняющей), надо сразу после открывающей скобки поставить вопросительный знак и двоеточие. Сохраняющие и несохраняющие скобки могут иметь какой угодно уровень вложенности. Сохраняющие скобки нумеруются в порядке появления открывающей скобки от 1 до 99, чтобы за пределами оператора поиска иметь сохраненными нужные фрагменты текста. Текст, сопоставленный подшаблону в первой паре захватывающих скобок, окажется в специальной переменной $1, сопоставленный второй паре захватывающих скобок - в переменной $2 и т.д. до $99.
    Разумеется, не обязательно иметь 99 пар скобок, - незадействованные специальные переменные будут иметь неопределенное значение. Обратите внимание, что нумерация начинается не с нуля и что перемнная $0 не имеет отношения к регулярным выражениям, а хранит имя файла выполняемого сценария.
    Рассмотрим такой пример: пусть в переменной $text хранится текст для тега a
    Internet-обучение
    Нам надо написать регулярное выражение, которое соответствует тегу a и извлекает из него ссылку. Можно написать так:
    $text =~ m#[^<]+#; print $1;
    Заметьте, что в качестве символов-ограничителей были выбраны решетки, чтобы избежать частокола замаскированных символов /, которые встречаются в регулярном выражении. В результате на печать выведется ссылка
    http://www.intuit.ru/
    Сначала в регулярном выражении идет литеральный текст
    Internet-обучение

    В этом случае наш оператор поиска не найдет соответствия. Надо пропускать символы, пока не встретится 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='Internet-обучение'; if ($text =~ m#]*?href\s*=\s*["']?([^"'> ]+)["']?[^>]*>[^<]+#i) { print $1 }

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

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

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


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

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

    Internet-обучение

    В этом случае наш оператор поиска не найдет соответствия. Надо пропускать символы, пока не встретится 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='Internet-обучение'; if ($text =~ m#]*?href\s*=\s*["']?([^"'> ]+)["']?[^>]*>[^<]+#i) { print $1 }

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

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

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

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

    Атомарная группировка

    Иногда нужно, чтобы для какого-то квантификатора не создавались сохраненные состояния. Например, при извлечении ссылки вокруг знака равенства могли быть пробельные символы, и мы применяли подшаблон
    \s*=\s*
    Представим, что вокруг знака равенства стояли бы пробелы и впоследствии ссылка не была бы найдена. Тогда произошел бы возврат и квантификатор \s* отдал бы один пробел для продолжения поиска. Но мы-то знаем, что это не повлияет на нахождение ссылки, так зачем проделывать ненужную работу? Для уничтожения сохраненных состояний подшаблон \s* надо заключить в специальную скобочную конструкцию:
    (?>\s*)
    Эти скобки не являются захватывающими. При выходе за закрывающую скобку все сохраненные состояния, созданные внутри этих скобок, будут уничтожены. В данном примере это, вообще говоря, приведет к более быстрому приходу к отрицательному результату поиска. Слова "вообще говоря" означают, что на уничтожение сохраненных состояний тоже требуется время, поэтому выигрыш (или даже проигрыш) во времени определяется каждым конкретным случаем.
    Аналогично можно было бы воспользоваться атомарной группировкой в случае пропуска пробельных символов в других местах и пропуска всего до закрывающей угловой скобки:
    \s+) (?>[^>]*) и т.д.
    При возврате к атомарной группировке не будет входа внутрь ее скобок, а произойдет переход сразу за открывающую атомарную группировку скобку. Заметим, что внутри этих скобок возвраты в случае несовпадения подшаблонов все равно будут присходить, потому что квантификаторы внутри атомарных групп работают как обычно.
    Рассмотрим такие примеры:
    (?>\w*)c
    и строку
    abc
    Как вы уже догадались, совпадения не будет найдено. Квантификатор * захватит все три символа, а после выхода за скобки сохраненные состояния будут уничтожены, поэтому этот квантификатор ни за что не отдаст захваченное. Будут перепробованы итерации со второго, третьего символа и с конца строки, но все будет безрезультатно. Без атомарной группировки совпадение было бы найдено при первой итерации с начала строки.
    А что будет в случае минимального квантификатора? Возьмем шаблон
    (?>\w*?)c

    и строку

    abc

    Совпадение будет найдено, но только при третьей итерации. \w* захватит 0 символов, а литерал c совпадет с символом c. Если в "жадном" режиме квантификатор внутри атомарной группировки захватывает все и не отдает, то в минимальном режиме он берет минимум возможного и ничего больше.

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

    (?>\w)*c (?>\w)*?c

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

    Вот более практический и сложный пример: пусть нам надо округлять числа типа

    23.34000012 23.345000023 34.4000025 456.00

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

    23.34 23.345 34.40 456.00

    Округление мы будем делать оператором s/…/…/. Поставим еще условие, что он не должен ничего менять, если число уже имеет правильный вид. Тогда получалась бы замена цифр самих на себя.

    Вначале нас интересует десятичная точка: \., затем - две обязательных цифры: \d\d. За ними может идти цифра от 1 до 9, а может и не идти: [1-9]?. Все это мы возьмем в захватывающие скобки, чтобы этим заменить все от точки до конца числа. А после еще могут идти цифры: \d*, они будут удалены. Оператор получается такой:

    s/(\.\d\d[1-9]?)\d*/$1/;

    Но этот оператор делает замену, к примеру, в числе 23.345, меняя найденное на себя, т.е. выполняя ненужную работу. Вот пример кода:

    $_='23.345'; print s/(\.\d\d[1-9]?)\d*/$1/."\n"; print $_;

    Он выведет

    1 23.345

    Напомню, что оператор s возвращает число успешно сделанных замен. Вот, как это происходит: подшаблон

    (\.\d\d[1-9]?)

    совпадает с

    .345

    Далее цифр нет, поэтому \d* совпадает с пустой подстрокой, и т.к. совпадение найдено, то замена срабатывает. А она должна срабатывать только, если после .345 есть цифра. Давайте для этого попробуем заменить \d* на \d+ и посмотрим, что получится. А получается результат

    1 23.34

    Третья цифра обрезается. Это происходит потому, что теперь \d+ совпасть не с чем. Но ведь у регулярного выражения в запасе есть сохраненное состояние в подшаблоне [1-9]?. Происходит возврат внутрь скобок к этому подшаблону, и для него пробуется состояние, когда квантификатор равен нулю. В этом случае скобки совпадают с .34, а \d+ совпадает с третьей цифрой 5, поэтому пятерка из результата удаляется. Нам нужно, чтобы при совпадении подшаблона [1-9]? с третьей цифрой это совпадение не отменялось. Но для этого надо удалить данное сохраненное состояние, т.е. заключить подшаблон [1-9]? в атомарные скобки:

    s/(\.\d\d(?>[1-9]?))\d+/$1/;

    Теперь пример работает правильно.


    Третья цифра обрезается. Это происходит потому, что теперь \d+ совпасть не с чем. Но ведь у регулярного выражения в запасе есть сохраненное состояние в подшаблоне [1-9]?. Происходит возврат внутрь скобок к этому подшаблону, и для него пробуется состояние, когда квантификатор равен нулю. В этом случае скобки совпадают с .34, а \d+ совпадает с третьей цифрой 5, поэтому пятерка из результата удаляется. Нам нужно, чтобы при совпадении подшаблона [1-9]? с третьей цифрой это совпадение не отменялось. Но для этого надо удалить данное сохраненное состояние, т.е. заключить подшаблон [1-9]? в атомарные скобки:

    s/(\.\d\d(?>[1-9]?))\d+/$1/;

    Теперь пример работает правильно.

    © 2003-2007 INTUIT.ru. Все права защищены.

    Механизм работы регулярных выражений Поиск с возвратами

    Рассмотрим еще один пример. Пусть мы имеем шаблон
    abcd|abc|ab
    и строку
    ab
    Когда механизм поиска обрабатывает конструкцию выбора, он пытается найти совпадение с одной и той же текущей позиции текста, пробуя для нее последовательно эти подшаблоны в порядке их записи (слева направо). Если какой-то подшаблон совпал, то механизм поиска пропускает оставшиеся подшаблоны из конструкции выбора и далее переходит к подшаблону, стоящему за этой конструкцией.
    В нашем примере сначала будет попробован подшаблон abcd, эта проба закончится неудачей. Также неудачей закончится проба следующего подшаблона abc. И только последний подшаблон ab совпадет. Если бы после этой конструкции выбора шаблон продолжался и следующий за ней подшаблон не совпал, то в общем случае произошел бы возврат к этой конструкции выбора и в дело пошел бы следующий за ab подшаблон (если бы он существовал). В этом примере ab - последний подшаблон, поэтому в общем случае возврат продолжился бы еще левее, за конструкцию выбора и так далее до самого начала всего шаблона. И только в случае, когда при всевозможных значениях квантификаторов и номеров подшаблонов в конструкциях выбора совпадение не было бы найдено, произошло бы продвижение начальной позиции поиска в тексте на один символ.
    На этом примере надо усвоить одно важное правило использования конструкций выбора: если в такой конструкции есть подшаблоны, которые могут совпасть с текущей позицией текста, то имеет значение порядок следования этих подшаблонов. Представим, что имеется шаблон
    (ab|abc)\w*

    Текущая позиция поиска

    Процесс сопоставления заданного текста шаблону начинается с начала текста (если в шаблоне не задано другое) и с начала шаблона. Механизм поиска совпадения хранит текущие позиции в тексте и в шаблоне, до которых он обнаружил совпадение куска текста куску шаблона. Совпадение может существовать с разных позиций текста. Например, шаблон с "жадным" квантификатором
    a*
    может совпасть со строкой
    aaab
    с первой, второй и третьей буквы, но при нахождении совпадения учитывается лишь более ранняя находка, поэтому этот шаблон будет соответствовать всем трем буквам "a" с начала строки. После того, как это совпадение будет найдено, механизм поиска совпадения закончит работу. (Но в случае модификатора g работа будет продолжена, что мы увидим при рассмотрении примеров работы этого модификатора.)
    Если с текущей начальной позиции в тексте совпадение не будет обнаружено, то в дело вступает механизм смещения текущей позиции поиска: состояние шаблона и позиция поиска в шаблоне будут сброшены в начальное состояние, а текущая позиция в тексте будет продвинута на один символ. После этого начнется новая итерация поиска совпадения. (Но если шаблон привязан к началу текста якорем \A или ^, то в случае неудачи второй итерации не будет, ведь в этом случае шаблон должен совпасть только в начале текста и нигде больше.) Такие итерации будут повторяться, пока начальная позиция в тексте не выйдет за конец этого текста. В этом случае поиск закончится неудачей. Поиск заканчивается удачей, когда в процессе его работы текущая позиция в шабл оне выходит за пределы шаблона.
    Как видим, поиск происходит очень скрупулезно, ведь если шаблон может совпасть с частью текста с какой-либо позиции и с какими-то значениями квантификаторов и какими-то подшаблонами из конструций выбора, то это совпадение должно быть найдено.

    Возвраты и сохраненные состояния

    Линейный алгоритм поиска не всегда обеспечивает нахождение совпадения. Рассмотрим такой пример: пусть мы имеем шаблон
    \w+bb
    и строку
    aaabb
    При сопоставлении этой строки шаблону модификатор + захватит все символы, и на долю подшаблона bb ничего не останется. Но после этой неудачи не произойдет сразу итерации поиска со следующего символа, т.к. в совпавшей части шаблона имеются квантификаторы, у которых может быть разное число повторов. В этом случае произойдет возврат к последнему подшаблону с таким квантификатором и к позиции в тексте, с которой он совпадал. Но для таких возвратов надо для каждого пройденного подшаблона с квантификатором запоминать значения этого квантификатора и позицию в тексте, с которой совпал этот подшаблон. В нашем случае вначале для подшаблона \w+ будет запомнено 5 состояний: что он захватил с начала текста a, aa, aaa, aaab и aaabb.
    Для хранения этих состояний требуется память, поэтому, как мы видели, максимальное значение, которое могут иметь квантификаторы, ограничено. Кроме того, есть ограничение на количество всех сохраненных состояний для всего шаблона.
    Т.к. для литерала b в шаблоне не находится соответствия, "жадность" квантификатора будет уменьшена на один символ, и мы будем иметь совпадение подшаблона \w+ с подстрокой aaab, далее подшаблон b совпадет с символом b, а на следующий подшаблон b ничего не останется. На самом деле, литералы сравниваются не по одному символу, а по целому литералу (если не указан модификатор i), а пример выше я привел для наглядности рассуждений.
    Лишь при втором уменьшении "жадности" квантификатора + будет найдено совпадение для всего шаблона:
    \w+ совпадет с aaa,
    подшаблон bb совпадет с bb.
    Если бы текст на этом не заканчивался, то все равно было бы зафиксировано совпадение и оператор поиска вернул бы истину. Ведь шаблон поиска не привязан к концу строки якорями $, \z или \Z. Если бы это имело место, то поиск мог бы закончиться успехом только тогда, когда шаблон и текст исчерпываются одновременно. Точнее говоря, после исчерпания шаблона, оканчивающегося на $ или \Z, в тексте может еще остаться единственный символ новой строки \n.
    Мы рассмотрели самый элементарный пример возврата при поиске. В шаблоне может находиться много подшаблонов с квантификаторами, и внутри подшаблона с квантификатором могут быть другие такие подшаблоны. Но принцип возвратов остается прежним: в случае локальной неудачи поиска совпадения происходит откат к предыдущему минимальному подшаблону, имеющему переменный квантификатор.
    Разумеется, квантификаторы вида a{20} не могут порождать возвратов, это просто иная запись литерала.
    Что будет в случае с минимальным квантификатором? В этом случае он начинает захват с наименьшего возможного значения квантификатора (от нуля и далее). А механизм поиска кроме запоминания текущей позиции совпадения для каждого такого подшаблона и его значения, конечно, "знает" о том, какой вид имеет этот квантификатор: минимальный или максимальный.
    Если рассмотреть пример шаблона
    \w{1,3}?b
    и строку
    aaaabb
    то совпадение с первого символа строки вообще не будет найдено ни при каких значениях квантификатора. Оно будет найдено со второго символа строки и после того, как будут перепробованы значения квантификатора 1, 2 и 3. В результате получим совпадение подшаблона \w{1,3}? с подстрокой aaa, и затем подшаблон b совпадет с символом b. На этом поиск совпадения успешно закончится.

    и строку

    aaaabb

    то совпадение с первого символа строки вообще не будет найдено ни при каких значениях квантификатора. Оно будет найдено со второго символа строки и после того, как будут перепробованы значения квантификатора 1, 2 и 3. В результате получим совпадение подшаблона \w{1,3}? с подстрокой aaa, и затем подшаблон b совпадет с символом b. На этом поиск совпадения успешно закончится.

    Рассмотрим еще один пример. Пусть мы имеем шаблон

    abcd|abc|ab

    и строку

    ab

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

    В нашем примере сначала будет попробован подшаблон abcd, эта проба закончится неудачей. Также неудачей закончится проба следующего подшаблона abc. И только последний подшаблон ab совпадет. Если бы после этой конструкции выбора шаблон продолжался и следующий за ней подшаблон не совпал, то в общем случае произошел бы возврат к этой конструкции выбора и в дело пошел бы следующий за ab подшаблон (если бы он существовал). В этом примере ab - последний подшаблон, поэтому в общем случае возврат продолжился бы еще левее, за конструкцию выбора и так далее до самого начала всего шаблона. И только в случае, когда при всевозможных значениях квантификаторов и номеров подшаблонов в конструкциях выбора совпадение не было бы найдено, произошло бы продвижение начальной позиции поиска в тексте на один символ.

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

    (ab|abc)\w*

    и строка

    abc

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

    .*|abc


    первый подшаблон может совпасть с текущей позиции текста всегда при нулевом значении квантификатора *. До попытки применить вторую альтернативу очередь никогда не дойдет, как будто ее нет вовсе!

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

    Такой алгоритм поиска называется недетерминированным конечным автоматом (НКА). Этот алгоритм управляется шаблоном, и программист может указать, какое именно совпадение ему нужно. Существуют другие алгоритмы поиска (детерминированный конечный автомат ДКА или смешанные алгоритмы), но в языке Perl и других языках и библиотеках (PHP, PCRE) применяется НКА, хотя он не самый быстрый среди алгоритмов поиска. Программисту, привыкшему к алгоритму НКА, другой язык программирования, который использует другой алгоритм поиска, может преподнести сюрприз. Я уже не говорю о том, что в других языках некоторые метасимволы могут работать иначе, чем в Perl.

    Рассмотрим еще примеры:

    $_='abcd'; /(\w+)(\w+)/; print "$1|$2";

    Сначала первая пара скобок захватит всю строку, но во имя нахождения совпадения для всего шаблона пожертвует одним символом для второй скобки. В результате будет напечатано abc|d. Если во второй паре скобок вместо плюса стояла бы звездочка или вопросительный знак, то на их долю вообще не осталось бы символов - все их поглотил бы квантификатор из первой пары скобок, и переменная $2 получила бы пустое значение. Вот она, "жадность" в действии. Каждый "жадный" квантификатор оставляет остальной части шаблона минимум символов, лишь бы нашлось совпадение.

    Минимальные квантификаторы действуют наоборот: берут себе минимум символов, а оставляют максимум для нахождения совпадения. Конструкция же выбора не минимальна и не максимальна, она упорядочена по очереди появления в ней альтернативных подшаблонов.

    Возьмем шаблон

    12?3


    Чтобы избежать этой ошибки, нужно директивой

    use re 'eval';

    разрешить вставку кода Perl внутрь шаблонов.

    Этот код у нас распечатывает текущую позицию поиска pos в строке $_ (позиция отсчитывается от нуля) и значение переменной $1.

    Вначале мы видим строку

    Starting from 0

    Она говорит о старте очередной итерации поиска. Эта итерация в нашем примере единственная. Далее происходит сопоставление \w и символа a, а затем - печать текущей позиции поиска (2) и значения переменной $1. Но т.к. эта печать происходит еще до закрытия захватывающей скобки, когда переменная $1 еще не появилась, то возникает предупреждающее сообщение об использовании неопределенной переменной. Конечно, его можно было бы избежать, если написать

    print "$1\n" if defined $1;

    Но я не стал излишне загромождать код.

    Затем скобки захватывают символ a, и переменная $1 приобретает значение $1='a', а квантификатор * заставляет вернуться за закрывающую скобку и повторить поиск, начиная от \w. Теперь \w соответствует символу b, а распечатывается текущая позиция 2 и текущее значение $1, которое равно a. Затем повторяется тот же самый цикл, который печатает очередные текущие позиции и захваченные символы. После того, как подшаблон \w совпадает с d, печатается последняя позиция этой строки 4 и текущее значение $1, которое равно c. Значение d не вышло на печать, т.к. печать происходит до закрытия захватывающей скобки. А если бы мы вставили печать после нее или следующим оператором за оператором поиска, то увидели бы и значение d.

    С помощью такого диагностического вывода можно отследить, как выполняется поиск. Иногда вывод показывает слишком большое число итераций и/или возвратов, которые надо оптимизировать.

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

    Группировка элементов шаблона

    Мы уже применяли захватывающие и просто группирующие скобки. Рассмотрим еще некоторые тонкости.
    Как работают группирующие скобки с квантификаторами? Например, что захватят в переменную $1 скобки в регулярном выражении 'abc' =~ /(\w)*/ ? Это выражение похоже на (\w*), которое в переменную $1 захватит все три символа, но алгоритм его работы иной. Скобки в шаблоне (\w)* захватят последний символ - c. Для тех, кто впервые с этим сталкивается, это кажется непонятным. Это работает так: сначала \w совпадет с буквой a, затем квантификатор * будет заставлять повторяться всю внутрискобочную конструкцию снова и снова, насколько это возможно, оставляя при этом сохраненные состояния. При возвратах, когда происходит выход за открывающую захватывающую скобку, соответствующая ей нумерованная переменная становится неопределенной (перестает существовать). Если же при возврате происходит вход внутрь захватывающих скобок, то при следующем выходе за закрывающую скобку произойдет коррекция значения этой нумерованной переменной. Механизм работы с нумерованными переменными такой: при встрече открывающей скобки при движении вправо в структуре данных для соответствующей нумерованной переменной запоминается адрес позиции в результирующем тексте, где она начинается, а при выходе за закрывающую захватывающую скобку запоминается адрес позиции, где значение этой переменной заканчивается. Таким образом, нумерованные переменные не копируют фрагменты найденного текста, а просто ссылаются на них.
    В нашем случае выражение \w повторится трижды, захватывая каждый раз следующую букву, и в конце концов захваченной окажется буква c. Мы получаем интересный тактический прием захвата последнего фрагмента текста, который соответствует заданному подшаблону. Я думаю, вы уже можете догадаться, как в общем случае можно захватить такой фрагмент текста, который стоит на n-ном месте с начала или конца ряда таких фрагментов. Например, чтобы захватить предпоследний символ, надо записать
    /(\w)*\w/
    А как бы стал работать этот шаблон, если бы квантификатор был минимальным? В этом случае
    'abc' =~ /(\w)*?/;

    скобки бы совпали с пустым фрагментом в начале строки, а переменная $ 1 получила бы неопределенное значение. Ее попросту не было бы ввиду нулевого значения квантификатора. Если бы этот квантификатор стоял внутри скобок:

    'abc' =~ /(\w*?)/;

    то переменная $1 существовала бы и имела бы пустое значение.

    Теперь рассмотрим несохраняющие (группирующие) скобки:

    (?: шаблон )

    Шаблон в них может и отсутствовать (быть пустым).

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

    (?im:^passport nr\. \d+)

    Область действия этих квантификаторов ограничена шаблоном, стоящим в этих группирующих скобках. Это означает, что внутри этих скобок будет действовать режим поиска без учета регистра и метасимвол ^ будет совпадать не только в начале текста, но также в начале каждой логической строки, т.е. сразу после символа \n, если толко он не стоит самым последним в тексте. Но, конечно, эти режимы могут быть отменены другими квантификаторами, которые могут находиться в шаблонах внутри этих скобок.

    Аналогично можно отменить действие модификаторов, если поставить перед ними знак минус. Например:

    (?-i:^passport nr\. \d+)

    Поиск будет вестись с учетом регистра символов. Или

    (?i-ms:^passport nr\. \d+)

    Включается модификатор i и отключаются модификаторы m и s.

    И в конце добавлю, что скобки, как и другие метасимволы в регулярном выражении, нельзя задать как эскейп-последовательность: \x3a вместо (. Но! Забегая вперед, скажу, что почти произвольные части регулярного выражения могут находиться внутри интерполированных переменных. Смоторите, как это работает:

    my $a='('; 'abc' =~ /$a\w)*/; print $1;

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

    Комментарии в регулярных выражениях

    Регулярные выражения могут быть очень громоздкими и сложными, и разработчики предусмотрели возможность создания в них комментариев, так же, как и в текстах программ. Для этого есть два варианта.
    В первом, более простом варианте, вы можете использовать специальные скобки
    (?# комментарий ).
    Эта конструкция игнорируется. Комментарий не может содержать закрывающую круглую скобку, т.к. по ее наличию определяется, где заканчивается конструкция комментария. Сам комментарий может отсутствовать: закрывающая скобка может стоять сразу за решеткой. Конструкция комментария может даже стоять между подшаблоном и его квантификатором: оператор
    print "bb" =~ /^b(?#aaaaa){2}$/;
    напечатает единицу. Но это еще не значит, что конструкция комментария может стоять абсолютно в любом месте регулярного выражения. Обычно ее используют между подшаблонами, и этого бывает достаточно.
    Вот еще одна тонкость: если это наше регулярное выражение будет ограничено решетками, то произойдет ошибка синтаксиса:
    print "bb" =~ m#^b(?#aaaaa){2}$#;
    Возникнет сообщение
    Sequence (? incomplete in regex; …
    Дело в том, что Perl определяет, где заканчивается регулярное выражение, по символу, который соответствует ограничителю, стоящему перед этим выражением. И если внутри регулярного выражения этот символ присутствует в явном виде (а, скажем, не как эскейп-последовательность \x23), то он должен быть замаскирован обратной косой чертой:
    print "bb" =~ m#^b(?\#aaaaa){2}$#;
    Здесь уже все в порядке.
    Но это, конечно, запутывает регулярное выражение. Представьте, что в качестве ограничителей мы выбрали круглые скобки:
    m( регулярное выражение )
    и хотим в этом выражении применять скобочные конструкции. Тогда нам придется маскировать обратным слэшем все круглые скобки! А как в шаблоне записать литерал, состоящий из такой скобки? Похоже, что в явном виде вообще никак. Поэтому, если внутри шаблона встречается косая черта /, то в качестве ограничителей лучше выбирать восклицательный знак, решетку, двоеточие, запятую, знак процента или аналогичный символ, который отсутствует в шаблоне. Если вы, конечно, не участвуете в конкурсе на самую непонятную программу на Perl.
    Чаще используют запись регулярного выражения в свободном стиле, для чего существует модификатор x. В зоне действия этого модификатора все, что начинается с символа решетки, и до конца строки в файле программы считается комментарием и игнорируется. Например:
    print "bb" =~ /^ # Это начало строки b # Далее идет символ b {2} # За ним следует квантификатор $ # И в конце - конец строки /x;
    И этот пример работает. Как видим, мы включили не только комментарии, но и пробелы для красивых отступов вне комментариев! Да, в пределах действия квантификатора x все пробельные символы (а не только пробелы) игнорируются. Ведь комментарии надо чем-то отделять для наглядности от кода. Но теперь в регулярное выражение нельзя вставить эти самые пробельные символы в явном виде, ведь они не будут работать. Придется воспользоваться эскейп-последовательностями
    \x20 для пробела \t для табуляции и т.д.
    Или же в общем виде: \s.

    Опережающая проверка

    Это сложное условие (мнимый символ, якорь). Эту проверку еще называют "заглядыванием вперед". Вводится она конструкцией
    (?= шаблон )
    Шаблон может быть как угодно сложным. Это условие истинно, если в текущей позиции находится текст, совпадающий с заданным шаблоном. Обратите внимание, что эти условные конструкции совпадают не с текстом, а с позицией в тексте подобно своим более простым собратьям \b, \A, $ и т.д.
    Замечу, что атомарная группировка имитируется позитивной опережающей проверкой и обратной ссылкой:
    (?> шаблон ) эквивалентно (?=( шаблон ))\1
    Происходит это потому, что после выполнения опережающей (как и ретроспективной) проверки уничтожаются все сохраненные состояния, возникшие внутри нее.
    Имеется противоположный случай проверки - негативная опережающая проверка:
    (?! шаблон )
    Такое условие истинно, если в текущей позиции нет текста, совпадающего с заданным шаблоном.
    Например, шаблон
    \w+(?=\s+)
    найдет слово, за которым стоит один или несколько пробельных символов, но сами пробельные символы в результат поиска не войдут. Дело в том, что позиционная проверка не поглощает текст: после применения этих условных конструкций текущая позиция в тексте вернется на прежнее место, где была до применения этого якоря. Интересно, что захватывающие скобки внутри этих проверок захватывают текст, о чем забывают упомянуть авторы книг по Perl.
    Рассмотрим такой пример:
    $_='abc'; print "1\n" if /a(?=(b))bc/; print $1;
    Будут напечатаны такие строки:
    1 b
    Как видим, после совпадения с символом a после него ищется символ b. Он находится, и поиск продолжается дальше. Если сразу бы после a не стоял символ b, то поиск потерпел бы неудачу и началась бы следующая итерация, т.к. сохраненных состояний, к которым можно вернуться, нет. Внутри опережающей проверки этот символ захватывается в переменную $1, после чего проверка заканчивается успешно, и текущая позиция в строке возвращается к символу b. Затем выполняется соответствие подшаблона bc символам bc, на этом оператор поиска успешно завершается.
    Подобные сложные проверки бывают необходимы в сложных случаях, когда надо принять решение в зависимости от того, какой фрагмент текста находится в текущей позиции (или вообще впереди нее). Эти условия обычно используются в условных конструкциях, о которых речь впереди.
    Обратите внимание на интересную особенность сложных условий: в результате работы оператора
    "a" =~ /(?=(a))/
    переменная $1 получит значение a, а в результате работы похожего регулярного выражения
    "a" =~ /((?=a))/
    переменная $1 получит пустое значение. Это можно объяснить тем, что в первом случае внутри сложного условия происходит захват буквы a, а затем после выхода за это сложное условие (на конец регулярного выражения) текущая позиция в шаблоне возвращается в точку перед буквой a, но сама буква уже сидит в переменной $1. Во втором случае после выполнения фрагмента шаблона ((?=a текущая позиция в шаблоне передвигается за букву a, но после закрытия скобки в сложном условии ((?=a) происходит возврат текущей позиции в шаблоне перед буквой а, и после закрытия захватывающей скобки эта текущая позиция остается перед буквой a, как и в момент открытия захватывающей пары скобок. В результата эта пара скобок захватывает пустой фрагмент текста.

    Ретроспективная проверка

    Имеется аналогичная возможность также "заглянуть назад":
    (?<= шаблон )
    Это условие истинно, если перед текущей позицией имеется текст, совпадающий с шаблоном. Но не надо думать, что проверка здесь идет справа налево. Например:
    $_='abcd'; print "1\n" if /ab(?<=(ab))/; print $1;
    В результате получим вывод
    1 ab
    Как видим, захват текста в этих якорях также происходит.
    Негативная ретроспективная проверка задается выражением
    (? и требует, чтобы непосредственно перед текущей позицией не было текста, соответствующего данному шаблону.
    Но в ретроспективных проверках шаблон уже не может быть каким угодно, а должен соответствовать только определенному числу символов. Механизм поиска должен по этому шаблону определить длину текста, которому он может соответствовать, и искать левее текущей позиции на число символов, с которыми этот шаблон может совпасть.
    Иначе пришлось бы искать с начала всего текста, а это большие непроизводительные затраты времени. Поэтому шаблон не может содержать квантификаторы переменной длины, а все альтернативы в альтернативных конструкциях должны совпадать с текстом одной и той же длины. Вот примеры ошибок:
    (?<=\w+) (?<=\w{1,2}) (?<=\w{3}|abcd)
    В первых двух случаях имеем переменную длину квантификатора, а в последнем - разную длину альтернатив.
    В качестве примера использования этих якорей рассмотрим задачу разделения разрядов числа запятыми. Большие числа удобнее воспринимать, когда они поделены запятыми по три разряда: 12,345,678. Хотя тройки разрядов отсчитываются справа, а механизм поиска просматривает текст слева, эту техническую сложность нетрудно обойти. Сформулируем условие вставки запятой так: запятая вставляется в позицию, слева от которой имеется цифра, а справа - произвольное ненулевое число групп из трех цифр и далее не стоит цифра. Вот программа, которая моделирует этот пример:
    $_='number 1: 12345678 number 2: 98765432154321'; s/(?<=\d)(?=(?:\d{3})+(?!\d))/,/g; print $_;
    На печать выведется
    number 1: 12,345,678 number 2: 98,765,432,154,321

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

    Если бы мы вынесли за скобку проверку (?!\d) и написали бы так:

    s/(?<=\d)(?=(?:\d{3})+)(?!\d)/,/g;

    то это было бы неправильно. Смысл получился бы таким: позиция, перед которой стоит цифра, за которой идет группа из троек цифр и за которой нет цифры. Этот набор условий никогда не выполняется.

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

    /(?=\d)(?![09])/;

    означает, что в этой позиции должна стоять цифра и она не должна быть равна нулю и девяти. Это равносильно условию

    /(?=[1-8])/;

    Ко всему сказанному добавлю, что к позиционным проверкам, как и вообще к условиям с нулевой длиной совпадения, нельзя ставить квантификаторы.

    Условная конструкция

    В регулярных выражениях Perl имеется условная конструкция, которая позволяет сделать выбор подобно операторам if … then или if … then … else … Она имеет такой синтаксис:
    (? условие шаблон-да )
    или полный вариант
    (? условие шаблон-да | шаблон-нет )
    Работает эта конструкция так: вначале проверяется условие, и если оно истинно, то вся эта конструкция как бы заменяется на шаблон-да, а если условие ложно, то вместо конструкции подставляется шаблон-нет (если он есть). А если его нет, то на место этой конструкции ничего не подставляется, как будто этой конструкции не было.
    После символов (? в реальной программе всегда будет стоять открывающая круглая скобка. Эта скобка не может отделяться пробельными символами от знака вопроса, даже если регулярное выражение записано в свободном формате (с модификатором x).
    Шаблон-да и шаблон-нет представляют из себя произвольные регулярные выражения, а условие может иметь следующие значения.
  • Число в круглых скобках. Тогда оно считается номером каких-то захватывающих скобок. Если захватывающие скобки с данным номером участвовали в совпадении, то условие считается истинным, если нет - ложным. Здесь опять повторю замечание, что участвовать в совпадении и иметь непустое значение - не одно и то же. В операторе '' =~ /(.*)/;
    скобки участвовали в совпадении и переменная $1 получила пустое значение. В операторе
    '' =~ /(.)*/;
    скобки не участвовали в совпадении, хотя поиск также завершился удачно. Но т.к. квантификатор имел значение 0, то переменная $1 не существует (имеет неопределенное значение).
    В следующем примере отыскивается ссылка href, которая может быть заключена в кавычки, апострофы или не быть ограничена ничем:
    my $text='Internet-обучение'; if ($text =~ m!]*?href\s*=\s* (["'])? # совпадение для разделителя (', " или пусто). Запоминаем его ([^"'>\x20]+) # ссылка (все кроме пробела, ' и ") (?(1)\1) # если был разделитель, то подставляем шаблон для него [^>]*>[^<]+!ix) { print $2 }

    Обратите внимаение, что при появлении квантификатора x изменились ограничители регулярного выражения (c # на !) и внутри класса символов пробел задан как \x20.

  • Позиционная проверка, четыре варианта которой мы рассматривали. Если проверка возвращает истину, то подставляется шаблон да, иначе подставляется шаблон нет или "пусто" в случае короткого варианта условной конструкции.

    Для этого случая рассмотрим такой пример: пусть надо обрабатывать десятичные и 16-ные числа и пусть 16-ные числа предваряются символом $. Программа обработки может быть такой:

    $_=' 1234 aaa $12aF $abc $z 1456'; /(?(?=\$|\d) # начало числа? ( # да, начинаем его захват (?(?=\$) # 16-ное число? \$[\da-fA-F]+ # да, подставляем шаблон 16-ного числа (?{ print 'hex ' }) # печатаем префикс hex | \d+ # нет, ставим шаблон 10-ного числа (?{ print 'dec ' }) # печатаем префикс dec ) # конец захвата числа ) # конец вложенного условия (?{ print "$1\n" }) # печатаем само число [^\$\d]* # это не начало числа, пропускаем все до числа )/gx;

    На печать выдается следующее:

    dec 1234 hex $12aF hex $abc dec 1456

    А теперь разберемся, как работает эта программа. В регулярном выражении имеется две условные конструкции, вторая из них вложена в первую (используется в качестве шаблона да). Внешняя условная конструкция имеет такую логику: следующий символ - $ или десятичная цифра? Да - тогда применяем вложенную условную конструкцию, которая разделит десятичные и 16-ные числа. Нет - тогда подставим шаблон [^\$\d]*, который быстро пропустит все до следующего числа.

    Вложенная конструкция тоже имеет полную форму и работает так: если следующий символ - $, то она подставляет шаблон \$[\da-fA-F]+, который соответствует 16-ному числу, а иначе подставляет шаблон \d+ для десятичного числа.

    Кроме того, в первой альтернативе внешней условной конструкции производится захват числа в нумерованную переменную $1, а после шаблонов для каждого типа чисел выводится на печать, соответственно, слово hex или dec. После закрывающей захватывающей скобки выводится само число из $1.


    Может возникнуть вопрос: как будет обрабатываться фрагмент текста $z, который не является числом, но начинается с символа $? В этом случае сработает первая альтернатива из внешней условной конструкции, которая подставит шаблон \$[\da-fA-F]+. Он не совпадет с этим фрагментом, а это будет означать, что все регулярное выражение не совпало с данной позиции. Но благодаря модификатору g, поиск будет продолжаться в цикле со следующей позиции текста (символа z), и все числа будут напечатаны.

    Подробнее о работе модификатора g мы будем говорить, когда будем описывать работу операторов m/…/ и s/…/…/.

  • Фрагмент кода Perl. Если в качестве условия выступает фрагмент кода Perl, то используется возвращаемое им значение, т.е. результат последнего вычисления. Если он отличен от пустой строки, строки, которая состоит только из числа 0 (0.0), неопределенности и числового нуля, то считается истиной, иначе ложью. Здесь еще скажу, что в Perl есть интересная специальная переменная $^R - результат последнего выполнения кода Perl в регулярном выражении. Здесь слово "последнего" понимается как результат кода, который выполнился последний раз по времени в ходе работы системы поиска соответствия. Т.е. $^R всегда хранит самый "свежий" результат выполнения встроенного кода Perl. Кроме того, при возврате назад за код Perl вычисленное этим кодом значение "забывается" и восстанавливается предыдущее вычисленное значение, т.е. эта переменная автоматически локализуется. В начале работы программы переменная $^R имеет неопределенное значение, а после выхода из оператора поиска или проверки имеет последнее присвоенное ей значение. Кроме того, код Perl, который стоит в части условия условной конструкции, не изменяет значения переменной $^R! Интересно, что Perl не запрещает присваивать этой переменной значение напрямую.


  • Встроенный код Perl

    Встроенный код Perl является условием, которое всегда выполняется. Когда такой код попадается при движении в шаблоне слева направо, он выполняется. В этом коде можно проделать полезную работу, например, запомнить промежуточные значения нумерованных переменных, ведь позже поиск может завершиться неудачей. При прохождении встроенного кода в обратном направлении в случае возврата тоже могут происходить интересные и тонкие действия, которые мы рассмотрим в соответствующих лекциях. Но не вставляйте в этот код операторы перехода next, last, break, redo, goto и вызовы подпрограмм - это не предусмотрено правилами, хотя транслятор за этим не следит. Не следут также использовать во встроенном коде операторы, в которые входят регулярные выражения, - я не слышал, чтобы кто-то гарантировал правил ьность работы таких нестандартных приемов. Если вы хотите выйти из регулярного выражения при каком-то условии, то надо воспользоваться возможностями, которые даются регулярными выражениями. Иначе интерпретатор Perl может зависнуть, ведь перед выполнением оператора с регулярным выражением он выделяет память, инициализирует структуры данных. Эти действия корректно завершаются только при нормальном завершении оператора.
    Также не пытайтесь изменять во встроенном коде результирующий текст - это не даст эффекта.
    Иногда вы можете обнаружить, что код Perl должен был бы выполниться, но почему-то не выполняется. Здесь причина в том, что Perl имеет много оптимизаций при поиске по регулярному выражению. Если по каким-то причинам механизм поиска решит, что совпадения быть не может, он не будет пытаться делать этот поиск. Вот здесь поиск вообще не начнется:
    'abc' =~ /(?{ print '123' })abcd/;
    Система поиска определит, что для совпадения длина результирующего текста должна быть не меньше четырех символов, а их в тексте только три. Оператор закончится неудачно без применения поиска по регулярному выражению, и напечатано ничего не будет.
    Мы уже знаем, как выглядит конструкция вставки кода Perl:
    (?{ код Perl })
    Подробнее о встроенном коде мы поговорим в дальнейшем, когда наберемся опыта.

    Задание модификаторов внутри регулярного выражения

    Нами уже был рассмотрен вариант расположения модификаторов между знаком вопроса и двоеточием в группирующих скобках. Есть еще один вариант задания модификаторов:
    (? модификаторы )
    Например, (?is-mx) - включение режимов i и s и отключение режимов m и x. Область действия этой конструкции начинается сразу после нее и продолжается до следующей закрывающей круглой скобки. Имеется в виду, конечно, "настоящая" скобка, а не литерал \), который эквивалентен коду \x29. Если справа от этой конструкции нет закрывающей скобки, то ее область действия простирается до конца регулярного выражения. Эта закрывающая скобка может принадлежать захватывающей или незахватывающей паре скобок, закрывать условную конструкцию и т.д. Но, конечно, эту установку модификаторов может отменить другая установка тех же модификаторов. В конечном случае имеет действие та установка модификатора, которая находится "ближе" к данному фрагменту регулярного выражения.
    Атомарная группировка тоже относится к дополнительным конструкциям, но мы ее уже рассмотрели.

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

    Алгоритм работы операторов m// и s///

    В языке Perl есть такое понятие - контекст выражения. Операторы могут возвращать значение, которое зависит от того, чему оно присваивается. Если оно присвавается скалярной переменной, то возвращаемое значение может быть одно, а если присвоение происходит массиву, то возвращаться может уже список значений. Таким является оператор m//. Например, если мы используем его в условии условного оператора:
    if (m/…/) { … }
    то это скалярный контекст, т.к. в условии ожидается одно значение, которое трактуется как истина или ложь. А если написать
    my @m=m/…/;
    то это списковый контекст, в котором оператор m// возвратит список значений. Оператор print является списковым, т.к. он ожидает список в качестве своего аргумента, поэтому, если написать
    print m/…/;
    то это также будет использование оператора поиска в списковом контексте.

    Модификаторы операторов m// и s///

    Всего в регулярных выражениях используется восемь модификаторов.
  • i - игнорирует различие между заглавными и строчными буквами. На этот модификатор влияет установка локали.
  • s - метасимвол "точка" совпадает со всеми символами, включая символ новой строки \n.
  • m - разрешает привязку метасимволам ^ и $ к промежуточным символам \n в тексте. В этом случае метасимвол ^ совпадает не только в начале текста, как якорь \A, но и после каждого символа \n, который не стоит в самом конце текста. А метасимвол $ начинает совпадать не только в самом конце текста и перед \n, стоящим в самом конце текста, как якорь \Z, но также после каждого символа \n, который не стоит в самом конце текста.
  • x - игнорирует пробельные символы в регулярном выражении, разрешает использовать внутри шаблона комментарии.
  • g - поиск и замена выполняются глобально (в неявном цикле). Подробности мы рассмотрим.
  • c - работает только с модификатором g и только с оператором m//, который применяется в скалярном контексте. Запрещает сбрасывать текущую позицию поиска, когда не удалось найти очередного совпадения. Подробности будут далее.
  • o - шаблон с этим модификатором однократной компиляции транслируется один раз во время компиляции всей программы. Здесь дело в том, что внутри регулярного выражения могут встречаться переменные, которые будут интерполироваться, т.е. их значения будут подставляться в шаблон в качестве литералов. (Не путайте эти переменные с теми, которые присутствуют в исполняемом коде Perl, такие переменные не интерполируются.) Если интерпретатор видит, что происходит интерполяция переменных, то он каждый раз перед использованием этого оператора поиска/замены будет транслировать регулярное выражение во внутреннее представление (байт код). Это может отнять много времени, если такое выражение используется в цикле. Но
    если вы уверены, что интерполируемая переменная (или массив) не меняет своего значения, то можете поставить к такому регулярному выражению модификатор o, чтобы избежать аго многократной компиляции. Этот модификатор может стоять только после всего регулярного выражения.


  • Оператор m// в режиме однократного поиска и в скалярном контексте

    В режиме однократного поиска (т.е. без модификатора g) и в скалярном контексте оператор m// возвращает логическое значение: целое число 1, если поиск оказался успешным, или пустую строку в случае неудачи. Если в шаблоне имелись захватывающие скобки, то при успешном поиске создаются нумерованные переменные $1, $2, …, которые содержат соответствующие фрагменты захваченного текста. Если поиск оказался неудачным, то эти переменные хранят последнее состояние от предыдущего оператора поиска или замены.
    После успешного поиска также можно использовать специальные переменые:
  • $& - копия текста, совпавшего со всем регулярным выражением. Если вы заключите все регулярное выражение в круглые скобки: m/(…)/, то в случае успеха в переменной $1 получите то же самое значение.
  • $` - копия текста, предшествующего началу совпадения (т.е. текст, который был слева от совпадения).
  • $' - копия текста, следующего после совпадения (т.е. расположенного справа от совпавшего текста). После успешного совпадения текст "$`$&$'" представляет из себя копию исходного текста. Есть малозначительное исключение: если оператор m// был успешно применен к неопределенной переменной, то эти специальные переменные будут иметь пустые значения.
  • $+ - копия переменной $1 или $2… с максимальным номером, которой было присвоено значение. Если в регулярном выражении нет сохраняющих скобок или они не задействованы в совпадении, то эта переменная получает неопределенное значение.
  • $^N - копия переменной $1 или $2…, которая соответствует последней только что закрытой паре скобок на момент использования этой переменной. Если в регулярном выражении нет сохраняющих скобок или они не задействованы в совпадении, то эта переменная получает неопределенное значение.
  • @ - массив начальных смещений в целевом тексте. $-[0] хранит смещение начала совпадения от начала текста для всего регулярного выражения. $-[1] хранит смещение начала совпадения от начала текста для фрагмента текста, захваченного в переменную $1, $-[2] - для переменной $2 и т.д.
  • @+ - аналогичный массив конечных смещений в целевом тексте. Т.е. смещений символов, следующих за конечными символами совпадения во всем тексте, в переменной $1, $2 и т.д. Имеет место соотношение substr($text,$-[0],$+[0]-$-[0]) эквивалентно $&, substr($text,$-[1],$+[1]-$-[1]) эквивалентно $1 и т.д.


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

    Начинающие часто путают специальные переменные $+ и $^N, между которыми имеется тонкое различие. Сначала рассмотрим пример использования переменной $+. Пусть в регулярном выражении мы имеем альтернативную конструкцию, альтернативы которой заключены в захватывающие скобки:

    $text =~ /(…)|(…)|(…)/;

    Мы знаем, что совпадение может быть не более чем с одной альтернативой, и хотим получить фрагмент текста, совпавшего с какой-либо альтернативой. Не будь переменной $+, нам пришлось бы перебирать переменные $1, $2,… и определять, какая из них имеет определенное значение.

    Для понимания отличия переменной $+ от $^N рассмотрим такой пример:

    print "\$+=$+, \$^N=$^N" if "abc" =~ /(a(bc))/;

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

    $+=bc, $^N=abc

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

    Использовать значения переменных $+ и $^N можно также в коде Perl внутри регулярного выражения. Эти переменные корректируются после того, как закроется очередная захватывающая скобка. Например:

    print "\$+=$+, \$^N=$^N" if "abcd" =~ /(a(bc)(d)(?{ print "\$+=$+, \$^N=$^N\n" }))/;

    Здесь выводятся значения переменных $+ и $^N после третьей скобки внутри регулярного выражения, а также после того, как оператор поиска отработал. В итоге получается результат:

    $+=d, $^N=d $+=d, $^N=abcd

    $+ в обоих случаях относится к нумерованной переменной с максимальным номером - $3, а переменная $^N внутри регулярного выражения копирует переменную $3, а вне регулярного выражения является копией переменной $1.

    Оператор m// в скалярном контексте с модификатором g

    Оператор поиска в скалярном контексте и с модификатором g ведет себя особым образом: переменная, к которой применяется поиск, будет неявно хранить позицию, на которой остановился последний поиск оператором m// в этой переменной. При очередном применении к этой переменной оператора поиска с модификатором g (в списковом или скалярном контексте) поиск продолжится не с начала текста, а с той позиции, на которой он остановился в последний раз. Это позволяет применить к одной и той же переменной несколько операторов поиска и извлекать из текста элементы один за другим. Эта позиция, на которой остановился оператор m//g в последний раз, может быть получена при обращении к функции pos с тем же аргументом, к которому применялся этот поиск.
    Вот пример:
    $_='1234567890'; /(\d)(\d)/g; print "$1,$2\n"; my @a=/(\d)(\d)/g; print join ',',@a;
    На печать выйдут строки
    1,2 3,4,5,6,7,8,9,0
    Такая особенность сделана для возможности создания программ лексеров, которые разбирают текст на лексические единицы. Примеры мы увидим в следующих главах.
    В случае применения оператора поиска к одной и той же константе:
    while ('abcd'=~/(\w)/g) { print $1 }
    она тоже будет хранить последнюю позицию поиска, иначе этот цикл продолжался бы вечно, каждый раз находя букву a. На печать выйдет строка
    abcd

    Оператор m// в списковом контексте без модификатора g

    Рассмотрим работу оператора m// в списковом контексте в режиме однократного поиска (т.е без модификатора g). Возвращаемое значение зависит от того, есть ли в шаблоне захватывающие скобки. Если есть хотя бы одна пара захватывающих скобок, то в результате будет возвращен список из значений всех нумерованных переменных по числу использованных в регулярном выражении захватывающих скобок: ($1, $2, $3, …).
    Сами эти нумерованные переменные также создаются. В этом варианте оператор m// обычно применяется, когда надо извлечь из строки заранее известное число групп переменная=значение. Например:
    my $url='var1=value1&var2=value2'; my @pairs=$url =~ /(\w+)=(\w+)&(\w+)=(\w+)/; print join ',',@pairs;
    На печать выйдет строка
    var1,value1,var2,value2
    Если в шаблоне нет захватывающих скобок, то в случае успешного поиска возвращается список из одного элемента - числа 1.
    В случае неудачного поиска в обоих случаях возвращается пустой список.
    Вот пример идиомы для применения оператора m// в списковом контексте:
    my $date='2007/3/12'; if (my($year,$month,$day)=$date =~ m!^(\d+)/(\d+)/(\d+)$!) { print "Year=$1, month=$2, day=$3"; } else { print 'Not found!'; }
    На печать выведется
    Year=2007, month=3, day=12
    Мы присваиваем вовращаемое значение списку, поэтому применяется списковый контекст оператора поиска. Затем в операторе if этот список рассматривается в скалярном контексте, что дает число элементов этого списка. Полученный результат используется для проверки успешности выполнения оператора поиска.

    Оператор m// в списковом контексте с модификатором g

    Если оператор поиска используется в глобальном поиске в списковом контексте и в шаблоне есть захватывающие скобки, то в случае успешного поиска возвращаются все найденные фрагменты текста. В случае неудачного поиска возвращается пустой список.
    Пример:
    my $text='123 234 345 456'; my @m=$text =~ /(\d+)(\s+)/g; print @m;
    Будет напечатана строка
    123 234 345 456
    Если в регулярном выражении нет захватывающих скобок, то возвращается список всех найденных прототипов всего шаблона, как если бы весь он был заключен в круглые скобки:
    my $text='123 234 345 456'; my @m=$text =~ /\d+/g; print join ',',@m;
    Будет напечатано
    123,234,345,456

    Предварительная обработка регулярных выражений

    Регулярные выражения в операторах m// и s/// обрабатываются особым образом. Вообще говоря, они "обрабатываются как строка в кавычках с учетом специфики регулярных выражений". Но если в операторе поиска/подстановки регулярное выражение ограничено апострофами, то такое регулярное выражение, как обычно пишут, "обрабатывается как строка в апострофах", т.е. интерполяция переменных в нем не производится. Здесь надо уточнить, что такие эскейп-последовательности, как \r, \n, \t, все равно работают в регулярном выражении, которое ограничено апострофами, хотя в строке, ограниченной апострофами, они не работают. В этом - специфика регулярных выражений.
    В части замены оператора s/// действует то же правило, за тем исключением, когда там стоит код Perl (есть модификатор e). В строке, ограниченной апострофами, имеются только два метасимвола: апостроф и обратный слэш. Здесь замечу, что в строке замены, которая ограничена апострофами, эскейп-последовательности \r, \n, \t, … не работают. В случае, если регулярное выражение не ограничено апострофами, в нем символы $ и @ являются метасимволами. Но символ $ используется как якорь для конца строки, не получается ли здесь конфликта? Этот вопрос мы рассмотрим позднее.
    Если скалярная переменная просто заменяется своим значением, то массив интерполируется всеми своими значениями или срезом своих элементов, который тоже может быть задан. Разделителем между элементами массива служит значение специальной переменной $", которая по умолчанию содержит пробел. Хеши в регулярное выражение не интерполируются, т.к. это не имеет смысла, поэтому символ % в шаблонах не является метасимволом.
    Замечу еще, что внутри классов переменные тоже интерполируются. Для закрепления этого материала рассмотрим такие примеры:
    $_='abc'; my $s='c'; print '1' if /^ab$s$/;
    Будет напечатана единица. После интерполяции регулярное выражение станет таким:
    /^abс$/
    Другой пример:
    $_='abc'; my $s='ab'; print /[$s]/g;
    Напечатается ab. Это регулярное выражение эквивалентно такому:
    /[ab]/g
    Класс совпал два раза и захватил на первой итерации букву a, а на второй - букву b.
    В регулярном выражении интерполируются также специальные переменные кроме переменных $$, $(, $), $|, чтобы не допускать конфликта с метасимволом $. Не возникает коллизии, если в конце регулярного выражения стоит $ и за ним ограничитель / (или другой ограничитель). Ведь первым делом при обработке регулярного выражения интерпретатор ищет завершающий ограничитель, а это происходит до интерполяции переменных.
    После интерполяции переменных происходит обработка конструкций изменения регистра символов /L, /l, /U, /u, а также конструкций вставки литерального текста \Q…\E.
    Полученный результат передается механизму регулярных выражений для интерпретации.

    Класс совпал два раза и захватил на первой итерации букву a, а на второй - букву b.

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

    После интерполяции переменных происходит обработка конструкций изменения регистра символов /L, /l, /U, /u, а также конструкций вставки литерального текста \Q…\E.

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

    © 2003-2007 INTUIT.ru. Все права защищены.

    Работа оператор s/// с модификатором g и без него

    Оператор замены s/// без модификатора g ищет в заданной переменной прототип регулярного выражения и в случае нахождения заменяет его на выражение для замены, после чего заканчивает свою работу. При наличии модификатора g замена производится столько раз, сколько раз был найден фрагмент текста, соответствующий регулярному выражению. В результате все найденные фрагменты будут заменены на заданное выражение. В результате возвращается число произведенных замен или пустая строка (или соответствующий список в случае спискового контекста). Например:
    my $text='123 234 345 456'; $text =~ s/\d+/a/; print $text;
    Получим строку
    a 234 345 456
    Еще пример:
    my $text='123 234 345 456'; $text =~ s/\d+/reverse $&/ge; print $text;
    Здесь получим такой результат:
    321 432 543 654
    В последнем операторе поиска и замены мы использовали режим замены с модификатором e (с выполнением кода Perl). В результате каждое найденное число заменилось на это же число, записанное в обратном порядке. Также мы могли бы написать:
    $text =~ s/(\d+)/reverse $1/ge;
    В операторах m// и s/// специальные переменные $1, $2, …, $&, $`, $' и т.д. для каждой итерации, обусловленной модификатором g, создаются заново (локализуются). В результате при замене используются нужные значение этих переменных, которые возникли при последней итерации поиска.

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

    Как укоротить длинные URL и длинные слова?

    У вебмастеров иногда возникает задача укоротить длинные URL, которые вводят в форму участники форумов. Из-за длинных ссылок расползается дизайн страниц. Предположим, кто-то отослал на форум такой текст:
    Рекомендую посетить: http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/
    На самом деле ссылка может оказаться еще длинее. В HTML-коде страницы форума эта ссылка должна появиться в виде URL:
    Рекомендую посетить: http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/
    Внутри тега a ссылку, естественно, обрезать не надо, тем более, что в видимом тексте ее не будет, а вот то, что стоит перед тегом , будет видно, и если это "слово" будет слишком длинным, то это может испортить дизайн. Надо после 50-го символа такой ссылки вставить три точки и получить такой текст:
    Рекомендую посетить: http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.co…
    Другие длинные слова (не ссылки) надо также урезать. Мало ли что может ввести пользователь… Я нашел такое решение:
    s/(\A|\s)(?!href=")(\S{50})\S+/$1$2.../g;
    Предполагаем, что текст находится в переменной $_. Регулярное выражение начинает поиск от начала текста и каждого пробельного символа. Если после этого не стоит фрагмент href=, то проверяется, есть ли после этого "слово" длиной 50 символов, после которого стоит непробельный символ. Если да, то происходит замена всего, что найдено на $1 - пробельный символ перед длинным "словом", 50 символов с начала этого "слова" на $1, эти же 50 символов с начала длинного "слова", а последующие непробельные символы заменяются на три точки. Если бы в начале шаблона вместо (\A|\s) стояло просто (\s), то длинное "слово", стоящее в самом начале текста, не было бы укорочено.
    Вот еще один вариант с использованием кода Perl в части замены:
    s/((\S{50})\S+)/index($1,'href=') > -1 ? $1 : "$2..."/ge;

    Ищем 50 непробельных символов, сразу за которыми есть хотя бы один непробельный символ. Эти 50 символов берем в переменную $2, а все длинное "слово" берем в переменную $1. В части замены проверяем, есть ли фрагмент текста href= в перемнной $1.

    Если он есть, то заменяем найденное на $1, т.е. на себя, а если нет, то заменяем найденное на текст $2, за которым идут три точки.

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

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

    $_=' ' x 13000; $_.='a'; if (/[^\s<>](?![^<>]*>)/g) { print "1\n".pos; } else { print "0\n"; }

    Вначале мы создаем длинную строку (больше 90000 символов) из тегов, в конец которой вставляем букву a вне тегов. В регулярном выражении мы ищем символ, отличный от пробельного и символов < и >, который не находится внутри тега. "Не находится внутри тега" означает, что сразу после этого символа не должно быть текста, соответствующего шаблону [^<>]*>. В случае нахождения такого символа программа печатает единицу и смещение этого символа от начала текста.

    Вот другое решение, которое в отличие от первого настроено быстро пропускать все символы, которые нас не интересуют, т.е. пробельные и теги <> вместе с их содержимым:

    /\A(?>(?:(?>\s*)<(?>[^>]*)>)*)\S/g

    Оно кажется сложным из-за наличия трех атомарных группировок. Вот этот же шаблон без них:

    /\A(?:\s*<[^>]*>)*\S/g

    Внутри скобок мы пропускаем последовательности пробельных символов \s* и конструкции <[^>]*>. Обратите внимание, что между этими подшаблонами излишне ставить знак альтернативы |. Вначале стоит привязка \A, чтобы поиск не начался внутри тега.

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

    Поиск множественных совпадений

    Для поиска множественных совпадений можно применить модификатор g и скалярный контекст. Например:
    while ('12345' =~ /\d/g) { print "Найдена еще цифра: $&\n" }
    Будет напечатано:
    Найдена еще цифра: 1 Найдена еще цифра: 2 Найдена еще цифра: 3 Найдена еще цифра: 4 Найдена еще цифра: 5
    Иногда для поиска множественных совпадений нужно искать следующее сразу от конца предыдущего совпадения, аналогично привязке к началу текста \A. Для этого есть специальный якорь \G, о котором мы поговорим позже.
    Модификатор g и списковый контекст позволяет запомнить сразу все совпадения в тексте:
    my @a='12345' =~ /\d/g; print join ',',@a;
    Получаем
    1,2,3,4,5

    Поиск n-ного совпадения

    С модификатором g происходит поиск всех прототипов шаблона, но что делать, если нужно только вполне определенное, скажем, третье, совпадение? Здесь можно применить оператор цикла while:
    use locale;
    my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122'; my $count=0; while ($s =~ /тел\.\s+([\d-]+)/gi) { if (++$count == 3) { print "Третий номер: $1\n"; } }
    На печать выйдет строка
    Третий номер: 4-1122
    В регулярном выражении мы захватываем в переменную $1 последовательность из цифр и знака минус, которая стоит после фрагмента "тел. ". Модификатор g обеспечивает поиск всех прототипов шаблона друг за другом, сохраняя в переменной $s смещение конца предыдущего совпадения. При очередном успехе оператор поиска возвращает единицу, что заставляет цикл while продолжать работать. После того, как будет найден последний номер телефона, оператор поиска вернет пустую строку, и оператор while закончит выполнение. Также можно было бы после печати третьего телефона поставить оператор last.
    Модификатор i нужен, т.к. не все фрагменты "тел." набраны строчными буквами. Но тогда нужна русская локаль, чтобы модификатор i действовал также на русские буквы.
    Иначе в этом примере совпадение начнется со второго телефонного номера и на печать ничего не выйдет.
    С таким же успехом можно было использовать цикл for:
    use locale;
    my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122'; for (my $count=0; $s =~ /тел\.\s+([\d-]+)/gi;) { if (++$count == 3) { print "Третий номер: $1\n"; last; } }
    Наконец, можно использовать встроенный код Perl, но тогда надо форсировать списочный контекст выполнения оператора поиска, чтобы поиск выполнялся итеративно под действием модификатора g, иначе после нахождения первого телефона поиск закончится:
    use locale;
    my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122'; my $count=0; my @a=$s =~ /тел\.\s+([\d-]+) (?{ if (++$count == 3) { print "Третий номер: $1\n"; } })/gix;
    Это, конечно, не так красиво из-за того, что пришлось присваивать все найденные телефоны массиву.

    Поиск нечувствительных к регистру совпадений

    По умолчанию, модификатор i отключен и сравнение символов чувствительно к регистру. Если вам все равно, большими или маленькими буквами написано то, что вы ищете, то применяйте модификатор i: m/…/i. Бывает так, что что-то отыскивается с учетом регистра символов, а что-то - без такого учета. В этом случае можно использовать интервальное задание квантификаторов или задание их локально для данных группирующих скобок между вопросительным знаком и двоеточием. Рассмотрим такой пример:
    /ab(cd(?-i)ef(?i:gh)ij)kl/i;
    В этом регулярном выражении глобально действует модификатор i, т.е. сравнение производится без учета регистра символов. По такому правилу отыскиваются символы ab и cd. Затем внутри захватывающих скобок после себя конструкция (?-i) выключает модификатор i, и символы ef отыскиваются с учетом регистра. Внутри скобочной конструкции (?i:gh) опять включается режим сравнения без учета регистра символов, и символы gh отыскиваются без учета регистра. Символы ij стоят после этой конструкции, а в этом месте восстанавливается режим -i, который был задан выражением (?-i), и символы ij отыскиваются с учетом регистра. Символы kl отыскиваются опять без учета регистра, т.к. стоят за закрывающей скобкой, за которой кончается область действия выражения (?-i) и восстанавливается действие глобального модификатора i.
    Также заметьте, что модификатор i зависит от локальной установки системы, ведь он действует только на сравнение букв.

    Поиск отдельных слов

    В регулярных выражениях словом называется последовательность символов \w. В множество \w входят все строчные и прописные латинские буквы, десятичные цифры и знак подчерка: [a zA Z0 9_]. К этому множеству относятся все символы, считающиеся буквами в той локальной установке, которая используется. Для ActiveState Perl под Windows для этого достаточно написать директиву
    use locale;
    и все русские буквы (включая буквы ё и Ё) в кодировке Windows-1251 также будут считаться буквами.
    Если вы под буквами понимаете что-то другое (например, вы не считаете символ подчерка за букву), то определите соответствующий класс. Если вас интересуют все непробельные символы, то используйте эскейп-последовательность \S.
    Эскейп-последовательность \w соответствует ровно одной "букве". Для слов применяйте шаблон \w+. Слово всегда ограничено с обеих сторон мнимыми символами \b - границами слов. Действие этих якорей также зависит от локальной установки операционной системы. Для гарантии того, что совпадение со словом начнется и закончится на его границе, можно применять шаблон \b\w+\b. Чтобы уничтожить ненужные сохраненные состояния, можно также использовать атомарную группировку: \b(?>\w+)\b.
    В Perl мнимые символы, которые соответствуют началу слова и его концу, не различаются - оба обозначаются как \b. В некоторых языках программирования для этих символов существуют отдельные якоря. Но можно самому определить такие мнимые символы, исходя из их смысла. Вот якорь, который соответствует началу слова:
    (? Слева нет буквы, а справа она есть.
    А вот якорь, который соответствует концу слова:
    (?<=\w)(?!\w)
    Слева есть буква, а справа буквы нет.
    Я надеюсь, вы уже понимаете разницу между выражениями (?!\w) и (?=\W). В первом случае справа может и ничего не стоять (конец текста), а второй случай требует, чтобы справа стоял символ, который не является буквой.

    Применение якорей, проверки текста до и после шаблона

    Для привязки к началу текста можно использовать якорь \A или ^ без модификатора m.
    Вот как можно проверить, что текст не начинается с точки:
    if ($text =~ /^\./) { print "Текст не должен начинаться с точки!" }
    Так можно вставить по одному пробелу в начало каждой строки текста:
    $_=< s/^/ /gm;
    Без модификатора m пробел будет вставлен лишь в начало первой строки.
    Пустые строки удаляются оператором замены с таким регулярным выражением:
    $_=< Вторая строка. Третья строка. EOF
    s/^\n//gm; print $_;
    Получаем на выходе:
    Первая строка. Вторая строка. Третья строка.
    Может показаться, что оператор s/^$//gm тоже должен удалить пустые строки, но он не сделает замены. Удалиться должен реальный символ, а не совпадение с началом и концом строки, поэтому сперва он должен быть найден. А что-то вставить в пустую строку таким оператором s вполне возможно.
    При преобразовании текста в HTML нужно заменять символы & на & Это делается просто:
    s/&/&/g
    ведь амперсанд не является метасимволом в регулярных выражениях.
    Предположим, что нам надо найти последнее число в большом тексте. Здесь можно воспользоваться таким приемом: использовать конструкцию .* с модификатором s, которая мгновенно захватит весь текст и выйдет на его конец. Т.к. совпадение с числом не будет найдено, то квантификатор * будет отдавать по одному символу, пока не отыщется совпадение. Как будто все просто. Попробуем такой пример:
    $_=< /.*(\d+)/s; print $1;
    На печать выходит лишь цифра девять: 9. Удивлены? Квантификатор вернул несколько символов, и текущая позиция в тексте оказалась на цифре 9. Этого достаточно для совпадения шаблона \d+. Чтобы получить правильный результат, надо потребовать, чтобы перед подшаблоном \d+ не было цифры:
    $_=< /.*(? Печатает 789.
    Чтобы привязать шаблон к концу текста, можно воспользоваться якорями \z (для привязки к истинному концу текста) или \Z ($ без модификатора m):
    $_=< /(\d+)$/; print $1;
    Печатает 789. Как видим, привязка к концу текста нашла последнее число без захвата всего текста и без заглядывания назад. Но если после последнего числа могут быть символы, то регулярное выражение требуется усложнить:
    $_=< /(\d+)\D*$/; print $1;
    Опять получаем 789, но поиск будет медленнее из-за увеличивающегося числа проверок.

    Привязка к началу и концу строк и текста

    Мнимые символы \A, \Z и \z не зависят от установок модификаторов и работают всегда одинаково. Не таковы якоря ^ и $, работа которых зависит от модификатора m. В зоне, где модификатор m отключен, якорь ^ соответствует якорю \A, а якорь $ - якорю \Z. Рассмотрим такой пример:
    $_="a\r\n"; print '1' if m'a\r$\n';
    Здесь я использовал в качестве ограничителей регулярного выражения апострофы, чтобы не было попытки интерполяции специальной переменной $\. Метасимвол $ стоит перед символом \n, который является последним в тексте, и на печать выйдет единица.
    Если переставить символ $ перед символом \r:
    $_="a\r\n"; print '1' if m'a$\r\n';
    то поиск закончится неудачей, потому что здесь $ не стоит непосредственно перед символом \n.
    Теперь вернемся к первому примеру и добавим в конце текста какой-нибудь символ:
    $_="a\r\nb"; print '1' if m'a\r$\n';
    В этом случае поиск вновь завершится неудачно, потому что символ \n здесь уже не последний в тексте. Но если поставить модификатор m, то поиск закончится удачей:
    $_="a\r\nb"; print '1' if m'a\r$\n'm;
    Теперь такой пример:
    $_="a\n"; print '1' if /'a\n^/m;
    Единица напечатана не будет, потому что якорь ^ не совпадает после символа \n, если он стоит последним в тексте, и модификатор m здесь не поможет. Но если после \n поставить еще символ:
    $_="a\n\n"; print '1' if m'a\n^'m;
    то совпадение будет найдено. Теперь в последнем примере уберем модификатор m:
    $_="a\n\n"; print '1' if /a\n^/;
    Совпадения шаблона тексту уже не будет, т.к. без модификатора m якорь ^ совпадает только в начале текста.

    Замена n-го совпадения

    Теперь рассмотрим замену n-го найденного фрагмента текста.
    use locale;
    my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122'; my $count=0; $s =~ s/(тел\.\s+)([\d-]+)/ ++$count == 3 ? "${1}9-9999" : "$1$2" /egi; print $s;
    Напечатается строка
    Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 9-9999
    Оператор подстановки с модификатором g заменяет все найденные фрагменты текста глобально, и ему для этого не нужен списочный контекст, как оператору поиска.
    Мы хотели третий номер телефона заменить на 9-9999, но после каждого найденного номера выполняется замена, поэтому то, что не нужно менять, должно быть заменено "на себя". Для этого мы взяли все перед номером телефона в переменную $1, а все остальное - в переменную $2. Если номер телефона не равен 3, то мы найденный фрагмент текста (который соответствует всему регулярному выражению!) меняем на строку $1$2, а в третьем случае в замене вместо номера телефона подставляем 9-9999. Для разделения имени переменной от цифр используем фигурные скобки. Не забываем также поставить модификатор e.
    Как быть, когда нужно заменить n-й от конца найденный фрагмент текста, если заранее неизвестно, сколько таких фрагментов будет найдено? Рассмотрим такую идею.
    Призываем в помощь конструкцию опережающей проверки и записываем регулярное выражение так, чтобы оно совпало только на n-м от конца искомом фрагменте текста, где n отсчитывается с нуля. Пусть это n хранится в переменной $n. Попробуем вначале такую программу для решения задачи:
    #!/usr/bin/perl -w use strict; use locale;
    my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122'; my $tel='тел\.\s+[\d-]+'; my $n=1; $s =~ s/(тел\.\s+)[\d-]+ # ищем слово "тел." + номер (?=(?:.*?$tel){$n} # за которым $n раз идет "тел." + номер (?!.*?$tel) # после чего не встречается "тел." +номер )/${1}9-9999/isx; print $s;
    Поясню, как она должна работать по первоначальному замыслу.
    Т.к. литерал тел\.\s+[\d-]+ в регулярном выражении будет встречаться несколько раз, то оформим его в виде переменной $tel, которую будем подставлять. Сформулируем задачу в терминах регулярных выражений: нам надо найти фрагмент, соответствующий шаблону тел\.\s+[\d-]+, за которым (фрагментом) в тексте встречается ровно $n таких же фрагментов. Номер телефона в таком фрагменте мы меняем на 9-9999.
    Чтобы в этом фрагменте всё, кроме номера телефона, сохранилось, мы берем это остальное в скобки и получаем подшаблон (тел\.\s+)[\d-]+, с которого начинается регулярное выражение. За фрагментом текста, соответствующим этому шаблону, должен идти фрагмент (?:.*?$tel){$n}. ."*" впереди него означает, что эти $n фрагментов не обязательно должны идти сразу за первым фрагментом и друг за другом, между ними могут быть посторонние включения, которые поглощаются конструкцией ".*?". После того, как эти $n фрагментов поглощены, не должно стоять такого же фрагмента текста с любой позиции после этих $n фрагментов, это проверяет условие (?!.*?$tel). Оба этих подшаблона (?:.*?$tel){$n} и (?!.*?$tel) включены в позиционную проверку (?=…). Т.е. за найденным номером телефона должен быть фрагмент текста, который стоит внутри всей этой проверки (?=…).
    Что ж, как будто бы все верно, начинаем проверять работу этой программы. Задаем $n=0 и видим результат:
    Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 9-9999

    Верно! Заменился номер телефона, который стоит нулевым справа. Далее даем $n значение 1 и смотрим результат… Вот неожиданность: заменился первый телефон

    Тел. 9-9999. Другой тел. 3-2233, а вот еще один тел. 4-1122

    а должен был бы тот, что посередине! При $n=2 - та же картина… Видимо, где-то закралась ошибка. Можете вы найти (и исправить) ее самостоятельно? Тогда читайте дальше.

    Разберем работу этого регулярного выражения при $n=1. После того, как нашелся первый номер телефона по подшаблону (тел\.\s+)[\d-]+, началось заглядывание вперед, и подшаблон (?:.*?$tel){$n} поглотил второй телефонный номер. За ним началась проверка (?!.*?$tel), которая закончилась неудачей, т.к. после второго телефонного номера есть еще номер. "Не беда! - говорит механизм поиска соответствия. - Буду увеличивать значение минимального квантификатора .*? в подшаблоне (?:.*?$tel){$n}, авось поможет." И начинает его увеличивать и пробовать эти значения. Когда этот квантификатор захватит все до вертикальной черты в следу ющей строке:

    Тел. 2-3344. Другой тел. 3-2233, а вот еще один т|ел. 4-1122

    весь подшаблон захватит фрагмент

    , а вот еще один тел. 4-1122

    т.е. все до конца текста, и после этого проверка (?!.*?$tel) пройдет успешно. Налицо совпадение всего регулярного выражения, поэтому первый номер телефона будет заменен на 9-9999. Ошибка ясна: .*? начал поглощать символы, которые относились к номеру телефона, а он не должен был этого делать. Надо заменить его на правильный подшаблон - он должен поглощать символы до фрагмента тел., за которым идет номер.

    Когда речь шла о пропуске символов до закрывающей угловой скобки при поиске ссылки, то все было просто: [^>]*, но здесь уже не один символ, поэтому конструкция [^$tel]*, вообще говоря, не годится, у нас не просто множество символов, а часть текста. Вот практический прием пропуска символов до данного фрагмента текста:

    (?:(?!$tel).)*$tel

    Мы каждый раз в цикле проверяем, находится ли в текущей позиции фрагмент текста $tel, и если нет, то берем следующий символ точкой. А после этого цикла должен идти текст $tel. Такой цикл хотя и медлителен, но он гарантирует, что мы не проскочим мимо искомого фрагмента текста.

    Вся программа теперь выглядит так:

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


    my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122'; my $tel='тел\.\s+[\d-]+'; my $n=2; $s =~ s/(тел\.\s+)[\d-]+ # ищем слово "тел." + номер (?=(?:(?:(?!$tel).)*$tel){$n} # за которым $n раз идет "тел." + номер (?!.*?$tel) # после чего не встречается "тел." +номер )/${1}9-9999/isx; print $s;

    Она верно заменяет нужный телефонный номер, а если задано слишком большое значение для $n, которого нет, замена не производится. Обратите внимание, что в подшаблоне (?!.*?$tel) мы не стали заменять конструкцию .*? на новую, т.к. здесь она работает правильно: если впереди есть текст, соответствующий шаблону .*?$tel, то он будет найден и негативная опережающая проверка (?!.*?$tel) вернет ложь.

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

    Атомарная группировка в этом шаблоне пришлась бы кстати. Например, подшаблон [\d-]+ можно заключить в атомарные скобки, т.к. нет смысла возвращать цифры номера телефона, это не приведет к совпадению.

    Другой способ заменить n-е от конца совпадение - повторять в цикле while поиск номера телефона, взяв его в захватывающие скобки, и запоминать в массивы значения $-[1] и $+[1]. Затем отсчитать от конца массивов $n, получить смещение начала и конца нужного телефонного номера и воспользоваться функцией substr, которой можно присваивать значение.

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

    Якорь \G, его смысл и использование

    Мнимый символ \G означает позицию конца предыдущего совпадения. Это аналог функции pos, но только внутри регулярного выражения. Этот метасимвол впервые появился в Perl для проведения глобального поиска и замены. Он совпадает с позицией, в которой закончилась предыдущая итерация поиска с модификатором g. Свое значение этот якорь хранит также и после окончания работы оператора поиска/замены, как и функция pos. При первой итерации поиска/замены или после сброса текущей позиции поиска в начало текста \G совпадает в начале текста как и метасимвол \A. При принудительной переустановке текущей позиции поиска при обращении к функции pos это якорь соответствует установленной позиции. Рассмотрим примеры, которые показывают, как работает якорь \G и чем он может быть полезен.
    Мы уже рассматривали примеры поиска в скалярном контексте с модификатором g. При обращению к другому или тому же оператору с модификатором g и той же переменной с целевым текстом отыскивается следующее совпадение. Но оно ищется без привязки к концу предыдущего совпадния и может быть найдено с любой позиции после конца предыдущего совпадения. Иногда бывает нужно, чтобы следующий поиск был привязан к концу последнего совпадения и чтобы поиск заканчивался неудачей, если поиск следующего образца начинается не с этой начальной позиции. Это аналогично привязке к началу текста \A.
    Например, мы ищем слова, разделенные вертикальной чертой:
    $_='|ab|cd |ef'; while (/\|(\w+)/g) { print "$1\n" }
    Этот пример выведет
    ab cd ef
    А теперь потребуем, чтобы слова были разделены лишь одной вертикальной чертой. Вот здесь и нужна эта привязка \G к концу предыдущего совпадения:
    $_='|ab|cd |ef'; while (/\G\|(\w+)/g) { print "$1\n" }
    В результате получаем:
    ab cd
    Якорь \G надежно работает, если он стоит в самом начале регулярного выражения, которое не имеет высокоуровневой конструкции выбора альтернативы. Если такая конструкция имеется, то якорь \G надо выносить за скобки:
    /\G(?: шаблон1 | шаблон2 | … )/
    Если этот мнимый символ стоит где-либо в другом месте, то правильная его работа не гарантируется, во всяком случае, это приведет к сильному замедлению в работе регулярного выражения.
    Теперь поясню, чем же отличается конец предыдущего совпадения от начала текущего совпадения. Неужели это не одно и то же? Если совпадение было с непустным фрагментом текста, то это то же самое. Но если совпадение было с "пустотой", то, как мы уже знаем, механизм регулярных выражений принудительно передвигает текущую позицию поиска на один символ, чтобы избежать зацикливания. Рассмотрим такие примеры:
    $_='abcd'; s/z*/!/g; print $_;

    Восклицание будет вставлено в каждую позицию строки:

    !a!b!c!d!

    Теперь поставим в начало регулярного выражения якорь \G:

    $_='abcd'; s/\Gz*/!/g; print $_;

    Восклицание будет вставлено только в начало строки:

    !abcd

    После первой итерации замены текущая позиция поиска продвинется на один символ и станет равна единице, а конец предыдущего совпадения будет равен нулю (в исходной строке). Поэтому поиск во второй итерации закончится неудачей, и замены на этом прекратятся.

    Заметим еще, что если комбинируется поиск в скалярном и списковом контексте, то якорь \G хранит свое значение только после успешного поиска в скалярном контексте с модификатором g. После поиска в списковом контексте с модификатором g якорь \G сбрасывается в начало текста.

    Рассмотрим такие примеры. Сначала ищем в скалярном, а затем в списковом контексте:

    $_='abcd'; /\w/g; my @a=/\w/g; print @a;

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

    bcd

    Символ a был пройден при первом поиске.

    Теперь поменяем операторы:

    $_='abcd'; my @a=/\w/g; print "@a\n"; if (/(\w)/g) { print "Found $1" }

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

    a b c d Found a

    Это можно объяснить тем, что поиск в списковом контексте продожается до своей неудачи, а она сбрасывает позицию поиска и якорь \G в начало текста. То же самое сделает и поиск в скалярном контексте, если он будет выполняться в цикле до исчерпания совпадений:

    $_='abcd'; while (/(\w)/g) { print "$1 " } my @a=/\w/g; print "\n@a";

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

    a b c d a b c d

    Запомните, что оператор поиска/замены, который хочет искать от конца предыдущего совпадения \G, обязательно должен иметь модификатор g!

    Лексический анализ текста с помощью якоря \G и модификатора gc

    С помощью конструкции /\G шаблон /gc можно делать лексический анализ текста, производя извлечение из него нужных фрагментов один за другим, без пропусков. Это учебный пример, который показывает принцип разбора:
    my $text='123 234 345 456'; while ($text =~ /\d+/g) { print "Найдено число $&\npos=".pos($text)."\n" }
    На печати увидим:
    Найдено число 123 pos=3 Найдено число 234 pos=7 Найдено число 345 pos=11 Найдено число 456 pos=15
    Отпечатаются позиции за каждым найденным числом.
    Предположим, что мы делаем разбор текста на имена, которые состоят из латинских букв, чисел и символов перевода строк. Мы должны последовательно извлекать из текста эти объекты, называть и выводить их. Если встречается что-то, что не соотвтетствует синтаксису ни одного объекта, то это будет называться unknown (неизвестно). Регулярное выражение будет начинаться с \G и представлять собой высокоуровневую конструкцию выбора:
    $_=<[\000-\011\013-\040]+)| # все от \0 до пробела кроме \n (\b(?>[a-zA-Z]+)\b)(?{ print "Name: $^N\n" })| # имя ((?>[+-]?)(?>\d+)\b)(?{ print "Number: $^N\n" })| # число (\n)(?{ print "Newline:\n" })| # \n ((?>\S+))(?{ print "Unknown: $^N\n" }) # все остальное )/gx) {}
    Чтобы отделить первую альтернативу выбора от \G, необходимо всю конструкцию выбора взять в скобки. На печать выйдет
    Number: 123 Name: abc Newline: Number: -234 Name: def Newline: Unknown: $@ Name: xyz Newline:
    В этом примере обратите внимание на то, что ветка \S+ для unknown поставлена последней, иначе разбор получился бы некорректным.
    Не обязательно все шаблоны хранить в одной большой конструкции выбора, можно делать разбор текста по нескольким регулярным выражениям, но тогда надо использовать модификатор gc. Вот упрощенный пример программы проверки кода HTML с проверкой правильности закрытия тега A:
    #!/usr/bin/perl -w use strict; use bytes; use locale;
    $_=< <слово1 слово2 1234> Это ссылка EOF

    my $close_a=0; while (!/\G\z/gc) { if (/\G<(\w+)[^>]*>\s*/gc) { print "Тег $^N открылся\n"; if ($^N =~ /^a$/i) { print "Ошибка: вложенный тег A\n" if $close_a++; } } elsif (m!\G]*>\s*!gc) { print "Тег $^N закрылся\n"; if ($^N =~ /^a$/i) { print "Ошибка: нет парного тега A\n" if !$close_a--; } } elsif (/\G(\w+)\s*/gc) { print "Найдено слово/число $^N\n"; } elsif (/\G(&#?\w+;)\s*/gc) { print "Найдена подстановка $^N\n"; } # Пропускаем все кроме тегов, слов и подстановок elsif (/\G[^<>&\w]+/gc) { } else # Нашли ошибку, сделаем сообщение об этом { my $offset=pos($_); my ($error)=$_ =~ /\G.{1,10}/gs; die "Непонятные символы\n$error\nв позиции $offset\n"; } } print "Имеется незакрытый тег A\n" if $close_a;

    Эта программа напечатает следующее:

    Тег html открылся Тег body открылся Найдена подстановка < Найдено слово/число слово1 Найдено слово/число слово2 Найдено слово/число 1234 Найдена подстановка > Тег a открылся Найдено слово/число Это Найдено слово/число ссылка Тег a закрылся Тег body закрылся Тег html закрылся

    Предотвращение зацикливания при поиске и замене

    Версия 8 регулярных выражений системы программирования Perl, которую (версию) мы изучаем, дает очень мощные средства для поиска и замены образцов текста. Но за этой мощью кроются сложности ее применения. Сейчас мы рассмотрим один сложный аспект применения регулярных выражений Perl. Как вы уже знаете, совпадение может быть не только с фрагментом текста, но также и с позицией в тексте, а при замене, когда не было совпавшего текста, а была найдена только позиция совпадения, заменяющий текст подставляется в эту позицию. За этим кроется возможность зацикливания при поиске в цикле или с модификатором g. Рассмотрим такой пример:
    while ('abcd' =~ /z?/) { … }
    Ясно, что этот цикл будет выполняться вечно, т.к. без модификатора g не будет смещения текущей позиции поиска. Но здесь это нормально, так задумал программист.
    Однако, бесконечный цикл можно организовать также и внутри регулярного выражения, применяя квантификатор к скобкам, которые захватили пустой фрагмент текста:
    $_='abcd'; print "$`|$&$'" if /(z?)*/;
    Мы печатаем все до совпадения, за ним вертикальную черту и далее все после совпадения. Будет напечатано
    |abcd
    Модификатор * заставляет совпадать z, взятое 0 раз, еще и еще с того же самого начала текста. Чтобы предотвратить подобное зацикливание внутри системы поиска совпадения, эта система прерывает такой цикл, обусловленный повтором совпадения с нулевой длиной.
    Бесконечный цикл также можно задать во внешнем цикле выполнения поиска и замены, с помощью модификатора g:
    $_='abcd'; s/.??/<$&>/g; print $_;
    Будет напечатано:
    <><><><><>
    Это означает, что была найдена и заменена каждая буква, и кроме того, найденный фрагмент $& вставлен в каждую позицию между буквами, а также в начало и конец текста. А казалось бы, замена должна была бы выполняться бесконечно путем вставки $& в начало текста. Эта проблема решена разработчиками регулярных выражений Perl так: переменной, которая является операндом оператора поиска/замены, присваивается дополнительное состояние "предыдущее совпадение имело нулевую длину". Это состояние хранится в переменной лишь между циклами поиска, обусловленными модификатором g, и сбрасывается явным или неявным присвоением значения функции pos для этой переменной. Если в каком-то цикле под модификатором g получается совпадение нулевой длины и да нная переменная имеет установленное состояние "предыдущее совпадение было нулевой длины", то в конце такого цикла система поиска совпадения производит принудительный поиск с возвратами, пока не произойдет совпадения с непустым фрагментом текста.
    В связи с этим материалом обратите внимание еще на такой парадоксальный пример:
    print "$`|$&|$'\n" if 'abc' =~ /(a?)*/; print length $1;

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

    |a|bc 0

    Текущий фрагмент совпадения $& равен a, а в $ 1 захвачен пустой фрагмент текста. Как будто бы это ошибка, но на самом деле ошибки в этом примере нет. Вначале квантификатор ? получает значение 1, квантификатор * тоже получает значение 1.

    Происходит совпадение скобок с буквой a. Затем квантификатор * заставит подшаблон в скобках сделать повтор с позиции 1 (за буквой a), и в результате будет найдено совпадение нулевой длины для a с квантификатором ?, равным нулю. При этом переменная $1 обновится и получит пустое значение. В следующем повторе зафиксируется нулевая длина совпадения, и выполнение оператора поиска принудительно прервется. Если переписать этот пример в виде

    print "$`|$&|$'\n" if 'abc' =~ /(?:(a?)(?{print 'pos='.pos($_).", text='$1'\n"}))*/; print length $1;

    и посмотреть, что будет напечатано:

    pos=1, text='a' pos=1, text='' |a|bc 0

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

    $_=':abc:'; print "$& $1" if /(\w)+/;

    где вместо квантификатора * стоит квантификатор +. Этот пример напечатает

    abc c

    Предварительная настройка начальной позиции поиска

    Полезным свойством функции pos является то, что ей можно присваивать значение!
    Например, вы знаете, что в переменной $s совпадение будет найдено только после тысячного символа. Вы можете начать поиск именно с этого символа, присвоив
    pos($s)=1000;
    Напомню, что счет символов идет с нуля. Но оператор поиска для использования этого значения должен иметь модификатор g (или gc). Вот пример:
    $_='abcd'; pos($_)=1; /\w/g; print "$&\n"; pos($_)=1; print /\w/g;
    На печати получим:
    b bcd
    Второй раз оператор поиска был использован в списковом контексте, потому что оператор print ожидает список значений.
    Якорь \G учитывает значение, присвоенное функции pos, что видно на следующих примерах:
    $_='abcd'; pos($_)=1; /\G\w/g; print "$&\n"; pos($_)=1; print /\G\w/g;
    Результат такой же:
    b bcd
    А теперь рассмотрим более сложный пример, когда без якоря \G трудно организовать поиск. Предположим, данные представляют собой сплошную последовательность черырехзначных чисел без разделителей, и нам нужно отобрать из нее все числа, которые начинаются на 99.
    Прямая попытка
    my @a=/99\d\d/g
    ведет к неудаче, потому что после несовпадения текущая позиция поиска будет увеличена на один символ и следующие итерации поиска не будут начинаться с начала чисел. В примере
    $_='12991234'; my @a=/99\d\d/g; print @a;
    будет "найдено" число 9912.
    Попробуем вариант с минимальным квантификатором, который пропускает все четверки чисел до числа 99\d\d, а сохраняющие скобки захватывают в массив @a нужные числа:
    $_='12991234'; my @a=/(?:\d{4})*?(99\d\d)/g; print @a;
    Опять "найдено" число 9912… В этом примере при отсутствии совпадения (когда в тексте не останется нужных чисел) также применяется механизм смещения на один символ, и поиск продолжается не с границы чисел. Аналогично ведет себя негативная опережающая проверка с максимальным квантификатором:
    $_='12991234'; my @a=/(?:(?!99)\d{4})*(99\d\d)/g; print @a;
    Опять 9912… Первая пара скобок всегда заканчивается на границе числа, но при несовпадении второй пары будет смещение текущей позиции поиска, что все и портит.
    Можно было бы ко второй паре скобок поставит квантификатор ?, чтобы при несовпадении со следующим числом эти захватывающие скобки совпали с пустым фрагментом, и это не привело бы к смещению текущей позиции поиска на один символ.
    Но тогда в массиве @a появилось бы много элементов с неопределенным значением, потому что при каждой итерации поиска формировался бы очередной элемент массива независимо от того, найдено число или нет, - ведь теперь все регулярное выражение может совпасть с пустым фрагментом, а совпадение для захватывающих скобок вообще не обязательно.
    Наиболее четкое и простое решение дает использование якоря \G. Он разрешает поиск только от конца предыдущего числа, не допуская увеличения текущей позиции поиска на один символ при несовпадении:
    $_='12991234'; my @a=/\G(?:(?!99)\d{4})*(99\d\d)/g; print @a;
    В этом примере поиск закончится неудачей и возвратится пустой список. В примере
    $_='129912349934'; my @a=/\G(?:(?!99)\d{4})*(99\d\d)/g; print @a;
    напечатается число 9934.

    Запрет сброса позиции \G модификатором c

    Мы можем запретить сброс позиции \G в начало текста, если совпадение не было найдено.
    Для этого имеется модификатор c. Он не применяется без модификатора g, поэтому gc является идиомой, как бы одним модификатором gc, хотя модификаторы могут записываться в любом порядке и даже повторяться, что не влечет каких-лидо последствий (за исключением повтора модификатора e в операторе подстановки s///).
    Вот как выглядит последний пример с использованием модификатора c:
    $_='abcd'; while (/(\w)/gc) { print "$1 " } my @a=/\w/g; print "\n".scalar @a;
    Напечатается следующее:
    a b c d 0
    Мы видим, что второй поиск завершился неудачей, и размер массива @a равен нулю.
    Аналогично работает и предпоследний пример:
    $_='abcd'; my @a=/\w/gc; print "@a\n"; if (/\w/g) { print "Found $1" } else { print 'Not found' }
    Будет напечатано:
    a b c d Not found
    Если бы мы распечатали значение функции pos($_) после исчерпывающего поиска с модификатором gc, то pos указывала бы в конец строки (в нашем случае это 4). После исчерпывающего поиска с одним модификатором g, когда \G сбрасывается в начало текста, значение pos($_) становится неопределенным, как и для любой переменной, для которой не выполнялся поиск с модификатором g, или после присвоения этой переменной значения.

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

    Интерполяция кода 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. В ином случае внутри строки, ограниченной двойными кавычками, надо было бы маскировать $ и @, даже если бы они были внутри своей строки, заключенной в апострофы.

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

    Массивы интерполируются всеми своими значениями:
    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

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

    Т.к. регулярное выражение предварительно обрабатывается как строка, заключенная в кавычки (если оно не было ограничено апострофами), то интерполяция значений переменных и результата выполнения кода 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$/.

    Опять 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}$/;

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

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

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

    Интерполяция в строку простого скаляра: "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

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

    Вот пример интерполяции результата вызова подпрограммы, которая возвращает ссылку:
    #!/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 значения, будем получать разные результаты интерполяции.
    Здесь обратите внимание на разыменовывающий префикс & перед именем подпрограммы.
    Он здесь всегда обязателен. Этот способ интерполяции годится только для написанных вами подпрограмм.

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

    Если вы вставляете в регулярное выражение литерал из ввода пользователя или какую-либо переменную и хотите, чтобы переменная интерпретировалась как текст для поиска, а не как часть регулярного выражения, то для этого имеется эскейп-последовательность \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.

  • \L означает, что все буквы до эскейп-последовательности \E или до конца литерала регулярного выражения будут прописными.
  • \l означает, что следующий символ, если он буква, будет прописным.
  • \U означает, что все буквы до эскейп-последовательности \E или до конца литерала регулярного выражения будут заглавными.
  • \u означает, что следующий символ, если он буква, будет заглавным.


  • Например:

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

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

    Или

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

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

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

    $_='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";

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

    Преобразование ftp, http и e-mail ссылок в теги HTML

    Если в тексте могут присутствовать адреса электронной почты, то наша задача усложняется, поскольку ссылка через прокси-сервер
    http://www.proxy.com:80@www.site.com/
    может трактоваться неоднозначно из-за наличия в ней символа @. Если мы сначала будем искать адреса электронной почты, то программа может "найти" такой e-mail:
    80@www.site.com
    Конфликт также может возникнуть со ссылками вида
    ftp://login:passw@a-aa.com/www/
    Чтобы устранить этот конфликт, перепишем регулярные выражения для поиска URL и добавим к ним регулярное выражение для поиска e-mail. Форматировать ссылки будем несколькими операторами подстановки, т.к. для одного оператора эта задача слишком сложна.
    Вот текст всей этой программы:
    Листинг 8.3.
    (html, txt)
    А вот результат ее работы:
    Листинг 8.4.
    (html, txt)
    Как видим, в этом тестовом тексте программа правильно отделила e-mail ссылки от остальных ссылок.

    Преобразование ftp и http ссылок в теги HTML

    Возьмем такой текст:
    Зайдите на www.intuit.ru и посмотрите список курсов
    Здесь текст www.intuit.ru является ссылкой, несмотря на отсутствие протокола http://, который подразумевается по умолчанию. В итоге наше регулярное выражение должно преобразовать этот текст к такому виду:
    Зайдите на
    www.intuit.ru и посмотрите список курсов
    Может быть и так, что ссылка не отделена пробелом от окружающих слов или после нее идет знак препинания (точка, запятая и т.д.) Желательно, чтобы регулярное выражение это учитывало и не включало такой знак в ссылку. И конечно, оно не должно совпадать там, где ему совпадать не следует. Неплохо было бы, если бы оно также форматировало текст ссылки href: протокол, домен и субдомены должны быть записаны строчными буквами. А сам текст, который будет виден на странице, должен оставаться таким, каким его ввел участник форума. Задача эта непростая и не формализуется. Критерием успешности регулярного выражения является то, как оно справляется с набором тестов, которые провоцируют его к несовпадению или совпадению не в тех местах.
    Это регулярное выражение достаточно сложное и громоздкое, и мы будем создавать его по частям. Начнем с протокола.
    Протокол может быть http, https и ftp. Для его обнаружения создадим строковую переменную $protocol:
    my $protocol='(?:(?=[FfHh])(?i:http(?>s?)|ftp)://)';
    Если в тексте следующий символ F, f, H или h, то этот подшаблон делает проверку следующих за ним символов и, если это протокол, поглощает его вместе с префиксом. Я взял весь шаблон для протокола в скобки, потому что в общем регулярном выражении у этого подшаблона может стоять квантификатор, который должен относиться ко всему этому подшаблону, а не к последнему его символу /.
    Результирующий оператор подстановки у нас будет иметь модификатор x, поэтому для имени хоста запишем такое регулярное выражение в свободном формате:
    my $host=<[A-Za-z0-9]{1,63}\\.) (?>[A-Za-z0-9] (?>[-A-Za-z0-9]{0,62})\\. )* HOST

    После имени хоста через двоеточие может идти порт:

    my $port="(?::\\d{1,5}$wb)";

    А после зоны может идти хвост, который содержит множество всяких параметров, передаваемых с URL:

    my $tail=<[^.,"'<>()[\\]{}\\s\\x7F-\\xFF]*) (?:(?>[.,?]+) (?:[^"'<>()[\\]{}\\s\\x7F-\\xFF]+) )* (?
    В конце стоит заглядывание назад

    (?
    которое учитывает, что после URL могут стоять знаки препинания, которые в него не входят.

    Все регулярное выражение для поиска URL в тексте выглядит немного страшновато:

    my $re=<($protocol)(?(2)(?>$host$zone)|$host$zone) (?![A-Za-z0-9])| (?(?>$port?(?>\\\@$host$zone(?![A-Za-z0-9_.-]*\\\@))?)?) ) ($tail?) RE

    Это выражение учитывает заход через прокси-сервер вида

    http://proxy.com@site.com/

    Часть URL от начала до хвоста, который может идти после символов / или ?, мы берем в нумерованную переменную $1. Эту часть URL внутри тега
    Внутри переменной $re встречается эскейп-последовательности \@. Но т.к. внутри текста here doc \ и @ являются метасимволами, то, чтобы в результате получить последовательность \@, надо написать \\\@. Тогда при обработке такого текста \\ превратится в \, а \@ превратится в @, и в конце получится нужная последовательность \@. Для проверки напечатайте переменную $re.

    Программа должна "подсвечивать" ссылки в тексте. Например, имеем текст

    Look at:aaa.Museum.

    Это должно превратиться в

    Look at:
    aaa.Museum.

    Здесь возможный URL отделяется от прилипших справа и слева символов и оформляется в тег
    Возьмем в качестве тестового такой текст:

    my $text=<

    my $re=<($protocol)(?(2)(?>$host$zone)|$host$zone) (?![A-Za-z0-9])| (?(?>$port?(?>\\\@$host$zone(?![A-Za-z0-9_.-]*\\\@))?)?) ) ($tail?) RE

    my $text=<
    NOT URLs: aaa.museumm http://aaa.museumm, http://-aaa.com www._aaa.com www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com TEXT

    $text =~ s!$re!$1$3!gx; print $text;

    Листинг 8.1.

    А вот текст, который она печатает:

    URLs: Ftp://a.com/AAa Look at:aaa.Museum. http://www.proxy.com:80@www.site.com/ http://proxy.com:80@site.com/ http://proxy.com@site.com/ aAaa.com.au.rr.ggg Zwww.Yabcd.co.uk Фforum.abcd.de www.Abc.eu П123.123.123.1234.com/?q=aaa http://Abc.Tk Ahttp://www.Abc.pt/AAa http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.old-avto.tk

    NOT URLs: aaa.museumm http://aaa.museumm, http://-aaa.com www._aaa.com www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com

    Листинг 8.2.

    3-я, 10-я и 13-я строка не уместились по ширине страницы.

    Обратите внимание, как преобразуется в URL строка

    aAaa.com.au.rr.ggg

    Получается

    aAaa.com.au.rr.ggg

    .ggg не считается частью URL. Количество последовательностей символов через точку ограничено, чтобы не захватить в URL следующий за ним текст. Это интуитивное ограничение.

    w use

    #!/usr/bin/perl - w use strict;
    my $protocol='(?:(?=[FfHh])(?i:http(?>s?)|ftp)://)';
    my $host=<[A-Za-z0-9]{1,63}\\.) (?>[A-Za-z0-9] (?>[-A-Za-z0-9]{0,62})\\. )* HOST
    my $subdom=<[A-Za-z0-9] (?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])? )\\. )+ SUBDOM
    my $wb='(?![A-Za-z0-9])';
    my $zone=<com|net|org|edu|biz|gov|int|mil)| (?(?=[a-z]{2}$wb)[a-z]{2}| (?(?=[a-z]{4}$wb)(?>info|aero|name)| (?(?=[a-z]{6}$wb)museum|(?!) ) ) ) ) (?>\\.[a-z]{2}$wb)? ) ZONE
    my $port="(?::\\d{1,5}$wb)";
    my $tail=<[^.,"'<>()[\\]{}\\s\\x7F-\\xFF]*) (?:(?>[.,?]+) (?:[^"'<>()[\\]{}\\s\\x7F-\\xFF]+) )* (? my $re=<($protocol)(?(2)(?>$host$zone)|$host$zone) (?![A-Za-z0-9])| (?(?>$port?(?>\\\@$host$zone(?![A-Za-z0-9_.-]*\\\@))?)?) ) ($tail?) RE
    my $text=< NOT URLs: aaa.museumm http://aaa.museumm, http://-aaa.com www._aaa.com www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com TEXT
    $text =~ s!$re!$1$3!gx; print $text;
    Листинг 8.1.
    Закрыть окно

    #!/usr/bin/perl -w
    use strict;
    my $protocol='(?:(?=[FfHh])(?i:http(?>s?)|ftp)://)';
    my $host=<
    (?>[A-Za-z0-9]{1,63}\\.)
    (?>[A-Za-z0-9]
    (?>[-A-Za-z0-9]{0,62})\\.
    )*
    HOST
    my $subdom=<
    (?:
    (?>[A-Za-z0-9]
    (?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?
    )\\.
    )+
    SUBDOM
    my $wb='(?![A-Za-z0-9])';
    my $zone=<
    (?i:(?(?=[a-z]{3}$wb)(?>com|net|org|edu|biz|gov|int|mil)|
    (?(?=[a-z]{2}$wb)[a-z]{2}|
    (?(?=[a-z]{4}$wb)(?>info|aero|name)|
    (?(?=[a-z]{6}$wb)museum|(?!)
    )
    )
    )
    )
    (?>\\.[a-z]{2}$wb)?
    )
    ZONE
    my $port="(?::\\d{1,5}$wb)";
    my $tail=<
    (?:[/?]
    (?>[^.,"'<>()[\\]{}\\s\\x7F-\\xFF]*)
    (?:(?>[.,?]+)
    (?:[^"'<>()[\\]{}\\s\\x7F-\\xFF]+)
    )*
    (? )
    TAIL
    my $re=<
    (
    (?>($protocol)(?(2)(?>$host$zone)|$host$zone)
    (?![A-Za-z0-9])|
    (? (? $subdom$zone(?![A-Za-z0-9_.-]*\\\@)
    )
    (?>(?>$port?(?>\\\@$host$zone(?![A-Za-z0-9_.-]*\\\@))?)?)
    )
    ($tail?)
    RE
    my $text=<
    URLs:
    Ftp://a.com/AAa
    Look at:aaa.Museum.
    http://www.proxy.com:80\@www.site.com/
    http://proxy.com:80\@site.com/
    http://proxy.com\@site.com/
    aAaa.com.au.rr.ggg
    Zwww.Yabcd.co.uk
    Фforum.abcd.de
    www.Abc.eu
    П123.123.123.1234.com/?q=aaa
    http://Abc.Tk
    Ahttp://www.Abc.pt/AAa
    http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image
    Ф.Www.old-avto.tk
    NOT URLs:
    aaa.museumm
    http://aaa.museumm,
    http://-aaa.com
    www._aaa.com
    www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com
    TEXT
    $text =~ s!$re!$1$3!gx;
    print $text;

    NOT URLs:

    URLs: Ftp://a.com/AAa Look at:aaa.Museum. http://www.proxy.com:80@www.site.com/ http://proxy.com:80@site.com/ http://proxy.com@site.com/ aAaa.com.au.rr.ggg Zwww.Yabcd.co.uk Фforum.abcd.de www.Abc.eu П123.123.123.1234.com/?q=aaa http://Abc.Tk Ahttp://www.Abc.pt/AAa http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.old-avto.tk
    NOT URLs: aaa.museumm http://aaa.museumm, http://-aaa.com www._aaa.com www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com
    Листинг 8.2.
    Закрыть окно

    URLs:
    Ftp://a.com/AAa
    Look at:aaa.Museum.
    http://www.proxy.com:80@www.site.com/
    http://proxy.com:80@site.com/
    http://proxy.com@site.com/
    aAaa.com.au.rr.ggg
    Zwww.Yabcd.co.uk
    Фforum.abcd.de
    www.Abc.eu
    П123.123.123.1234.com/?q=aaa
    http://Abc.Tk
    Ahttp://www.Abc.pt/AAa
    http://abc.au/query/vid.cam.dig/sony.dcrhc15.htm#full_image
    Ф.Www.old-avto.tk
    NOT URLs:
    aaa.museumm
    http://aaa.museumm,
    http://-aaa.com
    www._aaa.com
    www.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.com

    и passw ограничены 32 символами

    #!perl -w use strict;
    my $wb='(?![A-Za-z0-9])';
    my $protocol='(?:(?=[FfHh])(?i:http(?>s?)|ftp)://)';
    my $host=<[-A-Za-z0-9_]{1,63}\\.) (?>[A-Za-z0-9_] (?>[-A-Za-z0-9_]{0,62})\\. )* HOST
    my $subdom=<[A-Za-z0-9] (?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])? )\\. )+ SUBDOM
    my $subdom1='[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?';
    my $zone=<com|net|org|edu|biz|gov|int|mil)| (?(?=[a-z]{2}$wb)[a-z]{2}| (?(?=[a-z]{4}$wb)(?>info|aero|name)| (?(?=[a-z]{6}$wb)museum|(?!) ) ) ) (?>\\.[a-z]{2}$wb)? ) ZONE
    my $port="(?::\\d{1,5}$wb)";
    my $tail=<[^.,"'<>()\\[\\]{}\\s\\x7F-\\xFF]*) (?: (?>[.,?]+) (?:[^"'<>()\\[\\]{}\\s\\x7F-\\xFF]+) )* (? my $firstchr='(?:[A-Za-z0-9])';
    my $namechr='(?:[A-Za-z0-9_+.-])';
    my $ip='(?:(?\\d{1,3})\\.(?>\\d{1,3})\\.(?>\\d{1,3})\\.(?>\\d{1,3})(?!\\d))';
    # Login и passw ограничены 32 символами my $loginpasswat='(?:(?>[A-Za-z0-9_]{1,32})(?>(?::[A-Za-z0-9_]{1,32})?)\\@)';
    my $res;
    $_=q(http://www.proxy.com:80@www.site.com/ Ftp://a.com/AAa Ftp://Login:Passw@Www.Aaa.Com/Www/ Ftp://login:passw@a-aa.com/www/ Mailto:aaa@sss.zzz.co. Mailto:aaa@sss.zzz.eee.co. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com ыы@ddd.com ыы@ddЫd.com ыыsы-sf.ff.com.com@ddd.com ыыsы.-sf.ff@ddd.com Mailto:aaa@sss.co, aaa@sss.comЫЫЫ aaa.Bb.b@aaaa.com.ru.rr.ggg aaa.museumm Look at:aaa.museum. httpS://aaa.museumm, http://www.proxy.com:80@www.site.com/ http://proxy.com:80@site.com/ http://proxy.com@site.com/ aAaa.com.ru.rr.ggg Zwww.Yabcd.co.uk Фforum.abcde.ru www.Eabcd.ru http://Eabcd.Ru Ahttp://www.Eabcd.ru/AAa http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.abcdefg-avto.ru httP://1.2.3.400/aaa/ddd.exe? 1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h, .0.2.3.400. http://66.123.234.555/ddd michel@ab-cdefg.ru http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a );
    # Оформляем ссылки без login:passw s#((?>($protocol)(?(2)(?>$ip|$host$zone)|$host$zone)(?![A-Za-z0-9])|(?(?>$port?(?>\@$host$zone(?![A-Za-z0-9_.-]*\@))?)?))($tail?)#$res=$2 ? '' : 'http://'; qq!$1$3!#gex; # Оформляем ссылки с login:passw s#($protocol)($loginpasswat)($ip|$host$zone)((?>$port?)$tail?)#$1$2$3$4"#gx; # Оформляем е-мейлы. Этот оператор чувствителен к тексту, на который меняет предыдущие операторы! s#((?$namechr{0,39})\@(?>$subdom1)(?:\.$subdom1)?\.$zone)(?!(?>[^\s"<]*)(?:"\starget="_blank">|))#$1#gx; # Оформляем ссылки с IP s#((?/])$ip(?>$port?))($tail?)#"$1$2"#gx;
    print $_;
    Листинг 8.3.
    Закрыть окно

    #!perl -w
    use strict;
    my $wb='(?![A-Za-z0-9])';
    my $protocol='(?:(?=[FfHh])(?i:http(?>s?)|ftp)://)';
    my $host=<
    (?>[-A-Za-z0-9_]{1,63}\\.)
    (?>[A-Za-z0-9_]
    (?>[-A-Za-z0-9_]{0,62})\\.
    )*
    HOST
    my $subdom=<
    (?:
    (?>[A-Za-z0-9]
    (?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?
    )\\.
    )+
    SUBDOM
    my $subdom1='[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?';
    my $zone=<
    (?i:
    (?=[a-z]{3}$wb)
    (?>com|net|org|edu|biz|gov|int|mil)|
    (?(?=[a-z]{2}$wb)[a-z]{2}|
    (?(?=[a-z]{4}$wb)(?>info|aero|name)|
    (?(?=[a-z]{6}$wb)museum|(?!)
    )
    )
    )
    (?>\\.[a-z]{2}$wb)?
    )
    ZONE
    my $port="(?::\\d{1,5}$wb)";
    my $tail=<
    (?:[/?]
    (?>[^.,"'<>()\\[\\]{}\\s\\x7F-\\xFF]*)
    (?:
    (?>[.,?]+)
    (?:[^"'<>()\\[\\]{}\\s\\x7F-\\xFF]+)
    )*
    (? )
    TAIL
    my $firstchr='(?:[A-Za-z0-9])';
    my $namechr='(?:[A-Za-z0-9_+.-])';
    my $ip='(?:(?\\d{1,3})\\.(?>\\d{1,3})\\.(?>\\d{1,3})\\.(?>\\d{1,3})(?!\\d))';
    # Login и passw ограничены 32 символами
    my $loginpasswat='(?:(?>[A-Za-z0-9_]{1,32})(?>(?::[A-Za-z0-9_]{1,32})?)\\@)';
    my $res;
    $_=q(http://www.proxy.com:80@www.site.com/
    Ftp://a.com/AAa
    Ftp://Login:Passw@Www.Aaa.Com/Www/
    Ftp://login:passw@a-aa.com/www/
    Mailto:aaa@sss.zzz.co.
    Mailto:aaa@sss.zzz.eee.co.
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com
    ыы@ddd.com
    ыы@ddЫd.com
    ыыsы-sf.ff.com.com@ddd.com
    ыыsы.-sf.ff@ddd.com
    Mailto:aaa@sss.co,
    aaa@sss.comЫЫЫ
    aaa.Bb.b@aaaa.com.ru.rr.ggg
    aaa.museumm
    Look at:aaa.museum.
    httpS://aaa.museumm,
    http://www.proxy.com:80@www.site.com/
    http://proxy.com:80@site.com/
    http://proxy.com@site.com/
    aAaa.com.ru.rr.ggg
    Zwww.Yabcd.co.uk
    Фforum.abcde.ru
    www.Eabcd.ru
    http://Eabcd.Ru
    Ahttp://www.Eabcd.ru/AAa
    http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image
    Ф.Www.abcdefg-avto.ru
    httP://1.2.3.400/aaa/ddd.exe?
    1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h,
    .0.2.3.400.
    http://66.123.234.555/ddd
    michel@ab-cdefg.ru
    http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a
    );
    # Оформляем ссылки без login:passw
    s#((?>($protocol)(?(2)(?>$ip|$host$zone)|$host$zone)(?![A-Za-z0-9])|(?(?>$port?(?>\@$host$zone(?![A-Za-z0-9_.-]*\@))?)?))($tail?)#$res=$2 ? '' : 'http://'; qq!$1$3!#gex;
    # Оформляем ссылки с login:passw
    s#($protocol)($loginpasswat)($ip|$host$zone)((?>$port?)$tail?)#$1$2$3$4"#gx;
    # Оформляем е-мейлы. Этот оператор чувствителен к тексту, на который меняет предыдущие операторы!
    s#((?$namechr{0,39})\@(?>$subdom1)(?:\.$subdom1)?\.$zone)(?!(?>[^\s"<]*)(?:"\starget="_blank">|))#$1#gx;
    # Оформляем ссылки с IP
    s#((?/])$ip(?>$port?))($tail?)#"$1$2"#gx;
    print $_;

    museumm Look

    http://www.proxy.com:80@www.site.com/ Ftp://a.com/AAa Ftp://Login:Passw@Www.Aaa.Com/Www/" Ftp://login:passw@a-aa.com/www/" Mailto:aaa@sss.zzz.co. Mailto:aaa@sss.zzz.eee.co. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com ыы@ddd.com ыы@ddЫd.com ыыsы-sf.ff.com.com@ddd.com ыыsы.-sf.ff@ddd.com Mailto:aaa@sss.co, aaa@sss.comЫЫЫ aaa.Bb.b@aaaa.com.ru.rr.ggg aaa. museumm Look at:aaa.museum. httpS://aaa.museumm, http://www.proxy.com:80@www.site.com/ http://proxy.com:80@site.com/ http://proxy.com@site.com/ aAaa.com.ru.rr.ggg Zwww.Yabcd.co.uk Фforum.abcde.ru www.Eabcd.ru http://Eabcd.Ru Ahttp://www.Eabcd.ru/AAa http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.abcdefg-avto.ru httP://1.2.3.400/aaa/ddd.exe? "1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h", ."0.2.3.400". http://66.123.234.555/ddd michel@ab-cdefg.ru http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a
    Листинг 8.4.
    Закрыть окно

    http://www.proxy.com:80@www.site.com/
    Ftp://a.com/AAa
    Ftp://Login:Passw@Www.Aaa.Com/Www/"
    Ftp://login:passw@a-aa.com/www/"
    Mailto:aaa@sss.zzz.co.
    Mailto:aaa@sss.zzz.eee.co.
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com
    ыы@ddd.com
    ыы@ddЫd.com
    ыыsы-sf.ff.com.com@ddd.com
    ыыsы.-sf.ff@ddd.com
    Mailto:aaa@sss.co,
    aaa@sss.comЫЫЫ
    aaa.Bb.b@aaaa.com.ru.rr.ggg
    aaa.museumm
    Look at:aaa.museum.
    httpS://aaa.museumm,
    http://www.proxy.com:80@www.site.com/
    http://proxy.com:80@site.com/
    http://proxy.com@site.com/
    aAaa.com.ru.rr.ggg
    Zwww.Yabcd.co.uk
    Фforum.abcde.ru
    www.Eabcd.ru
    http://Eabcd.Ru
    Ahttp://www.Eabcd.ru/AAa
    http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image
    Ф.Www.abcdefg-avto.ru
    httP://1.2.3.400/aaa/ddd.exe?
    "1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h",
    ."0.2.3.400".
    http://66.123.234.555/ddd
    michel@ab-cdefg.ru
    http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a

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

    Встроенный код и директивы my и local

    Во всех рассмотренных примерах результаты оказывались правильными только благодаря счастливой случайности: в наших регулярных выражениях либо не было возврата, либо возвраты за исполняемый код не приводили к сбоям в работе переменных my.
    Выполненный код в результате возврата не отменишь, но нам и не надо было его отменять, как и результатов присвоенных в нем значений переменным.
    Для корректной работы переменных внутри встроенного кода их надо локализовать внутри регулярного выражения. Это делается директивой local. Значения таких переменных при возврате за встроенный код, в котором им присваивались значения, восстанавливаются такими, какими были до выполнения этого кода. Такие переменные должны быть глобальными, т.е. созданными не директивой my. Если в программе используется директива use strict, то глобальную переменную можно создать с помощью ключевого слова our. Тогда директива local внутри регулярного выражения делает из этой переменной как бы стек ее значений: при присваивании ей значения во встроенном коде оно наслаивается поверх предыдущего значения этой переменной, а при возврате назад восстанавливается предыдущее значение. После конца работы регулярного выражения эта переменная восстанавливает свое значение, которое имела перед входом в регулярное выражение.
    Рассмотрим такой пример:
    #!/usr/bin/perl -w use strict;
    $_='ab'; our $o=1; my $m=1; / (?: a (?{ $o++; $m++ }) | ab (?{ print "\$o=$o, \$m=$m" }) ) $ /x;
    Регулярное выражение содержит конструкцию выбора:
    /(a|ab)$/
    Вначале будет найден символ a и выберется первая ветка условного шаблона, при этом переменные $o и $m увеличатся на единицу. Но затем этот выбор будет отменен, поскольку за символом a должен идти символ b, и управление получит вторая альтернатива конструкции выбора, в которой будут распечатаны значения переменных $o и $m. На печать выйдет:
    $o=2, $m=2
    В этом примере различия в работе этих переменных нет. Теперь локализуем глобальную переменную $o в регулярном выражении:
    #!/usr/bin/perl -w use strict;

    $_='ab'; our $o=1; my $m=1; / (?{ local $o }) (?: a (?{ $o++; $m++ })| ab (?{ print "\$o=$o, \$m=$m" }) ) $ /x;

    На сей раз напечатается это:

    $o=1, $m=2

    Этот пример показывает работу директивы local во встроенном коде.

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

    Если объявить переменную my внутри регулярного выражения, то в других блоках встроенного кода переменная с этим именем не будет соответствовать той переменной, что была объявлена. Она либо будет создана заново как глобальная, либо, если переменная с этим именем уже существует до регулярного выражения, она будет отождествлена с ней. При возврате такая переменная не будет восстанавливать свои старые значения. Вот два примера:

    $_='ab'; our $o=1; / (?{ my $m=10; local $o }) (?: a (?{ $o++; $m++ })| ab (?{ print "\$o=$o, \$m=$m" }) ) $ /x;

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

    $o=1, $m=1

    Мы видим, что во втором встроенном коде переменная $m создалась заново с неопределенным значением, затем к нему применили ++ и получили 1. И это значение потом использовалось при печати.

    $_='ab'; our $o=1; my $m=10; / (?{ my $m=5; local $o }) (?: a (?{ $o++; $m++ })| ab (?{ print "\$o=$o, \$m=$m" }) ) $ /x;

    Здесь напечатается

    $o=1, $m=11

    Во втором и третьем встроенном коде использовалась переменная $m, которая была создана до регулярного выражения.

    Директиву local можно комбинировать с присваиванием значения этой переменной.

    Например:

    local ($ctop) = $ctop+1;

    Встроенный код и интеллектуализация поиска

    Рассмотрим пример, когда встроенный код помогает сделать поиск более интеллектуальным. Путь нам надо найти в тексте самое большое натуральное число. При поиске мы используем цикл while и модификатор g. Результат будем запоминать в переменной $n, которая вначале будет иметь неопределенное значение. Во встроенном коде мы проверяем, имеет ли переменная $n определенное значение или $n меньше очередного найденного числа. Если это так, то мы присваиваем $n новое значение. В результате $n должна хранить первое попавшееся максимальное число.
    my $n; $_='20 0 36 35'; while (/(\d+)(?{$n=$+ if !defined $n || $n < $+})/g) {} print "Наибольшее число - это $n" if defined $n;
    Напечатается
    Наибольшее число - это 36
    Этот пример можно упростить, исключив из него цикл while и модификатор g. Для этого в конец регулярного выражения добавим подшаблон (?!), который ни с чем не совпадает. Это будет заставлять механизм поиска соответствия делать возвраты при переборе, а когда возвраты исчерпаются, - продвигать начальную позицию поиска на один символ и повторять поиск.
    my $n; $_='20 0 36 35'; /(\d+)(?{$n=$+ if !defined $n || $n < $+})(?!)/; print "Наибольшее число - это $n" if defined $n;
    Опять напечатается, что
    Наибольшее число - это 36
    Относительно этого красивого примера хочу сделать такое замечание: если вы распечатаете начальные позиции поиска, то обнаружите, что благодаря работе условной конструкции (?!) поиск стартует, начиная с каждой цифры, т.е. проверяются также "числа" 6 и 5. И только по счастливой случайности это не привело к ошибке в результате. Вообще говоря, перед подшаблоном (\d+) надо поставить условие, что слева нет цифры, тогда поиск будет начинаться только с начала чисел:
    my $n; $_='20 0 36 35'; /(? Обратите внимание, что поиск при использовании подшаблона (?!), если он не стоит в альтернативной конструкции и условном операторе, всегда заканчивается неудачей, а такой оператор поиска используется только ради побочных эффектов (установки нумерованных переменных), поэтому неправильно вставлять подобный оператор в заголовок цикла while и в условие оператора if.
    А сейчас распространим этот пример на отрицательные числа. Надо учитывать знак минус перед числом. С циклом while и модификатором g все работает так же, как и раньше:
    my ($n,$tmp); $_='-200 0 36 35'; while( /(-)? # берем минус в $1, если он есть (\d+) # берем число в $2 (?{ $tmp=$1 ? -$2 : $2; # в $tmp получаем число с учетом знака $n=$tmp if !defined $n || $n < $tmp; }) /gx) {}; print "Наибольшее число - это $n" if defined $n;

    Для удобства я применил модификатор x и комментарии. Если переменная $1 определена, то мы в тернарном условном операторе учитываем, что число отрицательное; если $1 имеет неопределенное значение, то берем число из $2 таким, как есть. В операторе

    $n=$tmp if !defined($n) || $n < $tmp;

    мы не можем аналогично написать

    $n=$tmp if !$n || $n < $tmp;

    потому что значения $n==0 и $n==undefined будут неразличимы.

    В итоге на печать выходит строка

    Наибольшее число - это 36

    В более красивом случае нужно позаботиться о том, чтобы поиск не начинался сразу после знака минус и чтобы перед подшаблоном (\d+) не было цифры. И все число неплохо заключить в атомарные скобки, т.к. число должно состоять из всех встретившихся подряд цифр и знака минус, если он был.

    my ($n,$tmp); $_='-200 0 36 35'; /(? # атомарная группировка для всего числа (-)? # берем минус в $1, если он есть (?
    И опять на печать выходит, что

    Наибольшее число - это 36

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

    Мы уже имели возможность убедиться в полезности встроенного кода при выводе текущей позиции поиска и содержимого специальных переменных, изменяемых при поиске. Встроенный код также необходим во время отладки и ускорения работы регулярного выражения. Часто программист не подозревает, сколько лишней работы производят его конструкции внутри регулярного выражения.
    Для примера рассмотрим программу, которая ищет и печатает даты в формате Jan 13 2007, Apr 5 2007 и т.д. Вот такая программа, которая сразу приходит в голову:
    $_='Дата1 Jan 13 2007, дата2 Apr 5 2007'; while (/(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(?:\s+\d+){2}/g) { print "Нашла дату $&\n"; }
    Программа выдает:
    Нашла дату Jan 13 2007 Нашла дату Apr 5 2007
    Но задумаемся, как она производит поиск. Чтобы найти месяц, наша программа к каждому символу исходного текста применяет конструкцию выбора со всеми 12-ю месяцами. И как правило, безуспешно, а это значит, что все 12 имен месяцев сравниваются с каждым символом исходной строки, кроме двух. На нашем небольшом примере не заметно, что программа тратит на работу слишком много времени, но если это регулярное выражение будет обрабатывать десятки мегабайтов текстов, это станет видно.
    Для любопытства вставим распечатку текущей позиции поиска в начало регулярного выражения:
    $_='Дата1 Jan 13 2007, дата2 Apr 5 2007'; while (/(?{print pos($_).' '})(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) (?:\s+\d+){2}/g) { print "\nНашла дату $&\n"; }
    Получим на выходе
    0 1 2 3 4 5 6 Нашла дату Jan 13 2007 17 18 19 20 21 22 23 24 25 Нашла дату Apr 5 2007
    Вот столько раз (и как правило, безуспешно) эта программа применяет затратную конструкцию выбора.
    Сделаем оптимизацию по начальному символу месяца - соберем все начальные символы в класс:
    $_='Дата1 Jan 13 2007, дата2 Apr 5 2007'; while (/(?=[ADFJMNOS])(?{print pos($_).' '}) (?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)(?:\s+\d+){2}/g) { print "\nНашла дату $&\n"; }
    (В этом тексте длинная строка могла быть перенесена на символе пробела.)
    Теперь альтернативный подшаблон будет работать, только если в тексте встретится один из символов ADFJMNOS. Класс по-прежнему будет применяться к каждому очередному символу, но классы работают гораздо быстрее альтернатив. В итоге программа напечатает
    6 Нашла дату Jan 13 2007 25 Нашла дату Apr 5 2007
    Отсюда мы видим, что теперь альтернативный подшаблон применяется два раза вместо 16-ти. Встроенный код помог нам провести оптимизацию регулярного выражения.

    Встроенный код и поиск вложенных конструкций

    В те времена, когда в арсенале регулярных выражений еще не было встроенного кода (и динамических регулярных выражений), поиск вложенных конструкций произвольного уровня вложенности был невозможен. Рассмотим вариант проверки, содержит ли текст правильно закрытые конструкции из круглых скобок. Например, строка
    2*(3+2*(5-1)-2)+12
    содержит конструкцию из правильно закрытых скобок, а строки
    ( ) ) ( )
    и
    ( ( ) ( )
    содержат неправильно сбалансированные круглые скобки.
    Для составления такого регулярного выражения надо завести счетчик открывающих скобок, который вначале будет содержать 0, при встрече открывающей скобки будет увеличиваться на 1, а при встрече закрывающей скобки вначале будет проводиться проверка этого счетчика на 0. Если встретилась закрывающая скобка и счетчик содержит 0, то это будет говорить о нарушении баланса скобок. Иначе мы вычтем из содержимого счетчика 1. А в конце текста надо проверить, имеет ли счетчик значение 0, и если нет, то это опять ошибка.
    Схема регулярного выражения будет такая:
    ^ # поиск от начала текста (?> # поиск без возвратов (?: (?> [^()]+ ) # все кроме круглых скобок без возврата | \( # или открывающая круглая скобка | \) # или закрывающая круглая скобка )* # сколько угодно раз ) $ # поиск до конца текста
    Вначале счетчик $ctop (count of open parens) содержит 0. При встрече открывающей скобки выполняем код
    (?{ ++$ctop })
    При встрече закрывающей скобки выполняем условный оператор с кодом Perl в условии:
    (?(?{ $ctop }) (?{ --$ctop }) | (?!) )
    А если к этому моменту $ctop равен нулю, то подставляем шаблон (?!), который приведет к несовпадению для всего регулярного выражения.
    В конец регулярного выражения подставим код
    (?(?{ $ctop }) (?!) )
    который тоже приведет к несовпадению для всего шаблона, если счетчик $ctop не будет равен нулю.
    Теперь вся программа:
    $_='( () ) ( ) (()())'; my $ctop=0; if (/ ^ (?> (?: (?> [^()]+ ) | \( (?{ ++$ctop }) | \) (?(?{ $ctop }) (?{ --$ctop }) | (?!) ) )* ) (?(?{ $ctop }) (?!) ) $ /x ) { print 'Match' } else { print 'Not match' }

    При этих данных наша программа выводит Match, но если нарушить баланс скобок, то будет выведено Not match.

    В этом примере скобки представлялись одним символом, но они могут быть и многосимвольными. Например, мы проверяем правильность вложенности тегов table. В этом случае подшаблон (?> [^()]+ ) нужно заменить на другую конструкцию, т.к. многосимвольные скобки нельзя втиснуть в класс символов. Вместо этого подшаблона используется такая конструкция:

    (?> (?: (?!
    Эта конструкция проверяет, находится ли в текущей позиции фрагмент
    После этой вставки поменяем ограничители регулярного выражения и добавим модификаторы is, чтобы был поиск без учета регистра и точка соответствовала также символу перевода строки. Получим такую программу:

    $_=<
    EOF

    my $ctop=0; if (m% ^ (?> (?: (?> (?: (?!
    С данными в переменной $_ будет напечатано Match.

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

    Объекты регулярных выражений и квантификаторы

    Как вы помните, при поиске и подсветке ссылок в тексте нам приходилось заключать в скобки регулярные выражения, которые находились в переменных, чтобы квантификаторы в общем регулярном выражении относились ко всему вставляемому подшаблону, а не к последнему его символу. В случае использования объектов регулярных выражений такой проблемы нет, они ведут себя так, как если бы были заключены в незахватывающие скобки:
    $_='abab'; my $re=qr/ab/; print $& if /$re+/;
    Напечатается abab. В случае с интерполяцией текста в регулярное выражение:
    $_='abab'; my $re='ab'; print $& if /$re+/;
    напечатается только ab, т.е. плюс относится лишь к последнему символу b.

    Ограничители в операторе qr/…/

    В операторе qr/…/ в качестве ограничителей регулярного выражения не обязательно применять слэши, можно применять все те же символы, что и в операторе поиска m/…/. Но не применяйте в качестве ограничителей вопросительные знаки из-за их специального значения. Когда ограничителями выступают апострофы, при трансляции этого регулярного выражения не будет интерполяции переменных, если переменные будут там присутствовать. Вот пример с интерполяцией переменной:
    my $a='ab'; $_='ab'; my $re=qr"^$a$"; print "\$re=$re\n"; print $& if /$re/;
    Напечатается
    $re=(?-xism:^ab$) ab
    Теперь сделаем ограничителями апострофы и попробуем поискать строку '$a':
    my $a='ab'; $_='$a'; my $re=qr'^$a$'; print "\$re=$re\n"; print $& if /$re/;
    В результате напечатается
    $re=(?-xism:^$a$)
    Мы видим, что поиск закончился неудачей. Оказывается, символ $ перед именем переменной в операторе qr'…' все равно надо маскировать:
    my $a='ab'; $_='$a'; my $re=qr'^\$a$'; print "\$re=$re\n"; print $& if /$re/;
    Напечатается
    $re=(?-xism:^\$a$) $a
    То же касается текстов в интерполированных переменных: если после символа $ (и наверно, после символа @) стоят символы, при которых возможна интерпретация этих символов как имени переменной, то $ и @ должны быть замаскированы обратным слэшем. Символы внутри регулярных выражений, которые должны представлять сами себя, а не быть метасимволами, надо так же маскировать внутри оператора qr'…', как и в операторе qr/…/ со всеми остальными типами ограничителей. Это же касается и интерполируемых в регулярное выражение переменных (что более понятно). Вот пример: пытаемся найти символ |:
    my $a='ab'; $_='|'; my $re=qr'^|$'; print "\$re=$re\n"; print "Found $&" if /$re/;
    Будет напечатано
    $re=(?-xism:^|$) Found
    В этом примере регулярное выражение соответствует пустому фрагменту, т.к. оно ищет не символ |, а представляет из себя конструкцию выбора с двумя пустыми альтернативами. Если замаскировать символ | в операторе qr'…', то получим нужный результат:
    my $a='ab'; $_='|'; my $re=qr'^\|$'; print "\$re=$re\n"; print "Found $&" if /$re/;
    Напечатается
    $re=(?-xism:^|$) Found |

    Оператор qr/…/ и интерполяция переменных

    Если интерполируемые переменные, которые используются в операторе qr/…/, после трансляции им регулярного выражения изменят свое значение, то это не отразится на объекте регулярного выражения, даже если оператор qr/…/ не имеет модификатора o.
    Рассмотрим такие примеры:
    my $a=$_='ab'; my $re=qr"$a"; print "Found $&\n" if /$re/; $_=$a='cd'; print "Found $&\n" if /$re/;
    Будет напечатано
    Found ab
    Во второй раз поиск закончился неудачей. Но если опять присвоить переменной $re значение qr"$a", то будет найдено и cd:
    my $a=$_='ab'; my $re=qr"$a"; print "Found $&\n" if /$re/; $_=$a='cd'; $re=qr"$a"; print "Found $&\n" if /$re/;
    Напечатается
    Found ab Found cd
    Если распечатать содержимое переменной, содержащей объект регулярного выражения, то на печать выйдет исходное регулярное выражение, отформатированное интерпретатором:
    my $re=qr/ [a-z]+ # все буквы /x; print $re;
    Напечатается
    (?x-ism: [a-z]+ # все буквы )
    Оператор qr/…/ полезен не только тем, что заранее компилирует регулярное выражение, которое потом применяется в готовом виде, но и тем, что позволяет строить большие и сложные регулярные выражения по кирпичикам. Например:
    $_='ABCabc123'; my $re1=qr/[A-Z]+/; my $re2=qr/(\d+)/; print "$&\n$1\n$2" if /$re1([a-z]+)$re2/;
    Напечатается
    ABCabc123 abc 123
    Если внутри какого-то объекта используются захватывающие скобки, то возникает сложность и неудобство при модификации такого регулярного выражения: ведь если впереди будет вставлена переменная или непосредственный текст регулярного выражения, который тоже содержит захватывающие скобки, то нарушится нумерация нумерованных переменных и обратные ссылки могут уже не соответствовать захватывающим скобкам, которые раньше были были их "родными". Например:
    $_='abc111abc'; my $re1=qr/[a-z]+/; my $re2=qr/(\d+)\1/; print "$&\n$1" if /$re1$re2/;
    Напечатается
    abc11 1
    А в случае
    $_='abc111abc'; my $re1=qr/([a-z]+)/; my $re2=qr/(\d+)\1/; print "$&\n$1" if /$re1$re2/;

    напечатается

    abc111abc abc

    В первом примере обратная ссылка \1 имела значение 1, а во втором - abc, т.е. стала относиться к нумерованной переменной из другого объекта регулярного выражения.

    В принципе, в этих двух случаях может помочь переменная $^N, которая дублирует значение нумерованной переменной, соответствующей последней только что закрытой паре захватывающих скобок. Если вам надо сохранить значение нумерованной переменной, то сразу после того, как закроется ее скобка, вставьте код Perl

    (?{ $s=$^N })

    и последнее значение нумерованной переменной сохранится во внешней переменной $s.

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

    (??{ код Perl })

    Возвращаемое значение этого кода Perl интерпретируется как текст и как часть общего регулярного выражения.

    В последнем примере можно записать так:

    $_='abc111abc'; my $re1=qr/([a-z]+)/; my $re2=qr/(\d+)(??{$^N})/; print "$&\n$1" if /$re1$re2/;

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

    abc11 abc

    Отсюда видим, что динамическое регулярное выражение (??{$^N}) соответствует обратной ссылке \2. Как и во встроенном коде Perl, в динамических регулярных выражениях переменные не интерполируются при компиляции шаблона, а каждый раз вычисляются заново. Другое дело, что динамические регулярные выражения и встроенный код Perl требуют увеличения времени на работу регулярного выражения.

    Оператор qr/…/ и модификаторы

    Объект регулярного выражения компилируется вместе со своими модификаторами, эти режимы поиска "вшиваются" в откомпилированное регулярное выражение, и их нельзя потом изменить. Пример:
    $_='aB'; my $re=qr/[a-z]+/; print $& if /$re/;
    Идет поиск с учетом регистра символов, на печать выводится a. Модификаторы, находящиеся снаружи объекта регулярного выражения, не могут повлиять на его работу:
    print $& if /$re/i;
    print $& if /(?i)$re/;
    print $& if /(?i:$re)/i;
    Все равно выводится только буква a. Модификатор должен стоять в самом операторе, который возвращает объект регулярного выражения:
    $_='aB'; my $re=qr/[a-z]+/i; print $& if /$re/;
    Теперь печатается aB.
    Верно и обратное: режимы поиска, которые были установлены внутри объекта регулярного выражения, не влияют на остальную часть регулярного выражения, в которое входит этот объект. Пример:
    $_='aB'; my $re=qr/(?i)[a-z]+/; print $& if /${re}b/;
    Ничего не напечатается, т.к. режим поиска без учета регистра (?i) не действует вне объекта регулярного выражения $re. Здесь фигурные скобки, окружающие имя переменной, отделяют ее от последующих букв так же, как и в случае с интерполяцией переменных. Следующий пример:
    $_='aB'; my $re=qr/(?i)[a-z]+/; print $& if /${re}B/;
    напечатает aB.

    Оператор qr/…/ и проблемы при использовании модификатора o

    Когда в операторе qr/…/ используется модификатор o и есть интерполируемые переменные, этот объект регулярного выражения больше не модифицируется, если ему даже присваивается значение. Т.е. он как бы привязан к действующим на момент первого выполнения значениям интерполируемых переменных. Поэтому не используйте модификатор o с объектами регулярных выражений, если не хотите труднонаходимых сюрпризов. Такой пример:
    my $a=$_='ab'; my $re=qr"$a"o; for my $i (0..1) { print "$re\n"; print "Found $&\n" if /$re/; $_=$a='cd'; $re=qr"$a"; }
    За циклом мы создаем объект регулярного выражения $re с интерполяцией переменной $a. В цикле перед вторым проходом переменная $a меняет значение, и после этого опять повторяется оператор $re=qr"$a". На печать выходят оба найденных значения:
    (?-xism:ab) Found ab (?-xism:cd) Found cd
    Теперь внесем первый оператор my $re=qr"$a"o в цикл:
    my $a=$_='ab'; for my $i (0..1) { my $re=qr"$a"o; print "$re\n"; print "Found $&\n" if /$re/; $_=$a='cd'; $re=qr"$a"; }
    Напечатается
    (?-xism:ab) Found ab (?-xism:ab)
    Мы видим следующее: хотя переменная $a поменяла свое значение на cd, объект регулярного выражения $re стал как бы константой и продолжает хранить старое ее значение $a='ab', несмотря на то, что мы присваиваем $re новое значение. Виной этому модификатор o. Без него пример работает так, как ожидается:
    my $a=$_='ab'; for my $i (0..1) { my $re=qr"$a"; print "$re\n"; print "Found $&\n" if /$re/; $_=$a='cd'; $re=qr"$a"; }
    И печатает, как и в первом случае,
    (?-xism:ab) Found ab (?-xism:cd) Found cd
    Если интерполируемых переменных нет, то опять все работает так, как ожидается:
    my $a=$_='ab'; my $re=qr"ab"o; for my $i (0..1) { print "$re\n"; print "Found $&\n" if /$re/; $_=$a='cd'; $re=qr"cd"; }
    Выводит
    (?-xism:ab) Found ab (?-xism:cd) Found cd

    sub subr() { my $b=$_; print "\$b=$b\n"; if (/$b/) { print "Found $&\n" } else { print "Not found\n" } }

    Выводится, как и положено,

    $b=ab Found ab $b=cd Found cd

    При первом обращении к подпрограме subr ее переменная $b имеет значение ab, при втором обращении она имеет значение cd. Пример без модификатора o работает так, как мы ожидали. Теперь поставим модификатор о к регулярному выражению:

    $_='ab'; &subr(); $_='cd'; &subr();

    sub subr() { my $b=$_; print "\$b=$b\n"; if (/$b/o) { print "Found $&\n" } else { print "Not found\n" } }

    Печатается

    $b=ab Found ab $b=cd Not found

    Как говорится, что и требовалось доказать. Здесь дело даже не в том, что переменная $b объявлена в подпрограмме, она могла быть объявлена просто в каком-нибудь блоке, эффект был бы таким же:

    $_='ab'; for my $i (0..1) { my $b=$_; print "\$b=$b\n"; if (/$b/) { print "Found $&\n" } else { print "Not found\n" } $_='cd'; }

    Печатается

    $b=ab Found ab $b=cd Found cd

    А в случае с модификатором о:

    $_='ab'; for my $i (0..1) { my $b=$_; print "\$b=$b\n"; if (/$b/o) { print "Found $&\n" } else { print "Not found\n" } $_='cd'; }

    Печатается

    $b=ab Found ab $b=cd Not found

    Но если подобное регулярное выражение используется в подпрограмме многократно в цикле, то отсутствие модификатора о повлечет за собой многократную компиляцию этого регулярного выражения, хотя переменная $b внутри подпрограммы не меняет своего значения. Как тут быть? Эту проблему решают объекты регулярных выражений.

    Объект регулярного выражения $re=qr/$b/ будет компилироваться каждый раз при входе в подпрограмму, но внутри цикла можно использовать этот объект многократно без перекомпиляции регулярного выражения:

    $_='ab'; &subr(); $_='cd'; &subr();

    sub subr() { my $b=$_; my $re=qr/$b/; print "\$b=$b\n"; if (/$re/) { print "Found $&\n" } else { print "Not found\n" } }

    Печатает

    $b=ab Found ab $b=cd Found cd

    Оператор qr/…/

    Наряду с операторами q/…/, qq/…/ и qx/…/ в Perl существует оператор qr/…/. Он получает в качестве операнда регулярное выражение, транслирует его и запоминает в переменной, которой присваивается результат. Например:
    $_='123abcd'; my $re=qr/((\d+)\w+)/; /$re/; print "$1 $2";
    Будет напечатано
    123abcd 123
    Транслируется регулярное выражение только в момент работы оператора qr/…/. Далее при использовании переменной, которой было присвоено оттранслированное значение, будет применен внутренний байт-код регулярного выражения.
    Использование ограничителей вокруг переменной, содержащей оттранслированное регулярное выражение, не обязательно, но в этом примере, где не применяется оператор связывания =~ регулярного выражения с целевой переменной, без ограничителей регулярное выражение работать не будет:
    #!/usr/bin/perl -w use strict;
    $_='123abcd'; my $re=qr/((\d+)\w+)/; $re; print "$1 $2";
    Напечатаются сообщения об ошибках:
    Useless use of private variable in void context at a.pl line 6. Use of uninitialized value in concatenation (.) or string at a.pl line 7. Use of uninitialized value in concatenation (.) or string at a.pl line 7.
    В случае использование оператора связывания все работает нормально:
    $_='123abcd'; my $re=qr/((\d+)\w+)/; $_ =~ $re; print "$1 $2";
    Печатается
    123abcd 123
    Записи
    $_ =~ $re;
    и
    $_ =~ /$re/;
    не различаются, но в первом случае вы не сможете ничего добавить к регулярному выражению и поставить к нему модификаторы и квантификаторы. Неправильно будет писать ни
    $_ =~ ^$re$;
    ни
    $_ =~ $re$re;
    ни
    $_ =~ $re.$re;
    Если же мы будем использовать ограничители для всего регулярного выражения, то все эти записи кроме последней будут верны:
    $_ =~ /^$re$/; $_ =~ /$re$re/; $_ =~ /$re.$re/;
    В последнем случае точка будет означать не конкатенацию переменных, а метасимвол регулярного выражения.
    После разделителя в команде qr/…/ могут стоять модификаторы. Сравните результаты:
    $_='Ab'; my $re=qr/[a-z]+/; print $& if $_ =~ $re;
    Печатается b
    $_='Ab'; my $re=qr/[a-z]+/i; print $& if $_ =~ $re;
    Печатается Ab
    Поддерживаются все модификаторы кроме e, g и c. Применение модификатора o с оператором qr/…/ ведет к неприятному эффекту, которы рассматривается в пункте 10.2.

    Применение объктов регулярных выражений

    Объекты регулярных выражений можно применять для повышения эффективности и построения библиотек регулярных выражений, как "кирпичики". Вот пример уже рассмотренного поиска и подсветки ссылок и e-mail в тексте с использованием объектов регулярных выражений вместо интерполяции переменных:
    Листинг 10.1.
    (html, txt)
    На печать выйдет
    Листинг 10.2.
    (html, txt)
    Если регулярное выражение применяется неоднократно, то в качестве его "кирпичиков" лучше использовать объекты регулярных выражений, чем каждый раз интерполировать в него переменные, т.к. объекты регулярных выражений компилируются, только когда в программе выполняется оператор qr/…/.

    # Оформляем ссылки без login:passw s#((?>($protocol)(?(2)(?>$ip|$host$zone)|$host$zone)(?![A-Za-z0-9])|(?(?>$port?(?>\@$host$zone(?![A-Za-z0-9_.-]*\@))?)?))($tail?)#$res=$2 ? '' : 'http://'; "$1$3"#ge; # Оформляем ссылки с login:passw s#($protocol)($loginpasswat)($ip|$host$zone)((?>$port?)$tail?)#$1$2$3$4"#g; # Оформляем е-мейлы. Этот оператор чувствителен к тексту, на который меняет предыдущие операторы! s#((?$namechr{0,39})\@(?>$subdom1)(?:\.$subdom1)?\.$zone)(?!(?>[^\s"<]*)(?:" target=_blank>|))#$1#g; # Оформляем ссылки с IP s#((?/])$ip(?>$port?))($tail?)#"$1$2"#g;

    print $_;

    Листинг 10.1.

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

    http://www.proxy.com:80@www.site.com/ Ftp://a.com/AAa Ftp://Login:Passw@Www.Aaa.Com/Www/" Ftp://login:passw@a-aa.com/www/" Mailto:aaa@sss.zzz.co. Mailto:aaa@sss.zzz.eee.co. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com ыы@ddd.com ыы@ddЫd.com ыыsы-sf.ff.com.com@ddd.com ыыsы.-sf.ff@ddd.com Mailto:aaa@sss.co, aaa@sss.comЫЫЫ aaa.Bb.b@aaaa.com.ru.rr.ggg aaa.museumm Look at:aaa.museum. httpS://aaa.museumm, http://www.proxy.com:80@www.site.com/ http://proxy.com:80@site.com/ http://proxy.com@site.com/ aAaa.com.ru.rr.ggg Zwww.Yabcd.co.uk Фforum.abcde.ru www.Eabcd.ru http://Eabcd.Ru Ahttp://www.Eabcd.ru/AAa http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.abcdefg-avto.ru httP://1.2.3.400/aaa/ddd.exe? "1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h", ."0.2.3.400". http://66.123.234.555/ddd michel@ab-cdefg.ru http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a

    Листинг 10.2.

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

    © 2003-2007 INTUIT.ru. Все права защищены.

    и passw ограничены 32 символами

    #!perl -w use strict;
    my $wb=qr'(?![A-Za-z0-9])';
    my $protocol=qr'(?=[FfHh])(?i:http(?>s?)|ftp)://';
    my $host=qr'(?>[-A-Za-z0-9_]{1,63}\.) (?>[A-Za-z0-9_] (?>[-A-Za-z0-9_]{0,62})\. )*'x; # вместе с www
    my $subdom=qr'(?:(?>[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)\.)+';
    my $subdom1=qr'[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?';
    my $zone=qr"(?(?=[a-z]{3}$wb)(?>com|net|org|edu|biz|gov|int|mil)| (?(?=[a-z]{2}$wb)[a-z]{2}| (?(?=[a-z]{4}$wb)(?>info|aero|name)| (?(?=[a-z]{6}$wb)museum|(?!) ) ) ) ) (?>\.[a-z]{2}$wb)?"ix;
    my $port=qr":\d{1,5}$wb";
    my $tail=qr#[/?](?>[^.,"'<>()\[\]{}\s\x7F-\xFF]*)(?:(?>[.,?]+)(?:[^"'<>()\[\]{}\s\x7F-\xFF]+))*(? my $firstchr=qr'[A-Za-z0-9]';
    my $namechr=qr'[A-Za-z0-9_+.-]';
    my $ip=qr'(?\d{1,3})\.(?>\d{1,3})\.(?>\d{1,3})\.(?>\d{1,3})(?!\d)';
    # Login и passw ограничены 32 символами my $loginpasswat=qr'(?>[A-Za-z0-9_]{1,32})(?>(?::[A-Za-z0-9_]{1,32})?)@';
    my $res;
    $_=q(http://www.proxy.com:80@www.site.com/ Ftp://a.com/AAa Ftp://Login:Passw@Www.Aaa.Com/Www/ Ftp://login:passw@a-aa.com/www/ Mailto:aaa@sss.zzz.co. Mailto:aaa@sss.zzz.eee.co. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com ыы@ddd.com ыы@ddЫd.com ыыsы-sf.ff.com.com@ddd.com ыыsы.-sf.ff@ddd.com Mailto:aaa@sss.co, aaa@sss.comЫЫЫ aaa.Bb.b@aaaa.com.ru.rr.ggg aaa.museumm Look at:aaa.museum. httpS://aaa.museumm, http://www.proxy.com:80@www.site.com/ http://proxy.com:80@site.com/ http://proxy.com@site.com/ aAaa.com.ru.rr.ggg Zwww.Yabcd.co.uk Фforum.abcde.ru www.Eabcd.ru http://Eabcd.Ru Ahttp://www.Eabcd.ru/AAa http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.abcdefg-avto.ru httP://1.2.3.400/aaa/ddd.exe? 1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h, .0.2.3.400. http://66.123.234.555/ddd michel@ab-cdefg.ru http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a );
    # Оформляем ссылки без login:passw s#((?>($protocol)(?(2)(?>$ip|$host$zone)|$host$zone)(?![A-Za-z0-9])|(?(?>$port?(?>\@$host$zone(?![A-Za-z0-9_.-]*\@))?)?))($tail?)#$res=$2 ? '' : 'http://'; "$1$3"#ge; # Оформляем ссылки с login:passw s#($protocol)($loginpasswat)($ip|$host$zone)((?>$port?)$tail?)#$1$2$3$4"#g; # Оформляем е-мейлы. Этот оператор чувствителен к тексту, на который меняет предыдущие операторы! s#((?$namechr{0,39})\@(?>$subdom1)(?:\.$subdom1)?\.$zone)(?!(?>[^\s"<]*)(?:" target=_blank>|))#$1#g; # Оформляем ссылки с IP s#((?/])$ip(?>$port?))($tail?)#"$1$2"#g;
    print $_;
    Листинг 10.1.
    Закрыть окно

    к тексту, на который меняет

    #!perl -w
    use strict;
    my $wb=qr'(?![A-Za-z0-9])';
    my $protocol=qr'(?=[FfHh])(?i:http(?>s?)|ftp)://';
    my $host=qr'(?>[-A-Za-z0-9_]{1,63}\.)
    (?>[A-Za-z0-9_]
    (?>[-A-Za-z0-9_]{0,62})\.
    )*'x; # вместе с www
    my $subdom=qr'(?:(?>[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?)\.)+';
    my $subdom1=qr'[A-Za-z0-9](?:[-A-Za-z0-9]{0,61}[A-Za-z0-9])?';
    my $zone=qr"(?(?=[a-z]{3}$wb)(?>com|net|org|edu|biz|gov|int|mil)|
    (?(?=[a-z]{2}$wb)[a-z]{2}|
    (?(?=[a-z]{4}$wb)(?>info|aero|name)|
    (?(?=[a-z]{6}$wb)museum|(?!)
    )
    )
    )
    )
    (?>\.[a-z]{2}$wb)?"ix;
    my $port=qr":\d{1,5}$wb";
    my $tail=qr#[/?](?>[^.,"'<>()\[\]{}\s\x7F-\xFF]*)(?:(?>[.,?]+)(?:[^"'<>()\[\]{}\s\x7F-\xFF]+))*(? my $firstchr=qr'[A-Za-z0-9]';
    my $namechr=qr'[A-Za-z0-9_+.-]';
    my $ip=qr'(?\d{1,3})\.(?>\d{1,3})\.(?>\d{1,3})\.(?>\d{1,3})(?!\d)';
    # Login и passw ограничены 32 символами
    my $loginpasswat=qr'(?>[A-Za-z0-9_]{1,32})(?>(?::[A-Za-z0-9_]{1,32})?)@';
    my $res;
    $_=q(http://www.proxy.com:80@www.site.com/
    Ftp://a.com/AAa
    Ftp://Login:Passw@Www.Aaa.Com/Www/
    Ftp://login:passw@a-aa.com/www/
    Mailto:aaa@sss.zzz.co.
    Mailto:aaa@sss.zzz.eee.co.
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com
    ыы@ddd.com
    ыы@ddЫd.com
    ыыsы-sf.ff.com.com@ddd.com
    ыыsы.-sf.ff@ddd.com
    Mailto:aaa@sss.co,
    aaa@sss.comЫЫЫ
    aaa.Bb.b@aaaa.com.ru.rr.ggg
    aaa.museumm
    Look at:aaa.museum.
    httpS://aaa.museumm,
    http://www.proxy.com:80@www.site.com/
    http://proxy.com:80@site.com/
    http://proxy.com@site.com/
    aAaa.com.ru.rr.ggg
    Zwww.Yabcd.co.uk
    Фforum.abcde.ru
    www.Eabcd.ru
    http://Eabcd.Ru
    Ahttp://www.Eabcd.ru/AAa
    http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image
    Ф.Www.abcdefg-avto.ru
    httP://1.2.3.400/aaa/ddd.exe?
    1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h,
    .0.2.3.400.
    http://66.123.234.555/ddd
    michel@ab-cdefg.ru
    http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a
    );
    # Оформляем ссылки без login:passw
    s#((?>($protocol)(?(2)(?>$ip|$host$zone)|$host$zone)(?![A-Za-z0-9])|(?(?>$port?(?>\@$host$zone(?![A-Za-z0-9_.-]*\@))?)?))($tail?)#$res=$2 ? '' : 'http://'; "$1$3"#ge;
    # Оформляем ссылки с login:passw
    s#($protocol)($loginpasswat)($ip|$host$zone)((?>$port?)$tail?)#$1$2$3$4"#g;
    # Оформляем е-мейлы. Этот оператор чувствителен к тексту, на который меняет предыдущие операторы!
    s#((?$namechr{0,39})\@(?>$subdom1)(?:\.$subdom1)?\.$zone)(?!(?>[^\s"<]*)(?:" target=_blank>|))#$1#g;
    # Оформляем ссылки с IP
    s#((?/])$ip(?>$port?))($tail?)#"$1$2"#g;
    print $_;

    museumm Look

    http://www.proxy.com:80@www.site.com/ Ftp://a.com/AAa Ftp://Login:Passw@Www.Aaa.Com/Www/" Ftp://login:passw@a-aa.com/www/" Mailto:aaa@sss.zzz.co. Mailto:aaa@sss.zzz.eee.co. aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com ыы@ddd.com ыы@ddЫd.com ыыsы-sf.ff.com.com@ddd.com ыыsы.-sf.ff@ddd.com Mailto:aaa@sss.co, aaa@sss.comЫЫЫ aaa.Bb.b@aaaa.com.ru.rr.ggg aaa. museumm Look at:aaa.museum. httpS://aaa.museumm, http://www.proxy.com:80@www.site.com/ http://proxy.com:80@site.com/ http://proxy.com@site.com/ aAaa.com.ru.rr.ggg Zwww.Yabcd.co.uk Фforum.abcde.ru www.Eabcd.ru http://Eabcd.Ru Ahttp://www.Eabcd.ru/AAa http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image Ф.Www.abcdefg-avto.ru httP://1.2.3.400/aaa/ddd.exe? "1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h", ."0.2.3.400". http://66.123.234.555/ddd michel@ab-cdefg.ru http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a
    Листинг 10.2.
    Закрыть окно

    Пример Look at:aaa.museum

    http://www.proxy.com:80@www.site.com/
    Ftp://a.com/AAa
    Ftp://Login:Passw@Www.Aaa.Com/Www/"
    Ftp://login:passw@a-aa.com/www/"
    Mailto:aaa@sss.zzz.co.
    Mailto:aaa@sss.zzz.eee.co.
    aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@aaa.com
    ыы@ddd.com
    ыы@ddЫd.com
    ыыsы-sf.ff.com.com@ddd.com
    ыыsы.-sf.ff@ddd.com
    Mailto:aaa@sss.co,
    aaa@sss.comЫЫЫ
    aaa.Bb.b@aaaa.com.ru.rr.ggg
    aaa.museumm
    Look at:aaa.museum.
    httpS://aaa.museumm,
    http://www.proxy.com:80@www.site.com/
    http://proxy.com:80@site.com/
    http://proxy.com@site.com/
    aAaa.com.ru.rr.ggg
    Zwww.Yabcd.co.uk
    Фforum.abcde.ru
    www.Eabcd.ru
    http://Eabcd.Ru
    Ahttp://www.Eabcd.ru/AAa
    http://abc.ru/query/vid.cam.dig/sony.dcrhc15.htm#full_image
    Ф.Www.abcdefg-avto.ru
    httP://1.2.3.400/aaa/ddd.exe?
    "1.2.3.400/aaa/ddd.exe?d=c,f=t;&e=h",
    ."0.2.3.400".
    http://66.123.234.555/ddd
    michel@ab-cdefg.ru
    http://99.999.999.999/search?q=cache:w5K8GsupwvcJ:olympus.flexiblesoft.com/c-4000-man.doc+c-4000-man&hl=ru&client=firefox-a

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

    Автоматическая локализация специальных переменных, относящихся к регулярным выражениям

    Теперь рассмотрим такой пример:
    my $a=1; $_='a'; /(a)/; print "$1\n"; /(b)/; print "$1\n";
    Будет напечатано
    a a
    Первое регулярное выражение совпало, и переменная $1 получила значение a. Второе регулярное выражение не совпало, но мы этого не проверили и напечатали содержимое переменной $1, которое осталось от первого регулярного выражения. Ведь эти специальные переменные изменяются только при удачном поиске, а при неудачном содержат последние присвоенные значения. Во втором регулярном выражении вообще могло не быть захватывающих скобок, что не повлияло бы на печать. Поэтому, чтобы не ошибиться, надо проверять, было совпадение или нет. А в операторе подстановки надо проверять возвращаемый результат, который содержит число произведенных замен.
    Теперь рассмотрим более интересный пример:
    my $a=1; $_='ab'; /(a)/; print "$1\n"; if ($a) { /(b)/; print "$1\n"; } print "$1\n";
    Будет напечатано
    a b a
    Во внешнем регулярном выражении переменная $1 получает значение a, в регулярном выражении внутри блока эта переменная получает значение b. Затем при выходе из блока мы пытаемся использовать это последнее присвоенное значение. И тут нас поджидает сюрприз (не знаю, приятный или нет): вне блока переменная $1 опять имеет свое старое значение. Это может породить труднонаходимые ошибки в программе, если не позаботиться о сохранении необходимых специальных переменных в переменных my.
    Дело в том, что специальные переменные, используемые в регулярных выражениях, автоматически локализуются при входе каждый блок. Вначале при входе в блок новая локализованная переменная наследует свое значение из внешнего блока, но внутри блока ее значение может измениться в регулярном выражении. Этот же эффект возникает при использовании подпрограмм, ведь каждая подпрограмма определяет новый блок:
    $_='ab'; /(a)/; print "$1\n"; &subr(); print "$1\n";
    sub subr() { /(b)/; print "$1\n"; }
    Напечатается
    a b a
    Мы видим, что при выходе из подпрограммы переменная $1 вернула себе старое значение, а значение, полученное ею в подпрограмме, потерялось. Это следствие концепции динамической видимости. К специальным переменным, которые не относятся к регулярным выражениям, автоматическая локализация не применяется.

    Имитация именованного сохранения

    Регулярные выражения в 5-ой версии Perl не поддерживают именованного сохранения найденных фрагментов текста, а поддерживают только переменные с номером. Это создает неудобства, а при использовании объектов регулярных выражений, которые сохраняют найденные фрагменты текста, тяжело модифицировать эти объекты, если надо ввести новую сохраняющую переменную. В 6-й версии Perl должно появиться именованное сохранение, когда вместо номера переменной можно задать ей произвольное имя и обращаться к такой переменной по имени. А пока для этого остается использовать встроенный код и специальную переменную $^N. Например, у нас есть объект регулярного выражения, который сохраняет URL:
    my $re=qr/ Мы можем его использовать в коде
    $_=''; print $1 if /$re/;
    И напечатается
    http://www.intuit.ru
    URL получается в переменной $1. Если объект регулярного выражения $re входит кирпичиком в более крупное регулярное выражение, то нумерованные переменные использовать рискованно, т.к. при вставке новых объектов или редактировании существующих нумерация может сбиться и URL может оказаться уже не в переменной $1, а в другой нумерованной переменной. Мы можем объявить переменную $url и всегда сохранять в ней найденный URL, воспользовавшись тем, что переменная $^N является копией нумерованной переменной, которая соответствует последней паре захватывающих скобок:
    my $url; my $re=qr/ Опять напечатается
    http://www.intuit.ru
    Подобный кирпичик $re может войти в здание более сложного регулярного выражения, не влияя на другие переменные, сохраняющие фрагменты текста. Но встроенный код, конечно, требует дополнительного времени для выполнения.

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

    Перед применением регулярного выражения Perl, как правило, создает копию целевого текста. Это нужно, например, если после использования регулярного выражения идет обращение к нумерованным переменным. Ведь программист может изменить целевой текст, но содержимое нумерованных переменных не должно меняться, чтобы не вызвать неразбериху.
    Perl не создает каждый раз при использовании регулярного выражения специальные переменные $1, $+, $` и т.д., он просто создает копию целевого текста и эти переменные ссылаются на фрагменты текста в этой копии. Это экономит время, ведь не всегда эти переменные потом будут затребованы программистом.
    Целевой текст может оказаться большим, и система вынуждена будет тратить время и память на его дублирование, хотя этот дубль может и не понадобиться. Если мы не используем захватывающих скобок, будет ли Perl создавать копию целевого текста?
    Будет, т.к. существуют еще переменные $`, $& и $'. Perl не может решить, к какому регулярному выражению применяются эти переменные, и будет создавать копию целевого текста каждый раз, несмотря на отсутствие захватывающих скобок. По этой причине переменные $`, $& и $' называются "вредными". Кроме того, их применение замедляет работу программы.
    Транслятор просматривает всю программу и все используемые ею модули на предмет наличия этих "вредных" переменных. И если он их не находит, то выпоняет оптимизацию программы: не создает копию целевого текста для регулярных выражений, которые не используют захватывающих скобок. Базовые модули, которые входят в поставку Perl, за исключением модуля English, не используют этих переменных.
    "Вредные" переменные $`, $& и $' можно имитировать с помощью массивов @- и @+. (Предполагаем, что целевой текст находится в переменной $_):
  • $` соответствует substr($_, 0, $-[0])
  • $& соответствует substr($_, $-[0], $+[0] - $-[0])
  • $' соответствует substr($_, $+[0])


  • Концепция динамической видимости переменных

    В языках программирования существуют глобальные и закрытые (private) переменные, которые объявляются директивой my (…). В Perl специальные глобальные переменные, такие, как $_, $1, @ARGV, не объявляются и доступны из любой точки программы. Если вы не используете директиву use strict (или use strict 'vars') и объявляете в программе переменные ($a и т.д.), то эти переменные будут глобальными для данного пакета. Если вы употребили директиву use strict, вы должны будете объявлять эти переменные директивой our.
    Переменные my имеют лексическую видимость и видны в минимальном блоке { … }, в котором находится директива my. (Это утверждение также относится к блоку кода Perl (?{…}) внутри регулярного выражения.
    В языке Perl существует также концепция динамической видимости. Perl может сохранить значение глобальной переменной перед входом в блок и восстановить его перед выходом из него. Код внутри блока будет работать с двойником этой глобальной переменной, которой может присвоить другое значение. Динамическая видимость создается директивой local. Например:
    $_=1; { local $_=2; print "$_\n"; } print "$_\n";
    На печать выведется
    2 1
    Внутри блока программа работает с копией переменной $_, которая имеет то же имя, а при выходе из блока эта копия уничтожается, и программе становится доступна переменная $_, которая существовала до входа в этот блок. Директива local иногда применяется в подпрограммах, хотя в них логичнее создавать внутренние переменные директивой my. Директива local не применяется к переменным, созданным директивой my.

    Специальные переменные, изменяемые при поиске

    При совпадении в операторах m/…/ и s/…/…/ изменяют значения специальные переменные регулярных выражений. Напомню их.
  • $` - текст перед совпадением всего регулярного выражения.
  • $& - текст, с которым совпало все регулярное выражение.
  • $' - текст после совпадения всего регулярного выражения.
  • $1 - текст, совпавший с первой парой захватывающих скобок.
  • $2 - текст, совпавший со второй парой захватывающих скобок.
  • $99 - текст, совпавший с 99-й парой захватывающих скобок.
  • $+ - Содержимое нумерованной переменной ($1, $2, … ,$99) с максимальным номером (на момент использования переменной $+).
  • $^N - Содержимое нумерованной переменной ($1, $2, … ,$99), соответствующей последней только что закрытой паре скобок (на момент использования переменной $^N). (Эту переменную в отличие от $+ почему-то можно читать сразу после закрывающей захватывающей скобки. Возможно, эта ошибка уже исправлена в новой версии Perl.)
  • @- - массив начальных индексов совпадений в целевом тексте. $-[0] соответствует переменной $&, $-[1] - переменной $1, …, $-[99] - переменной $99.
  • @+- массив конечных индексов (т.е. индексов первого символа после совпадения) совпадений в целевом тексте. $+[0] соответствует переменной $&, $+[1] - переменной $1, …, $+[99] - переменной $99.
  • $^R - стоит немного особняком и допускает присваивание. Результат последней по времени исполняемой части встроенного кода, который расположен не в условии условной конструкции (? if then [ | else ] ).

  • Все эти переменные кроме $^R предназначены только для чтения. Многие авторы по ошибке считают, что переменная $^R тоже только читается, но мы убедились в противоположном. Также они ошибаются, говоря, что вне регулярного выражения эта переменная не имеет смысла.
    Все эти переменные изменяют свое значение только при успешном поиске, при неудачном они хранят последнее присвоенное значение.
    Переменные $1, $2, …, $99 устанавливаются сразу после закрытия соответствующей скобки, поэтому их можно использовать во встроенном коде или динамических регулярных выражениях внутри регулярного выражения. (Вне встроенного кода и динамических регулярных выражений используйте обратные ссылки \1, \2, …, \99.) То же относится к переменным $+, $^N, @- и @+. Переменная $^R получает значение после завершения соответствующего встроенного кода Perl.
    В случае применения модификатора g (gc) при каждой итерации значения этим переменным присваиваются заново. Поэтому в операторе подстановки эти переменные всегда соответствуют соответствующим фрагментам текста из последней итерации.

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

    Поиск вложенных конструкций

    Изначально динамические регулярные выражения создавались для того, чтобы искать вложенные конструкции произвольного уровня вложенности. Мы уже разбирали такой пример, который делал это с помощью встроенного кода Perl. Сейчас рассмотрим эти примеры с использованием динамических регулярных выражений.
    Рассмотрим задачу удаления всех комментариев, которые заключены в фигурные скобки, вместе с этими скобками. При этом предположим, что эти комментарии могут иметь произвольную вложенность. Т.е. надо уничтожить все правильно сбалансированные серии фигурных скобок вместе с их содержимым. От строки
    $_=" {a{b{c{{d{}m}e}f}}gf{ }f";
    после этого должно остаться ' {agff'.
    Будем рассуждать от легкого случая к более сложным. Если бы речь шла только о первом уровне вложенности: …{…}…, то мы могли бы создать такой объект регулярного выражения:
    my $level0=qr/(?>[^{}]*)/;
    который соответствует всему кроме фгурных скобок. А оператор замены был бы таким:
    s/\{$level0}//g;
    Напоминаю, что символ } не обязательно маскировать, т.к. он в отличие от символа { не является метасимволом регулярного выражения.
    Эта программа корректно бы удаляла комментарии первого уровня вложенности. От строки a{b}c осталось бы ac. Но если бы мы задали этой программе строку
    a{b{c}d}e
    то в результате получили бы остаток
    a{bd}e
    Удаляются только скобки с фрагментами, которые не содержат этих скобок. Мы могли бы повторять подстановку, пока оператор s/…/…/ возвращает ненулевой результат, но нам нужен общий метод для любого уровня вложенности. С этой целью расширим наш объект регулярного выражения. Он должен совпадать не только с текстом без скобок, но и с текстом без скобок, который ограничен этими фигурными скобками. Это мы сделаем с помощью конструкции альтернативного шаблона:
    my $level1=qr/(?>[^{}]|\{$level0})*/;
    Здесь мы воспользовались тем, что у нас уже есть регулярное выражение, которое соответствует фрагменту текста без фигурных скобок, это объект $level0. Чтобы фрагменты текста без скобок поглощались быстрее, можно поставить квантификатор +:
    my $level1=qr/(?>[^{}]+|\{$level0})*/;

    А чтобы не создавалось ненужных сохраненных состояний, можно это еще взять в атомарные скобки:

    my $level1=qr/(?>(?>[^{}]+)|\{$level0})*/;

    Программа

    $_='a{b{c}d}e'; my $level0=qr/(?>[^{}]*)/; my $level1=qr/(?>(?>[^{}]+)|\{$level0})*/; s/\{$level1}//g; print $_;

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

    ae

    Но если ей дать текст со скобками третьего уровня вложенности, то она оставляет скобки первого уровня вложенности:

    $_='a{b{c{d}}e}f'; my $level0=qr/(?>[^{}]*)/; my $level1=qr/(?>(?>[^{}]+)|\{$level0})*/; s/\{$level1}//g; print $_;

    Выводится

    a{be}f

    Мы могли бы по аналогии создать объект $level3 и т.д., но динамические регулярные выражения позволяют сразу создать объект $levelN для произвольного уровня вложенности:

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

    $_=" {a{b{c{{d{}m}e}f}}gf{ }f"; my $levelN; $levelN=qr /(?> (?>[^{}]+)| # все кроме фигурных скобок \{(??{$levelN})} # или текст, соответств. всему шаблону, ограниченный скобками )* # сколько угодно раз /x; s/\{$levelN}//g; print $_;

    В результате получаем

    {agff

    Это верный результат.


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

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

    $_=< ff
    bb ssssssss
    EOD

    my $levelN; $levelN=qr "(?> (?:(?!]*>(??{$levelN}) # или вся следующая таблица )* # сколько угодно раз "isx;

    my @tables=m"]*>$levelN"gi; print join "\n--\n",@tables if @tables;

    Конструкция (?!
    ff
    --


    Я не стал перегружать это регулярное выражение атомарными скобками, вы можете сделать это самостоятельно.

    По поводу использования динамических регулярных выражений надо сделать одно замечание, которое отсутствует у всех остальных авторов книг и документаций: мы не рассматривали случаев использования захватывающих скобок внутри динамических регулярных выражений. Но ведь код Perl внутри этих выражений может насоздавать сколько угодно захватывающих скобок. Что тогда будет? Ведь нумерация таких скобок после этого динамического регулярного выражения собьется. Разработчики предусмотрели такой казус, и захватывающие скобки, которые создаются кодом Perl внутри динамических регулярных выражений, на самом деле ничего не захватывают, а лишь группируют подшаблоны. Для подтверждения этого факта рассмотрим такой пример:

    $_='abc'; print "$1 $2" if /(a)(??{"(b)"})(c)/;


    В результате напечатается

    a c

    Если бы мы попытались распечатать "$1 $2 $3", то на печать вышло бы то же самое и еще предупреждение об использовании неинициализированной переменной (это $3).

    Если бы мы вставляли значение переменной, которое содержит текст (b), то результат был бы тем же:

    $_='abc'; my $a='(b)'; print "$1 $2" if /(a)(??{$a})(c)/;

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

    $_='abc'; my $a=qr'(b)'; print "$1 $2" if /(a)(??{$a})(c)/;

    Но как только мы попытаемся внутри всего регулярного выражения использовать одно динамическое регулярное выражение, содержащее захватывающие скобки, то Windows покажет окно с сообщением, что в Perl обнаружена ошибка и это приложение будет закрыто, при этом извиняясь за неудобство. Perl успевает вывести сообщение, что совпадение закончилось успешно. Эта ошибка возникает не только в Perl под Windows.

    Вот эти фрагменты кода вызывают ошибку Perl:

    $_='abc'; my $a='(b)'; /(??{$a})/;

    $_='abc'; print 'Found' if /(??{"(b)"})/;

    $_='abc'; my $a=qr'(b)'; print 'Found' if /(??{"$a"})/;

    Странно, но в случае использования объекта регулярного выражения, когда внутри динамического регулярного выражения $a присутствует без кавычек, ошибки не возникает:

    $_='abc'; my $a=qr'(b)'; print 'Found' if /(??{$a})/;

    В остальных случаях Perl аварийно завершается. При использовании незахватывающих скобок:

    $_='abc'; my $a='(?:b)'; /(??{$a})/;

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

    © 2003-2007 INTUIT.ru. Все права защищены.

    Примеры применения динамических регулярных выражений

    Для начала приведу простой и немного искусственный пример: пусть нам надо проверить правильность строки
    Далее стоит 13 нулей: 0000000000000
    Причем, число нулей может быть произвольным от 1 и более, например,
    Далее стоит 2 нуля: 00
    Нам надо составить регулярное выражение, которое проверяет, что число соответствует количеству нарисованных нуликов. Этот шаблон может выглядеть так:
    (\d+) \D+(??{"0{$1}"})$
    Работает он таким образом: вначале в переменную $1 захватывается число (13, 2 и т.д.).
    Затем идет пропуск всех нецифровых символов. Внутри динамического регулярного выражения конструируется подшаблон, состоящий из символа нуля и числителя, который равен содержимому переменной $1. Для фразы
    Далее стоит 13 нулей: 0000000000000
    Весь шаблон после подстановки результата выполнения кода Perl будет иметь вид
    (\d+) \D+0{13}$
    В случае строки
    Далее стоит 2 нуля: 00
    Это регулярное выражение примет вид
    (\d+) \D+0{2}$
    Даже для строки
    Далее стоит 0 нулей:
    будет найдено совпадение.

    Для начала приведу простой и немного искусственный пример: пусть нам надо проверить правильность строки
    Далее стоит 13 нулей: 0000000000000
    Причем, число нулей может быть произвольным от 1 и более, например,
    Далее стоит 2 нуля: 00
    Нам надо составить регулярное выражение, которое проверяет, что число соответствует количеству нарисованных нуликов. Этот шаблон может выглядеть так:
    (\d+) \D+(??{"0{$1}"})$
    Работает он таким образом: вначале в переменную $1 захватывается число (13, 2 и т.д.).
    Затем идет пропуск всех нецифровых символов. Внутри динамического регулярного выражения конструируется подшаблон, состоящий из символа нуля и числителя, который равен содержимому переменной $1. Для фразы
    Далее стоит 13 нулей: 0000000000000
    Весь шаблон после подстановки результата выполнения кода Perl будет иметь вид
    (\d+) \D+0{13}$
    В случае строки
    Далее стоит 2 нуля: 00
    Это регулярное выражение примет вид
    (\d+) \D+0{2}$
    Даже для строки
    Далее стоит 0 нулей:
    будет найдено совпадение.
    В документации по Perl приводится пример применения динамических регулярных выражений для того, чтобы выяснить, является ли заданная в тексте последовательность чисел последовательностью Фибоначчи. А мы в качестве более сложного примера поставим немного другую задачу: найти в $_ первую наидлиннейшую последовательность 10-ных цифр, которые стоят подряд и возрастают на 1. Например, в строке
    $_='0123 1234345678910 ';
    искомая наидлиннейшая последовательность такая: 3456789.
    Вот сама программа:
    #!/usr/bin/perl -w use strict;
    my ($len,$d,$res)=(0); $_='0123 1234345678910 '; no warnings;
    /((\d) (?{$d=$+}) (?> (??{ ++$d < 10 ? "$d" : "(?!)" })* ) ) (?{ # Эти символы нельзя разделять if (length $1 > $len) { $len=length $1; $res=$1; } }) # Эту скобку нельзя отделять от предыдущей скобки "}" (?!) /x; print $res if defined $res;
    Вначале переменной $len присваивается 0. В комментариях указана особенность конструкций (?{ и }), в которых фигурную скобку нельзя отделять от соседнего символа даже и при свободном форматировании.
    Теперь разберемся, как работает это регулярное выражение. Вначале открывается захватывающая скобка. Позже выяснится, что она захватывает очередную длиннейшую последовательность цифр, которые возрастают на единицу. Вторая пара захватывающих скобок захватывает одну цифру. Во второй строке стоит встроенный код Perl, который запоминает эту цифру в переменной $d. Далее идет динамическое регулярное выражение, к которому записан квантификатор *. Оно вставлено в атомарные скобки.
    Код в этом динамическом регулярном выражении при каждой итерации, обусловленной квантификатором *, увеличивает на единицу переменную $d, и если ее значение меньше десяти, то он возвращает значение этой переменной, а если $d после инкремента принимает значение 10, которое уже не подходит для цифры, то этот код Perl возвращает уже знакомый нам подшаблон (?!), который заставляет квантификатор * сделать шаг назад и остановиться на своем предыдущем состоянии, при котором было совпадение с наибольшей цифрой. После этого совпадения для атомарных скобок управление передается за них и выполняется код Perl, который проверяет, больше ли длина переменной $1 длины переменной $len. Если больше, значит, мы нашли более длинную последовательность цифр, поэтому мы запоминаем ее в переменной $res и обновляем содержимое переменной $len, которая хранит размер найденной длиннейшей последовательности цифр. После этого кода опять стоит подшаблон (?!), который заставляет все регулярное выражение сделать повтор со следующего символа целевого текста.
    Директива no warnings нужна, чтобы отменить предупреждение
    matches null string many times in regex; …


    которое появится, когда динамическое регулярное выражение вернет подшаблон (?!).

    Perl предупреждает, что подшаблон, который не поглощает текст, имеет квантификатор. Но в нашем примере это не приводит к зацикливанию, т.к. этот подшаблон возвращает несовпадение, и происходит выход за пределы квантифицируемого подшаблона.

    Здесь опять замечу, что это регулярное это выражение возвращает несовпадение и используется только ради побочного эффекта (установки переменной $res).

    Насколько эффективно работает это регулярное выражение? Вставьте встроенный код

    Perl после атомарных скобок, который будет печатать содержимое переменной $1:

    /((\d) (?{$d=$+}) (?> (??{ ++$d < 10 ? "$d" : "(?!)" })* ) ) (?{print "$1\n"}) (?{ # Эти символы нельзя разделять if (length $1 > $len) { $len=length $1; $res=$1; } }) # Эту скобку нельзя отделять от предыдущей скобки "}" (?!) /x; print $res if defined $res;

    В результате на печати получим:

    0123 123 23 3 1234 234 34 4 3456789 456789 56789 6789 789 89 9 1 0 3456789

    Видим, что вначале регулярное выражение находит длиннейшую последовательность цифр, а потом перестартовывает с каждой следующей цифры этой последовательности благодаря второму подшаблону (?!). Хотелось бы, чтобы вместо этого начальная позиция при итерациях устанавливалась сразу за найденной последовательностью цифр, дабы не делать лишней работы. Но мне кажется, это невозможно, т.к. присвоение внутри регулярного выражения функции pos (pos($_)=…) не дает эффекта.

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

    (?(?{$len == 4})\G(?!))

    При достижении этого условия встроенный код Perl вернет истину, и на место всей условной конструкции будет подставлен подшаблон

    \G(?!)

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

    Что будет, если убрать атомарную группировку?

    /((\d) (?{$d=$+})

    (??{ ++$d < 10 ? "$d" : "(?!)" })*

    ) (?{print "$1\n"}) (?{ # Эти символы нельзя разделять if (length $1 > $len) { $len=length $1; $res=$1; } }) # Эту скобку нельзя отделять от предыдущей скобки "}" (?!) /x; print $res if defined $res;

    Тогда на печать выйдет гораздо больше строк:

    0123 012 01 0 123 12 1 23 2 3 1234 123 12 1 234 23 2 34 3 4 3456789 345678 34567 3456 345 34 3 456789 45678 4567 456 45 4 56789 5678 567 56 5 6789 678 67 6 789 78 7 89 8 9 1 0 3456789

    Из этой распечатки видно, что после выполнения второго подшаблона (?!) не происходит итерация всего регулярного выражения, как в первом случае, ведь квантификатор * имеет сохраненные состояния. Он начинает отдавать по одной захваченной цифре, пока не отдаст их все. Только после этого происходит рестарт всего регулярного выражения со следующего символа целевого текста.

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

    Функция study

    Функция
    study( переменная с целевым текстом )
    является еще одной возможностью оптимизации использования регулярных выражений.
    Эта функция создает в переменной с целевым текстом скрытую информацию о тексте, который содержит эта переменная. Это список позиций, в которых каждый символ встречается в данном тексте. В результате на построение этого списка позиций расходуется время и память, которая обычно превосходит в четыре раза объем памяти для текста этой переменной. Поэтому выигрыша во времени можно не получить вовсе, если применять функцию study не там, где нужно. Функция study может существенно ускорить поиск, если в регулярном выражении имеется литеральный текст, но модификатор i сводит на нет усилия функции study. Также функция study может оказаться полезной, если к одной и той же переменной применяется много операторов с регулярными выражениями или если целевой текст имеет большой объем. При любом
    присваивании значения переменной, содержащей целевой текст, внутренняя информация, привязанная к этой переменной, становится недействительной.
    Для применения оптимизаций, которые возможны с функцией study, надо стараться выделять литеральный текст. Например, вместо
    a+
    надо написать эквивалентный подшаблон
    aa*
    а вместо
    a{3,6}
    нужно записать
    aaa{0,3}
    Если конструкция выбора (альтернативный шаблон) в каждой ветви начинается с одного и того же литерала, то его надо "вынести за скобку" и, к примеру, вместо
    that|this
    записать
    th(?:at|is)

    Функция
    study( переменная с целевым текстом )
    является еще одной возможностью оптимизации использования регулярных выражений.
    Эта функция создает в переменной с целевым текстом скрытую информацию о тексте, который содержит эта переменная. Это список позиций, в которых каждый символ встречается в данном тексте. В результате на построение этого списка позиций расходуется время и память, которая обычно превосходит в четыре раза объем памяти для текста этой переменной. Поэтому выигрыша во времени можно не получить вовсе, если применять функцию study не там, где нужно. Функция study может существенно ускорить поиск, если в регулярном выражении имеется литеральный текст, но модификатор i сводит на нет усилия функции study. Также функция study может оказаться полезной, если к одной и той же переменной применяется много операторов с регулярными выражениями или если целевой текст имеет большой объем. При любом
    присваивании значения переменной, содержащей целевой текст, внутренняя информация, привязанная к этой переменной, становится недействительной.
    Для применения оптимизаций, которые возможны с функцией study, надо стараться выделять литеральный текст. Например, вместо
    a+
    надо написать эквивалентный подшаблон
    aa*
    а вместо
    a{3,6}
    нужно записать
    aaa{0,3}
    Если конструкция выбора (альтернативный шаблон) в каждой ветви начинается с одного и того же литерала, то его надо "вынести за скобку" и, к примеру, вместо
    that|this
    записать
    th(?:at|is)

    Хронометраж времени выполнения регулярных выражений

    В поставке Perl есть стандартный модуль Benchmark. Он позволяет измерять время выполнения участков кода. При этом можно учитывать только время, которое потрачено процессором на выполнение кода вашей программы, а не на всю систему. Механизм применения этого модуля таков:
    use Benchmark; … my $t1=new Benchmark;
    # Здесь находится участок кода, время работы которого измеряется …
    my $t2=new Benchmark; print timestr(timediff $t2,$t1);
    В переменной $t1 запоминается время начала исполнения участка кода, в переменной $t2 запоминается время окончания выполнения этого участка кода. Затем с помощью функций timediff и timestr выводится разница между временем окончания и временем начала работы участка кода, который тестируется. Но не забывайте, что при первом обращении к регулярному выражению тратится время на его компиляцию!
    В качестве примера применения хронометража времени поставим задачу скорейшего определения факта наличия между тегами непробельного символа. При этом предполагается, что все теги закрыты и нет вложенных тегов. Для этого разработаем такой алгоритм регулярного выражения:
    $_=' ' x 13000; $_.='a'; my $re=qr /\A # начало текста (?> # атомарная группировка (?: # цикл пропуска пробелов и тегов (?>\s*) # пропускаем пробельные символы <(?>[^>]*)> # пропускаем тег с его содержимым )* # повтор любое число раз ) \S # и вот он наконец - непробельный символ /x; my $t1=new Benchmark; for (1..1000000) { /$re/; } my $t2=new Benchmark; print timestr(timediff $t2,$t1);
    В этом примере избран такой подход: в цикле (?: … )* пропускаются пробельные символы и теги, а после окончания цикла таких пропусков должен встретиться непробельный символ \S. Если он встретится, то поиск завершится удачей. В переменной $_ создается длинная строка с тегами и пробелами, которая завершается непробельным символом a вне тегов. Чтобы время компиляции регулярного выражения> не вошло в интересующий нас интервал времени, мы используем объект регулярного выражения $re. Поскольку регулярное выражение работает быстро, создаем цикл из миллиона повторов применения этого регулярного выражения к тексту.
    При прогоне этой программы на моем компьютере выводится следующее значение времени выполнения заданного участка кода:
    1 wallclock secs ( 1.11 usr + 0.00 sys = 1.11 CPU)

    А если этот символ стоит вне тегов, то такого текста найдено не будет, поэтому будет истинен шаблон

    (?![^<>]*>)

    Вот вся эта программа:

    use Benchmark;

    $_=' ' x 13000; $_.='
    a'; my $re=qr /[^\s<>] (?![^<>]*>) /x; my $t1=new Benchmark; for (1..1000000) { /$re/; } my $t2=new Benchmark; print timestr(timediff $t2,$t1);

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

    1 wallclock secs ( 1.09 usr + 0.00 sys = 1.09 CPU)

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

    Теперь узнаем, в какую цену обходится встроенный код Perl. Добавим в последний пример встроенный код, который будет увеличивать счетчик:

    use Benchmark;

    $_=' ' x 13000; $_.='
    a'; my $count=0; my $re=qr /[^\s<>] (?![^<>]*>) (?{ ++$count }) /x; my $t1=new Benchmark; for (1..1000000) { /$re/; } my $t2=new Benchmark; print timestr(timediff $t2,$t1); print "\n$count";

    На печати появится:

    3 wallclock secs ( 3.44 usr + 0.00 sys = 3.44 CPU) 1000000

    Время выполнения увеличилось примерно в 3 раза. Сравним это со временем выполнения самого кода автоприращения:

    use Benchmark;

    my $count=0; my $t1=new Benchmark; for (1..1000000) { ++$count } my $t2=new Benchmark; print timestr(timediff $t2,$t1); print "\n$count";

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

    0 wallclock secs ( 0.13 usr + 0.00 sys = 0.16 CPU) 1000000

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

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

    use Benchmark;

    $_='Далее стоит 13 нулей: 0000000000000' x 13000; my $re=qr/(\d+)\D+(??{"0{$1}"})/; my $t1=new Benchmark; for (1..1000000) { /$re/; } my $t2=new Benchmark; print timestr(timediff $t2,$t1);


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

    4 wallclock secs ( 4.74 usr + 0.00 sys = 4.74 CPU)

    А сейчас заменим динамическое регулярное выражение подшаблоном \d+:

    use Benchmark;

    $_='Далее стоит 13 нулей: 0000000000000' x 13000; my $re=qr /(\d+)\D+\d+/; my $t1=new Benchmark; for (1..1000000) { /$re/; } my $t2=new Benchmark; print timestr(timediff $t2,$t1);

    В этот раз время уменьшится:

    1 wallclock secs ( 1.38 usr + 0.00 sys = 1.38 CPU)

    Замечаем, что встроенный код и динамические регулярные выражения даже в простом случае увеличивают время выполнения регулярного выражения примерно в 3 раза.

    Аналогично заметно возрастает время выполнения оператора подстановки s/// с модификатором e. Были практические случаи, когда оператор подстановки с модификатором e замедлял работу программы в десятки раз.

    © 2003-2007 INTUIT.ru. Все права защищены.

    Компиляция и кэширование регулярных выражений

    При первой встрече в тексте программы регулярного выражения Perl обрабатывает литерал этого выражения, а затем переводит его во внутреннюю форму (компилирует), которую уже использует механизм регулярных выражений.
    При компиляции сначала происходит обработка литералов регулярного выражения.
  • Отыскивается завершающий ограничитель регулярного выражения и читаются модификаторы всего регулярного выражения, которые стоят за этим ограничителем. Наличие модифиатора x учитывается при обработке литерала регулярного выражения.
  • Если регулярное выражение имеет интерполируемые переменные, то вместо них подставляется их значение. При этом учитывается, что последовательности символов $|, $), … не являются переменными и не интерполируются.
  • Далее обрабатываются конструкции изменения регистра /U, /l, … а также конструкции /Q…/E, но в тех частях регулярного выражения, которые получились от интерполяции переменных, эти конструкции не распознаются и не обрабатываются.

  • Потом, если не было замечено ошибок на шагах 1-3, регулярное выражение переводится в свою внутреннюю форму, готовую к применению механизмом регулярных выражений.
    Но регулярное выражение может применяться в цикле, что при каждой его компиляции потребует существенных затрат времени процессора. Perl запоминает внутреннее представление регулярного выражения после его компиляции и привязывает его к соответствующему регулярному выражению в программе. В последующем при обращении к данному регулярному выражению повторной компиляции не происходит, а используется его готовое внутреннее представление. Это кэширование регулярного выражения делается для регулярных выражений, которые не содержат интерполируемых переменных или имеют модификатор o. Переменные, содержащиеся во встроенном коде, и динамические регулярные выражения на кэширование регулярного выражения не влияют, поскольку не интерполируются, а входят в исполняемый код, который не изменяется между обращениями к данному регулярному выражению. Кэш регулярных выражений имеет ограниченные возможности, поэтому кэшируются лишь десять регулярных выражений, которые использовались последними (хотя в точности этого числа кэшируемых регулярных выражений я не уверен).
    Если регулярное выражение содержит интерполированную переменную, то в некоторых случаях Perl может проверить простым побайтовым сравнением, не изменилось ли содержимое этой переменной с прошлого применения регулярного выражения. Если оно не изменилось, то Perl не делает перекомпиляцию этого регулярного выражения.
    Отказ от перекомпиляции регулярного выражения дает очень существенную экономию времени, поэтому используйте объекты регулярных выражений qr/…/. Они содержат уже откомпилированное и готовое к использованию регулярное выражение, представляя собой как бы автономный кэш регулярного выражения.
    Если после интерполяции переменных регулярное выражение имеет пустое содержание, то вместо него применяется последнее успешно совпавшее регулярное выражение. Это можно использовать для оптимизации времени работы программы. Приведу такой пример:
    my $a='abc'; my $b='cde'; my $re='(\w)'; $a =~ /$re/; print "$1\n"; $b =~ //; print $1;

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

    a c

    Пояснение: в операторе $a =~ /$re/; мы использовали регулярное выражение, которое совпало, и напечаталась буква a. Далее мы можем множество раз задавать пустое регулярное выражение //. Вместо него будет использоваться это последнее совпавшее кэшированное регулярное выражение. Это видно при печати буквы c. Если теперь изменить содержимое переменной $re, то это не повлияет на результат применения пустого регулярного выражения, потому что интерполированное значение переменной $re (т.е. внутреннее представление литерала (\w)) уже находится в кэшированном регулярном выражении по умолчанию).

    Эта оптимизация не является естественной и с приходом объектов регулярных выражений она устарела.

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

    my $re=/…/; … if ($_ =~ $re) … или if (m/$re/) …

    то в этом операторе напрямую применяется откомпилированный объект регулярного выражения.

    Здесь уместно вспомнить об опасности применения модификатора o с объектами регулярных выражений:

    my $re=/…/o;

    который дает неожиданный и неприятный эффект, описанный ранее.

    Отладочная информация регулярных выражений

    Если применить директиву
    use re qw(debug);
    то Perl будет выдавать подробную отладочную информацию по работе регулярных выражений. Иногда в ней можно найти что-то полезное (скажем, какие методы оптимизации используются или в чем была ваша ошибка). Например, имеем такую программку
    use re qw(debug);
    'abcd' =~ /abc(d)/;
    На печать будет выдано:
    Compiling REx `abc(d)' size 9 Got 76 bytes for offset annotations. first at 1 1: EXACT (3) 3: OPEN1(5) 5: EXACT (7) 7: CLOSE1(9) 9: END(0) anchored `abcd' at 0 (checking anchored) minlen 4 Offsets: [9] 1[3] 0[0] 4[1] 0[0] 5[1] 0[0] 6[1] 0[0] 7[0] Guessing start of match, REx `abc(d)' against `abcd'... Found anchored substr `abcd' at offset 0... Guessed: match at offset 0 Matching REx `abc(d)' against `abcd' Setting an EVAL scope, savestack=3 0 <> | 1: EXACT 3 | 3: OPEN1 3 | 5: EXACT 4 <> | 9: END Match successful! Freeing REx: `"abc(d)"'
    Текст
    1: EXACT (3) 3: OPEN1(5) 5: EXACT (7) 7: CLOSE1(9) 9: END(0)
    является расшифровкой внутреннего представления регулярного выражения. Например, строка
    1: EXACT (3)
    означает, что надо искать строковый литерал abc длиной 3 символа. Строка
    anchored `abcd' at 0 (checking anchored) minlen 4
    означает, что текст abcd длины 4 привязан к началу регулярного выраения и должен встретиться при любом совпадении. Поэтому, если целевой текст окажется короче этого текста, то Perl не станет применять регулярное выражение, оператор поиска без применения сразу возвратит неудачу. Строка
    3: OPEN1(5)
    означает, что открылась первая захватывающая скобка, 5 означает номер следующей исполняемой строки псевдокода.

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

    Функция grep

    Функция grep является мощным инструментом для создания из списков подсписков по определенным критериям. Эта мощь обусловлена тем фактом, что данная функция может использовать регулярные выражения. Модификаторы этого регулярного выражения те же, что и у оператора split. Функция grep имеет две разновидности вызова:
    grep { блок команд } список
    и
    grep выражение, список
    Эта функция в неявном цикле по всем элементам списка для каждого из них выполняет заданный блок команд или выражение. Причем при каждой итерации цикла во время выполнерия блока команд или выражения переменная $_ является синонимом очередного элемента списка. В частности, присвоение этой переменной значения повлечет присвоение этого же значения соответствующему элементу списка. Функция в качестве результата возвращает подсписок, для которых вычисленное значение выражения/блока команд является истиной (отлично от нуля, пустой строки и т.д.). Например, код
    open F, 'prog.pl'; print grep { !/^#|^\s*$/m } ; close F;
    распечатает все строки файла prog.pl, которые не начинаются с символа # и не содержат одни лишь пробельные символы.

    Оператор split

    Оператор разбиения split выполняет в каком-то смысле противоположную роль оператору поиска m/// с модификатором g в списковом контексте. Если этот оператор m/// возвращает все фрагменты текста, которые совпали с регулярным выражением, то оператор split возвращает фрагменты текста, которые не совпали с регулярным выражением, заданным ему в качестве аргумента. Оператор split разбивает исходный текст на куски, которые разделяются текстом, совпадающим с заданным регулярным выражением и возвращает эти куски текста в виде массива. В качестве разделителя может выступать регуляр ное выражение, которое совпадает только с позицией в тексте, например, оператор
    @lines=split /^/m, $text;
    вернет в массиве @lines заданный текст, разбитый на логические строки.
    Замечу, что в регулярном выражении в операторе split якорь начала текста ^ означает начало логической строки, даже если модификатор m не задан, поэтому оператор
    @lines=split /^/, $text;
    будет работать точно так же, как и предыдущий оператор с модификатором m. Но можно это неявное правило отменить:
    @lines=split /(?-m)^/, $text;
    Это будет эквивалентно оператору
    @lines=split /\A/, $text;
    На метасимвол $ это неявное правило не распространяется.
    В регулярном выражении, которое применяется в операторе split, точно так же работает интерполяция переменных и модификатор o, как и в обычных регулярных выражениях.
    Оператор split можно использовать в простых случаях, например, когда надо разбить выходные данные от HTML-формы по знаку равенства, но этот оператор можно применять и в более сложных случаях. Однако, оператор split перегружен разными тонкостями и исключениями, которые надо рассмотреть. В общем случае этот оператор имеет вид
    split [ совпадение [ , целевой текст [ , ограничение ] ]]
    Список операндов при необходимости можно заключать в скобки. Третий операнд не обязателен. Если его значение равно нулю, то это эквивалентно тому, что он не задан.
    Второй операнд также не обязателен, но в случае его отсутствия также должен отсутствовать и третий операнд. Не обязателен и первый операнд, оператор
    split
    эквивалентен вызову
    split ' ', $_, 0
    где первый операнд является строкой, состоящий из одного пробела. Этот вызов разобьет текст, находящийся в $_, по пробельным символам, при этом начальные и заключительные пустые элементы созданы не будут.

    Отсутствие побочных эффектов у оператора split

    Оператор split не имеет побочных эффектов: он не изменяет регулярное выражение по умолчанию, не устанавливает специальные переменные $1, …, $`, $&, $' и т.д. Существует лишь один побочный эффект, не связанный с регулярными выражениями: при использовании оператора split в скалярном контексте он записывает возвращаемый результа в массив @_, который также используется для передачи параметров функциям.
    При применении директивы use warnings или параметра -w при запуске Perl, выдается соответствующее предупреждение:
    #!/usr/bin/perl -w use strict;
    scalar split /(?=\w)/, "abcd"; print @_;
    Напечатается
    Use of implicit split to @_ is deprecated at z.pl line 4. abcd
    Сказанное об отсутствии побочных эффектов не означает, что внутри регулярного выражения нумерованные переменные не создаются. Если их распечатать во встроенном коде Perl, то мы увидим их значение. Просто после окончания работы регулярного выражения восстанавливаются старые значения всех специальных переменных регулярных выражений. Например, оператор
    split /(\w)(?{ print "=$1=" })/, "abcd";
    напечатает текст
    =a==b==c==d=

    Первый операнд (совпадение)

    Рассмотрим варианты для первого операндаоператора split. Это может быть
  • регулярное выражение;
  • объект регулярного выражения;
  • любое другое выражение, которое будет интерпретироваться как строка.

  • Для регулярных выражений вы можете употреблять лишь базовые модификаторы i, x, s, m, o. Если вы хотите сгруппировать подшаблоны, то используйте несохраняющие круглые скобки, т.к. захватывающие скобки имеют в этом операнде особый смысл.

    Специальный первый операнд // и ' '

    Специальный первый операнд // (пустое регулярное выражение) означает вовсе не применение регулярного выражения по умолчанию в качестве ограничителя для разбивки текста, а этот операнд разбивает входной текст на отдельные символы, из которых он состоит. Например, оператор
    print join '-',split //, 'abcde';
    напечатает a-b-c-d-e.
    Специальный операнд ' ' (строка из одного пробела), разбивает заданный текст по пропускам \s+, но при этом начальные (и конечные) пробельные символы игнорируются.
    Например, оператор
    print join '-',split ' ', ' a b c de ';
    напечатает a-b-c-de, а оператор
    print join '-',split m/\s+/, ' a b c de ';
    напечатает -a-b-c-de. Как видим, первый оператор игнорирует начальные и конечные пробелы, а второй - только конечные.

    Третий операнд (ограничение)

    Роль этого необязательного операнда в том, чтобы ограничить число фрагментов, на которые будет разбит заданный текст. Если этот операнд отсутствует, то ограничения нет. Например, оператор
    print join '-', split ' ', ' a b c de ', 3;
    напечатает
    a-b-c de
    После возврата двух элементов вернется остаток заданного текста. Если в результате текст разобьется на число частей, которое меньше заданного, то дополнительные элементы не создаются. Например:
    print join '-', split ' ', ' a b c de ', 10;
    Напечатается: a-b-c-de-. Мы видим, что в этой форме оператор split в отличие от случая, когда третий операнд отсутствует, начинает возвращать конечные пустые элементы.
    Также можно ограничить количество возвращаемых значений, если в качестве приемника задать список:
    ($a,$b,$c) = split /$re/, $text;
    После заполнения заданного количества полей Perl прекратит работу оператора split.
    Если количество кусков текста меньше заявленного количества элементов списка, то оставшиеся элементы списка получат пустые значения.

    Возвращение пустых элементов

    Если между совпадениями для первого операнда нет текста (совпадения идут подряд), то на соответствующих местах возвращаются пустые элементы. Оператор
    print join '-', split '=', 'a=b==c';
    напечатает
    a-b--c
    В этом случае пустые элементы, расположенные в конце текста, не возвращаются.
    Например, оператор
    print join '-',split '=', '===a=b==c===';
    напечатает
    ---a-b--c
    Для получения этих пустых элементов надо использвать третий операнд. Проще всего его установить в -1 (или в большое положительное число), тогда вернутся все элементы, на которые разбит текст. Оператор
    print join '-', split '=', '===a=b==c===', -1;
    напечатает
    ---a-b--c---
    Число -1 здесь эквивалентно бесконечно большому положительному числу.
    Иногда бывает нужно получить только непустые элементы разбиения текста. Для этого перед оператором split можно поставить вызов функции grep с аргументом length:
    print grep { length } split '=', '===a=b==c===', -1;
    или вместо length можно подставить само возвращаемое в $_ split значение:
    print grep $_, split '=', '===a=b==c===', -1;
    Напечатается просто abc. К каждому элементу списка, возвращаемому оператором split, будет применена функция length. И если результат будет равен нулю, то этот элемент будет удален из данного списка.
    Мы уже видели, что пустые фрагменты, совпавшие в начале текста, возвращаются. Но если совпадение было без поглощения текста, а чисто позиционным, то эти пустые фрагменты не возвращаются. Например, оператор
    print join '-', split /(?=\w)/, "abcd";
    напечатает a-b-c-d, хотя перед буквой a тоже было совпадение.
    Обращаю ваше внимание, что оператор
    split /^/m, $text;
    вернет все логические строки, в том числе и пустые: ведь при совпадении с пустыми строками возвращаются элементы \n, которые сами не являются пустыми.

    Второй операнд (целевой текст)

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

    Захватывающие скобки в первом операнде оператора split

    Если в первом операнде оператора split имеются захватывающие скобки, то фрагменты текста, которые они захватили, также вставляютя в результирующий список между фрагментами текста, на которые разбился заданный текст. Эти фрагменты, совпавшие с захватывающими скобками, не учитываются в общем числе возвращаемых фрагментов, т.к. третий операнд определяет не число возвращаемых фрагментов, а количество фрагментов, на которые разбивается исходный текст. Если какая-либо пара захватывающих скобок не участвовала в совпадении, то она возвращает значение undef.
    Пример:
    print join '-', split /(\d+)/, 'a12b23c34d45e56', 3;
    Напечатается
    a-12-b-23-c34d45e56
    т.е. числа, по которым разбивался текст, заносятся в результат, чередуясь с самими фрагментами, на которые был разбит исходный тест. Текст был разбит на три части.
    Вот еще пример - HTML-текст разбивается на текст вне тегов и при этом возвращаются все теги:
    $_=<жирный текст
    обычный текст курсив

    заголовок

    обычный текст EOD
    print join '-', split /(<[^>]+>)/;
    Будет напечатано:
    --жирный текст-- обычный текст --курсив-- -

    - заголовок -

    - обычный текст
    Оператор split обычно используется при получении данных от форм HTML. Ниже приведен пример стандартной программы, которая принимает формы, отправленные по методу GET и POST, и записывает имена и значения переменных в ассоциативный хеш contents:
    if ($ENV{'REQUEST_METHOD'} eq 'POST') # Принимаем данные по методу POST { # Читаем переданные формой данные read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'}); # Разбиваем их на пары имя=значение @pairs=split(/&/, $buffer); foreach $pair (@pairs) { # Каждую пару разбиваем на имя и значение ($name,$value)=split(/=/,$pair); # Заменяем плюсы на пробелы (браузеры кодируют пробелы плюсами) $value =~ tr/+/ /; # Заменяем 16-ные байты их значениями $value =~ s/%([a-fA-F0-9][a-fA-f0-9])/pack("C",hex($1))/eg; # Записываем результат в хеш $contents{$name}=$value; } } elsif ($ENV{'REQUEST_METHOD'} eq 'GET') # Принимаем данные по методу GET { @pairs=split(/&/, $ENV{QUERY_STRING}); foreach $pair (@pairs) { ($name,$value)=split(/=/,$pair); $value =~ tr/+/ /; $value =~ s/%([a-fA-F0-9][a-fA-f0-9])/pack("C",hex($1))/eg; $contents{$name}=$value; } }
    print "Content-Type: text/html\n\n"; print 'OK';
    Эту программу можно сократить, т.к. в цикле производятся те же самые операции.

    

        Программирование: Языки - Технологии - Разработка