Регулярные выражения Perl и их применение
Альтернативные шаблоны
Символ "|", напоминающий операцию "или", играет в регулярных выражениях роль, схожую с этой логической связкой. Это конструкция выбора (альтернативы). Дойдя до нее, система поиска соответствия начинает с текущей позиции целевой строки сопоставлять ей все шаблоны из конструкции выбора (альтернативные шаблоны) в порядке их написания (т.е. слева направо). Используется первый совпавший шаблон, после чего оставшиеся шаблоны пропускаются и управление передается за конструкцию выбора. В качестве шаблонов могут выступать регулярные выражения любой сложности, а не только такие, как в нашем примере. Слова шаблон и регулярное выражение для нас являются синонимами.Оператор "|" имеет очень низкий приоритет, поэтому, если перед или после конструкции выбора имеется еще что-то, то ее надо заключить в скобки.
Во втором примере символ "^" означает начало строки, а символ "$" - ее конец. Точный смысл этих символов мы обсудим позже. Эти символы также называют мнимыми символами, т.к. они совпадают не с текстом, а с позицией в целевой строке. Также их еще называют условиями, якорными метасимволами или просто якорями.
Если бы во втором примере регулярное выражение было записано без скобок:
^stop|quit|exit|abort$
то оно бы означало следующее: либо stop в начале строки, либо слова quit или exit в любом месте строки, либо abort, стоящее в конце строки. А это было бы не то, что нам нужно.
Классы символов
Как быть, если в данной позиции целевой строки могут стоять (ожидаются) разные символы? Например, параметры тега HTML могут заключаться в апострофы, а также двойные кавычки. Здесь конструкцию выбора применять неудобно. Для этого существуют классы символов. Класс - это последовательность символов, которая заключена в квадратные скобки. Например, класс ["'] совпадает с апострофом и с двойной кавычкой.Заметьте, что класс символов всегда соответствует ровно одному символу целевой строки. Кроме того, нельзя создавать пустые классы символов, т.е. такие классы, которые не соответствуют ни одному символу. Транслятор не всегда может это проверить, и ошибка будет на совести программиста.
Классы имеют свои метасимволы, а некоторые метасимволы регулярных выражений внутри классов не действуют. Например, символ ] является метасимволом лишь внутри класса и поэтому должен быть в нем замаскирован: \]. А символ [ является метасимволом в регулярном выражении, но не внутри класса символов.
Внутри класса символов можно использовать диапазоны символов, например, класс [a-f0-9] - это то же, что [abcdef0123456789], но первая запись короче. Диапазон включает все промежуточные символы, чьи коды расположены между кодами крайних символов. Если вы захотите включить знак минус в класс символов, то его надо либо замаскировать обратным слэшем, либо поставить в самом начале или конце класса.
Символ "^" внутри класса уже не означает начала строки, мнимые символы внутри классов не имеют смысла и не используются. Символ "^", который стоит в самом начале класса, инвертирует этот класс, и такой инвертированный класс соответствет любому из символов, кроме перечисленных в этом классе.
Некоторые классы так часто используются, что для их обозначения придумали специальные эскейп-последовательности.
Таким образом, \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. Это связано с тем, что применение квантификаторов требует запоминания состояний, чтобы в последующем в случае неудачи поиска вернуться и попробовать варианты с другим числом повторений. Этот вопрос мы рассмотрим позднее.
Некоторые числители используются так часто, что для них сделали краткие формы записи:
Квантификаторы имеют высокий приоритет, поэтому шаблон 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. Но тогда необходимо иметь еще пару якорей, которые совпадают лишь в начале и конце текста. И такие якоря есть, их даже три.Еще имеется мнимый символ \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 "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 "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 является условием, которое всегда выполняется. Когда такой код попадается при движении в шаблоне слева направо, он выполняется. В этом коде можно проделать полезную работу, например, запомнить промежуточные значения нумерованных переменных, ведь позже поиск может завершиться неудачей. При прохождении встроенного кода в обратном направлении в случае возврата тоже могут происходить интересные и тонкие действия, которые мы рассмотрим в соответствующих лекциях. Но не вставляйте в этот код операторы перехода 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///
Всего в регулярных выражениях используется восемь модификаторов.если вы уверены, что интерполируемая переменная (или массив) не меняет своего значения, то можете поставить к такому регулярному выражению модификатор o, чтобы избежать аго многократной компиляции. Этот модификатор может стоять только после всего регулярного выражения.
Оператор m// в режиме однократного поиска и в скалярном контексте
В режиме однократного поиска (т.е. без модификатора g) и в скалярном контексте оператор m// возвращает логическое значение: целое число 1, если поиск оказался успешным, или пустую строку в случае неудачи. Если в шаблоне имелись захватывающие скобки, то при успешном поиске создаются нумерованные переменные $1, $2, …, которые содержат соответствующие фрагменты захваченного текста. Если поиск оказался неудачным, то эти переменные хранят последнее состояние от предыдущего оператора поиска или замены.После успешного поиска также можно использовать специальные переменые:
Теперь буду давать пояснения к этому списку. Во-первых, имейте в виду, что переменная $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, за которым идут три точки.
Это был практический пример задачи, которую я решал по просьбе одного вебмастера.
Предположим, мы хотим узнать, есть ли в данном тексте непробельный символ между тегами. Предполагается, что теги правильно закрыты и нетеговых символов < и > не встречается. Вот первое решение:
$_='