The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME/НАИМЕНОВАНИЕ

perlretut - Учебник регулярных выражений Perl

ОПИСАНИЕ

Этот документ является базовым учебником по пониманию, созданию и использованию регулярных выражений в Perl. Он служит в качестве дополнения к справочной странице о регулярных выражениях perlre. Регулярные выражения являются неотъемлемой частью операторов m//, s///, qr// и split , также этот учебник пересекается со страницами "Регексп операторы заключения в кавычки" in perlop и "split" in perlfunc.

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

Что такое регулярное выражение? Регулярное выражение это просто строка , которая описывает образец или шаблон, который мы должны найти. Шаблон поиска используются теперь повсеместно; примерами являются шаблоны, которые вводятся в поисковую систему для поиска веб-страниц и шаблоны, используемые для получения списка файлов в директории, например, ls *.txt или dir *.*. В Perl, шаблоны, описанные регулярными выражениями, используются для поиска строк, извлечения нужных частей строк, и для операций поиска и замены.

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

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

Примечание: чтобы сэкономить время, 'регулярное выражение' часто сокращают как регексп или регекс. Регексп - более естественная аббревиатура, чем регекс, но труднее произносится. В документации Perl pod равномерно распределяются сокращения Регексп и Регекс; в Perl существует более одного способа сокращать его.(there is more than one way to abbreviate it!) В этом руководстве мы будем использовать регексп.

Часть 1: Основы

Простой поиск слова

Простейший регексп это просто слово, или, более обще, строка символов. Регексп, состоящий из слова, соответствует любой строке, содержащей это слово:

    "Hello World" =~ /World/;  # совпадение

О чём это выражение? "Hello World" - это проста строка, заключённая в двойные кавычки. World - это регулярное выражение и заключенное в // слово /World/ говорит Perl искать вхождение строки. Оператор =~ связывает строку с регекспом и выдает значение true если регексп совпадает, или false если нет. В нашем случае, World совпадает со вторым словом в "Hello World", таким образом выражение истинно. У этой идеи есть несколько вариаций. Такие выражения полезны в условных операторах:

    if ("Hello World" =~ /World/) {
        print "Найдено\n";
    }
    else {
        print "Не Найдено\n";
    }

Есть много полезных вариантов на эту тему. Смысл совпадения может быть полностью изменен при помощи оператора !~:

    if ("Hello World" !~ /World/) {
        print "Не Найдено\n";
    }
    else {
        print "Найдено\n";
    }

Последовательность строк в регексе может быть заменена переменной:

    $greeting = "World";
    if ("Hello World" =~ /$greeting/) {
        print "Найдено\n";
    }
    else {
        print "Не Найдено\n";
    }

Если вы ищите в специальной переменной по умолчанию $_, то эту часть $_ =~ можно опустить:

    $_ = "Hello World";
    if (/World/) {
        print "Найдено\n";
    }
    else {
        print "Не Найдено\n";
    }

Наконец, разделители по умолчанию // для поиска могут быть изменены на произвольные разделители, но тогда в начале должны быть буква 'm':

    "Hello World" =~ m!World!;   # найдено, разделители '!'
    "Hello World" =~ m{World};   # найдено, отметим совпадение '{}'
    "/usr/bin/perl" =~ m"/perl"; # найдено после '/usr/bin',
                                 # '/' стал произвольным символом

/World/, m!World! и m{World} все представляют собой то же самое. Например, когда кавычки (") используются в качестве разделителя, то косая черта '/' становится обычным символом и может быть использован в этом регекспе без проблем. Давайте рассмотрим, каким образом различные регекспы будет соответствовать "Hello World":

    "Hello World" =~ /world/;  # не найдено
    "Hello World" =~ /o W/;    # найдено
    "Hello World" =~ /oW/;     # не найдено
    "Hello World" =~ /World /; # не найдено

Первый регексп world не совпадает, потому регексп учитывает регистр. Второй регексп совпадает потому что подстрока 'o W' встречается в строке "Hello World". Пробельный символ ' ' рассматривается как любой другой символ в регекспе и будет найден в этом случае. Отсутствие пробела является причиной того, почему третий регексп 'oW' не совпадает. Четвертый регексп 'World ' не совпадает, потому что есть пробел в конце регекспа, но не в конце строки. Урок здесь состоит в том, что регекспы должны точно совпадать с частью строки для того, чтобы выражение стало истинным.

Если регексп находится больше одного раза в строке, Perl будет всегда находить самое первое совпадение в строке:

    "Hello World" =~ /o/;       # найдет 'o' в 'Hello'
    "That hat is red" =~ /hat/; # найдет 'hat' в 'That'

Что касается соответствия символов есть еще несколько пунктов, которые нужно знать. Во-первых, не все символы могут использоваться, 'как есть' в поиске. Некоторые, называемые метасимволами, зарезервированы для использования в описании регекса. Метасимволы это

    {}[]()^$.|*+?\

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

    "2+2=4" =~ /2+2/;    # не найдет, т.к. + это метасимвол
    "2+2=4" =~ /2\+2/;   # найдено, \+ считается обычным знаком +
    "The interval is [0,1)." =~ /[0,1)./     # будет синтаксическая ошибка!
    "The interval is [0,1)." =~ /\[0,1\)\./  # найдет
    "#!/usr/bin/perl" =~ /#!\/usr\/bin\/perl/;  # найдет

В последнем регекспе перед первой косой чертой '/' также ставиться обратная косая черта, потому что косая черта здесь используется, чтобы разграничить регексп. Это может привести к СВЗ (синдрому выступающей зубочистки), однако, выражение можно сделать более читаемым, если изменить разделители.

    "#!/usr/bin/perl" =~ m!#\!/usr/bin/perl!;  # читается легче

Символ обратной косой черты '\' является метасимволом и для его поиска нужна еще одна косая черта:

    'C:\WIN32' =~ /C:\\WIN/;   # найдет

В добавлении к метасимволам, есть некоторые символы ASCII которые не имеют печатных аналогов и вместо этого представлены escape-последовательностями. Типичными примерами являются \t для табуляции, \n для символа перехода на новую строку, \r для возврата каретки и \a для пищалки (или сигнала предупреждения). Если о строке думать как о последовательности произвольных байт, то восьмеричные escape-последовательности, например, \033, или шестнадцатеричные escape-последовательности, например, \x1B могут быть более естественным представлением для ваших байт. Вот несколько примеров escape-последовательностей:

    "1000\t2000" =~ m(0\t2)   # найдет
    "1000\n2000" =~ /0\n20/   # найдет
    "1000\t2000" =~ /\000\t2/ # не найдет, "0" не равен "\000"
    "cat"   =~ /\o{143}\x61\x74/ # найдено в кодах ASCII, хотя и странный способ
                                 # искать cat

Если вы уже использовали Perl какое-то время, все эти разговоры об escape-последовательностях могут показаться знакомыми. Подобные escape-последовательности используются в двойных кавычках и фактически регекспы в Perl в основном рассматриваются как строки в двойных кавычках. Это означает, что переменные также могут использоваться в регекспах. Подобно строкам в двойных кавычках, значение переменной в регекспе будет подставлено прежде чем регексп выполнит поиск. Итак, мы имеем:

    $foo = 'house';
    'housecat' =~ /$foo/;      # найдено
    'cathouse' =~ /cat$foo/;   # найдено
    'housecat' =~ /${foo}cat/; # найдено

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

    % cat > simple_grep
    #!/usr/bin/perl
    $regexp = shift;
    while (<>) {
        print if /$regexp/;
    }
    ^D

    % chmod +x simple_grep

    % simple_grep abba /usr/dict/words
    Babbage
    cabbage
    cabbages
    sabbath
    Sabbathize
    Sabbathizes
    sabbatical
    scabbard
    scabbards

Эту программу легко понять. #!/usr/bin/perl это стандартный способ вызвать программу Perl из командной строки. $regexp = shift; сохраняет первый аргумент командной строки, как регексп, который будет использоваться, а оставшиеся аргументы командной строки рассматриваются как файлы. while (<>) перебирает все строки во всех входящих файлах. Для каждой строки, print if /$regexp/; напечатает строку, если регексп будет в ней найден. В этой строке, оба оператора print и /$regexp/ используют переменную по умолчанию $_ неявно.

Во всех регекспах выше, если регексп находит соответствие в любой точке строки, то он считается найденным. Иногда однако, мы хотели бы указать где в строке регексп должен искать. Чтобы указать где он должен быть найден, мы будем использовать якорем метасимволы ^ и $. Якорь ^ означает поиск в начале строки , а якорь $ означает поиск в конце строки или перед символом перевода строки в конце строки. Вот как они используются:

    "housekeeper" =~ /keeper/;    # найдено
    "housekeeper" =~ /^keeper/;   # не найдено
    "housekeeper" =~ /keeper$/;   # найдено
    "housekeeper\n" =~ /keeper$/; # найдено

Второй регексп не найдет, т.к. ^ вынуждает, чтобы keeper искался только в начале строки, но "housekeeper" содержит keeper начинающийся посередине. Третий регексп найдет, т.к. $ принуждает keeper искаться только в конце строки.

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

    "keeper" =~ /^keep$/;      # не найдет
    "keeper" =~ /^keeper$/;    # найдет
    ""       =~ /^$/;          # ^$ находит пустую строку

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

    "dogbert" =~ /bert/;   # найдет, но не то, что хотим

    "dilbert" =~ /^bert/;  # не найдет, но ..
    "bertram" =~ /^bert/;  # найдет, но пока еще не так хорошо

    "bertram" =~ /^bert$/; # не найдет, хорошо
    "dilbert" =~ /^bert$/; # не найдет, хорошо
    "bert"    =~ /^bert$/; # найдет, идеально

Конечно, в случае со строковой константой, ее можно было бы найти гораздо проще просто использовав сравнение строк $string eq 'bert' и это было бы более эффективным. Regexp ^...$ действительно становится полезным, когда мы добавляем более мощные конструкции в regexp.

Использование классов символов

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

Одна из таких концепций это классы символов. Класс символов позволяет создавать набор возможных символов, а не только одного, чтобы соответствовать определенной точке регекспа. Классы символов обозначаются квадратными скобками <[...]>, с набором возможных для сопоставления символов внутри. Вот несколько примеров:

    /cat/;       # найдет 'cat'
    /[bcr]at/;   # найдет 'bat, 'cat', или 'rat'
    /item[0123456789]/;  # найдет 'item0' или ... или 'item9'
    "abc" =~ /[cab]/;    # найдет 'a'

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

    /[yY][eE][sS]/;      # найдет 'yes' в регистронезависимом поиске
                         # 'yes', 'Yes', 'YES', etc.

Последний пример показывает обычную задачу: сделать регистронезависимый поиск. Perl предоставляет путь, чтобы исключить все эти скобки, просто добавив 'i' в конце поисковой строки. Тогда /[yY][eE][sS]/; можно переписать как /yes/i;, где 'i' означает без учета регистра и является примером модификатора операции поиска. Мы встретим другие модификаторы в этом руководстве позднее.

Мы видели выше в разделе, что там были обычные символы, которые представляли сами себя и специальные символы, которым необходима обратная косая черта \ в начале, чтобы представлять себя. То же самое верно в классах символов, но наборы обычных и специальных символов внутри класса символов отличаются от тех, которые есть за его пределами. Специальными символами для класса символов являются -]\^$ (и разделитель шаблона , каким бы он ни был). ] - специальный, потому что оно обозначает конец класса символов. $ - специальный, потому что он обозначает скалярную переменную. \ - специальный потому что он используется в escape-последовательности, как показано выше. Вот как обрабатываются специальные символы ]$\:

   /[\]c]def/; # найдет ']def' или 'cdef'
   $x = 'bcr';
   /[$x]at/;   # найдет 'bat', 'cat', или 'rat'
   /[\$x]at/;  # найдет '$at' или 'xat'
   /[\\$x]at/; # найдет '\at', 'bat, 'cat', или 'rat'

Последние два немного сложнее. В [\$x], обратный слеш защищает символ доллара , таким образом класс символов включает 2 символа $ и x. В [\\$x], обратный слеш защищен, таким образом $x воспринимается как переменная и заменяется в стиле двойных кавычек.

Специальный символ '-' представляет из себя оператор диапазона внутри класса, так что непрерывный набор символов может быть записан, как диапазон. С диапазоном, громоздкое [0123456789] и [abc...xyz] становиться стройным [0-9] и [a-z]. Вот примеры

    /item[0-9]/;  # найдет 'item0' или ... или 'item9'
    /[0-9bx-z]aa/;  # найдет '0aa', ..., '9aa',
                    # 'baa', 'xaa', 'yaa', or 'zaa'
    /[0-9a-fA-F]/;  # найдет шестнадцатиричную цифру
    /[0-9a-zA-Z_]/; # найдет символ "слова",
                    # которое совпадает с именем переменной в Perl

Если '-' будет первым или последним символом в классе символов, то он считается обычным тире; [-ab], [ab-] и [a\-b] эквивалентны.

Специальный символ ^ в первой позиции класса означает отрицание класса символов, которое найдет любой символ, который не присутствует в списке в квадратных скобках. Оба и [...] и [^...] должны найти символы или поиск будет неудачным. Тогда

    /[^a]at/;  # не найдет 'aat' или 'at', но найдет
               # все другие 'bat', 'cat, '0at', '%at', и т.д.
    /[^0-9]/;  # найдет не цифровой символ
    /[a^]at/;  # найдет 'aat' или '^at'; здесь '^' - обычный символ

Теперь, даже писать несколько раз [0-9] может надоесть , так появляется интерес сократить число нажатий клавиш и сделать регулярные выражения удобочитаемыми, Perl имеет несколько сокращений для общих классов символов, как показано ниже. С момента введения Юникода, если нет модификатора //a, то этот класс символов найдет больше, чем просто несколько символов в диапазоне ASCII.

  • \d найдет цифру, не только [0-9] но еще и цифры не романских языков

  • \s найдет пробельный символ, набор [\ \t\r\n\f] и другие

  • \w найдет словесный символ (буквацифру или _), не только [0-9a-zA-Z_] но и цифры и символы из нероманских языков

  • \D отрицает \d; представляет другие символы, отличные от цифр, или [^\d]

  • \S отрицает \s; представляет любой не пробельный символ [^\s]

  • \W отрицает \w; представляет любой не словесный символ [^\w]

  • Точка '.' найдет любой символ, кроме "\n" (пока не работает модификатор //s, как объясняется позднее).

  • \N, как и точка, найдет любые символы кроме "\n", но делает это независимо, даже, если присутствует модификатор //s.

Модификатор //a появился начиная с Perl 5.14, используется, чтобы ужесточить поиск \d, \s, и \w и только в ASCII диапазоне. Полезно не полностью переводить вашу программу на полный Юникод ( в частности и по вопросам безопасности ), когда все, что вам нужно, это обработка английскоподобных текстов. ("a" может быть удвоено , используйте //aa, чтобы сделать больше ограничений , предотвращая регистрозависимый поиск ASCII в не-ASCII символах; в противном случае Юникодный "символ Кельвина" будет бессистемно соответствовать "k" или "K".)

Сокращения \d\s\w\D\S\W могут быть использованы, как внутри, так и снаружи класса символов. Вот примеры использования:

    /\d\d:\d\d:\d\d/; # найдет время в формате hh:mm:ss
    /[\d\s]/;         # найдет любую цифру или пробельный символ
    /\w\W\w/;         # найдет словесный символ, за которым несловесный символ,
                      # за которым символ слова
    /..rt/;           # найдет любые 2 символа и за ними 'rt'
    /end\./;          # найдет 'end.'
    /end[.]/;         # также найдет 'end.'

Т.к. точка это метасимвол, его нужно заэскейпить, чтобы найти обычную точку. Поэтому, например, \d и \w - это наборы символов , неверно думать, о [^\d\w] как [\D\W]; фактически [^\d\w] тоже, что и [^\w], что тоже самое, что и [\W]. Думайте законами Де Моргана!

Один из самых удобных якорей в регекспах является якорь границы слова \b. Находит границу между словным символом и и несловным символом \w\W или \W\w:

    $x = "Housecat catenates house and cat";
    $x =~ /cat/;    # найдет cat в 'housecat'
    $x =~ /\bcat/;  # найдет cat в 'catenates'
    $x =~ /cat\b/;  # найдет cat в 'housecat'
    $x =~ /\bcat\b/;  # найдет 'cat' в конце строки

Замечание к последнему примеру, конец строки здесь подразумевает и конец слова.

Вы можете спросить, почему '.' найдет все, кроме "\n" - почему не все символы? Причина в том, что часто при сравнении со строкой хотят игнорировать символы новой строки. К примеру, в то время как строка "\n" представляет одну строку, мы хотели бы думать о ней, как о пустой строке. Тогда

    ""   =~ /^$/;    # найдет
    "\n" =~ /^$/;    # найдет, $ якорь после "\n"

    ""   =~ /./;      # не найдет; требуется символ
    ""   =~ /^.$/;    # не найдет; требуется символ
    "\n" =~ /^.$/;    # не найдет; требуется символ отличный от "\n"
    "a"  =~ /^.$/;    # найдет
    "a\n"  =~ /^.$/;  # найдет, $ якорь после "\n"

Такое поведение удобно, потому что мы обычно хотим игнорировать символы новой строки, когда мы рассчитываем найти символы в строке. Иногда, однако мы хотим отслеживать символы новой строки. Мы, возможно, даже хотим поставить ^ и $ как якорь в начале и в конце линий в пределах строки, а не только начало и конец строки. Perl позволяет нам выбирать между игнорированием и обращением внимания на символ новой строки с помощью модификаторов //s и //m. //s и //m устанавливается для однострочной и многострочной строки и они определяют, рассматривать ли строку , как одну непрерывной линию или как набор линий. Два модификаторы влияют на два аспекта того, как регексп будет интерпретироваться: 1) как '.' определяется класс символов и 2) где могут найтись якори ^ и $. Ниже приведены четыре возможных комбинации:

  • без модификаторов (//): Поведение по умолчанию. '.' найдет любой символ кроме "\n". ^ найдет только начало строки и $ найдет только конец или перед новой строкой в конце.

  • s модификатор (//s): Представляет строку как единую длинную линию. '.' найдет любой символ, даже "\n". ^ найдет только начало строки и $ найдет только конец перед началом новой строки в конце.

  • m модификатор (//m): Представляет строку как набор строк. '.' найдет любой символ, кроме "\n". ^ и $ могут найти начало и конец любой линии внутри строки.

  • оба модификатора s и m (//sm): Представляют строку как одну длинную линию, но позволяют найти множество строк. '.' найдет любой символ, даже "\n". ^ и $, тем не менее, могут найти начало и конец любой линии внутри строки.

Вот примеры //s и //m в действии:

    $x = "There once was a girl\nWho programmed in Perl\n";

    $x =~ /^Who/;   # не найдет, "Who" не является началом строки
    $x =~ /^Who/s;  # не найдет, "Who" не является началом строки
    $x =~ /^Who/m;  # найдет, "Who" является началом второй строкиm
    $x =~ /^Who/sm; # найдет, "Who" является началом второй строкиm

    $x =~ /girl.Who/;   # не найдет, "." не найдет "\n"
    $x =~ /girl.Who/s;  # найдет, "." найдет "\n"
    $x =~ /girl.Who/m;  # не найдет, "." не найдет "\n"
    $x =~ /girl.Who/sm; # найдет, "." найдет "\n"

Большую часть времени, поведение по умолчанию - это то, что мы хотели, но //s и C <//m>, иногда очень полезны. Если используется //m, началу строки по-прежнему может быть сопоставлен \A и в конец строки все еще может быть сопоставлен с якорем \Z (найти оба конец строки и символ новой строки перед ним, подобно $) и \z (находит только конец строки):

    $x =~ /^Who/m;   # найдет, "Who" в начале строки
    $x =~ /\AWho/m;  # не найдет, "Who" не является началом всей строки

    $x =~ /girl$/m;  # найдет, "girl" в конце первой строки
    $x =~ /girl\Z/m; # не найдет, "girl" не является концом всей строки

    $x =~ /Perl\Z/m; # найдет, "Perl" это новая строка в конце всей строки поиска
    $x =~ /Perl\z/m; # не найдет, "Perl" не в конце строки

Теперь мы знаем, как сделать выбор среди классов символов в регекспе. Как насчет выбора среди словесных или символьных строк? Такие варианты описаны в следующем разделе.

Найти то или это

Иногда мы хотим, чтобы наш регексп имел возможность искать разные слова или символьные строки. Это достигается с помощью метасивола или '|'. Чтобы найти dog или cat, мы создаем регекс dog|cat. Как и прежде, Perl пытается найти соответствие в самом раннем месте строки. В каждой позиции символа Perl сначала пытается сопоставить первую альтернативу, dog. Если dog не найден, Perl теперь пробует следующую альтернативу, cat. Если cat тоже не находит, тогда поиск считается неудавшимся и Perl перемещается к следующей позиции в строке. Вот ряд примеров:

    "cats and dogs" =~ /cat|dog|bird/;  # найдет "cat"
    "cats and dogs" =~ /dog|cat|bird/;  # найдет "cat"

Даже при том, что dog является первой альтернативой во втором регексе, cat находится раньше в строке.

    "cats"          =~ /c|ca|cat|cats/; # находит "c"
    "cats"          =~ /cats|cat|ca|c/; # найдет "cats"

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

    "cab" =~ /a|b|c/ # найдет "c"
                     # /a|b|c/ == /[abc]/

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

Группировка элементов и иерархический поиск

Чередование позволяет регекспу выбирать среди альтернатив, но само по себе это является неудовлетворительным. Причина в том, что каждый вариант представляет собой единый регексп, но иногда мы хотим альтернатив для части регекспа. Например, предположим, мы хотим искать housecats или housekeeper. Регексп housecat|housekeeper вписывается в нашу задачу, но является неэффективным, потому что нам пришлось дважды писать house. Было бы неплохо иметь постоянную часть регекспа С<house> , а другие части будут иметь альтернативу, как cat|keeper.

Группирующие метасимволы () решают эту проблему. Группировка позволяет рассматривать части регекса как отдельную единицу. Части регекса сгруппированы по ограждающим их скобкам. Таким образом мы можем заменить регексп housecat|housekeeper на house(cat|keeper). Регексп house(cat|keeper)означает поиск house за которым следует либо cat либо keeper. Вот несколько примеров

    /(a|b)b/;    # найдет 'ab' или 'bb'
    /(^a|b)c/;   # найдет 'ac' в начале строки или 'bc' где угодно

    /(a|b)b/;    # найдет 'ab' или 'bb'
    /(ac|b)b/;   # найдет 'acb' или 'bb'
    /(^a|b)c/;   # найдет 'ac' в начале строки или 'bc' где угодно
    /(a|[bc])d/; # найдет 'ad', 'bd', или 'cd'

    /house(cat|)/;  # найдет или 'housecat' или 'house'
    /house(cat(s|)|)/;  # найдет или 'housecats' или 'housecat' или
                        # 'house'. Обратите внимание на то, что группы могут быть вложены.

    "20" =~ /(19|20|)\d\d/;  # находит пустой элемент выбора '()\d\d',
                             # т.к. '20\d\d' не может найти

Группирующие метасимволы () позволяют еще извлекать части найденной строки. Каждые найденные группы попадают в специальные переменные $1, $2, и т.д. Они могут быть использованы как обычные переменные:

Элементы выбора ведут себя одинаково как в группах, так и вне их: в данной позиции строки, крайняя левая альтернатива позволяет регекспу выполнить поиск. Так, в последнем примере на первой позиции строки "20" соответствует второй вариант, но нет ничего слева, чтобы найти следующие две цифры \d\d. Поэтому Perl переходит к следующему элементу выбора, которым является null и это работает, так как "20" состоит из двух цифр.

Процесс пытается найти первый элемент выбора, смотрит, есть ли совпадение, и переходя к следующему элементу, при этом возвращаясь в строке где попробовали предыдущую альтернативу,если она была неудачна, это называется возвратом (backtracking). Термин "возврат" приходит от идеи, что поиск регекспа похож на прогулку в лесу. Успешный поиск регекспа это как это как прибытие в пункт назначения. Существует множество возможных троп одна для каждой позиции строки и каждая из них пытался в порядке слева направо. У каждой тропы может быть много путей, некоторые из них успешные, а некоторые являются тупиками. Когда вы прогуливаетесь по тропе и попадаете в тупик, вам придется отступать вдоль тропы до более ранней точки, чтобы попробовать другой след. Если вы доберетесь до пункта назначения, вы немедленно остановитесь и забудете об остальных тропах. Вы стойкие и только, если вы попробовали все трассы от всех троп и не прибудете в пункт назначения, вы объявляете провал. Чтобы быть конкретным, вот пошаговый анализ того, что делает Perl, когда он пытается найти регексп

    "abcde" =~ /(abd|abc)(df|d|de)/;
0

Начинает с первой буквы в строке 'a'.

1

Пробует первый вариант в первой группе 'abd'.

2

Находим, что после 'a' следует 'b'. Пока все в порядке.

3

'd' в регекспе regexp не соответствует 'c' в последовательности - мертвый конец. Так возвращаются два знака и выбирают вторую альтернативу в первой группе 'abc'.

4

Находим 'a' за которым 'b' за которым 'c'. Еще один успех (We are on a roll) и поймали первую группу. Устанавливаем 1$ в 'abc'.

5

Переходим ко второй группе и выбираем первый вариант 'df'.

6

Находим 'd'.

7

регексп 'f' не не соответствует 'e' в строке, поэтому это мертвый конец. Возвращаемся на один символ и выбираем второй вариант во второй группе 'd'.

8

'd' найдено. Вторая группировка найдена, и $2 равна 'd'.

9

Мы в конце регекспа, и так, мы сделали это! Мы нашли 'abcd' в строке "abcde".

Есть несколько важных вещей,которые стоит сказать об этом анализе. Во-первых, третий вариант во второй группе 'de' также находится, но мы останавливаемся прямо перед ним,в данной позиции символ слева выигрывает. Во-вторых, мы могли получить успешный поиск с позиции первого символа строки 'a'. Если бы не было совпадений на первой позиции, Perl будет двигаться на вторую позицию символа 'b' и попытается найти все снова. Только, когда все возможные пути на всех возможных позициях символов были исчерпаны, Perl сдается и объявляет $string =~ /(abd|abc)(df|d|de)/; ложным.

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

Извлечение найденного

Метасимволы группировки () также служат для совершенно другой функции: они позволяют извлекать частей найденной строки. Бывает очень полезно узнать, что найдено и в целом для обработки текста. Для каждой группы, часть, найденная внутри скобок кладется в специальные переменные $1, $2, и др. Они могут использоваться так же, как обычные переменные:

    # извлекаем часы, минуты, секунды
    if ($time =~ /(\d\d):(\d\d):(\d\d)/) {    # найде hh:mm:ss формат
        $hours = $1;
        $minutes = $2;
        $seconds = $3;
    }

Теперь мы знаем, что в скалярном контексте, Now, we know that in scalar context, $time =~ /(\d\d):(\d\d):(\d\d)/ вернет значение правды или лжи. В контексте списка, однако, он вернет список найденных значений ($1,$2,$3). Таким образом, мы можем написать код более компактно:

    # извлекаем часы, минуты, секунды
    ($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/);

Если группы в регекспе вложенные, то C <$1> получает группу с самой левой открывающей скобкой, C <$2> следующая открывающая скобка, и т.д. Вот регексп с вложенными группами:

    /(ab(cd|ef)((gi)|j))/;
     1  2      34

Если регексп найдет, $1 содержит строку, начинающуюся с 'ab', $2 равно либо 'cd' либо 'ef', $3 равно либо 'gi' либо 'j', и $4 будет 'gi', как и $3, или останется неопределенной (undefined).

Для удобства, Perl устанавливает C < $+ настроек в строку, проведенных самым высоким номером C <$1>, C <$2>,..., который получил назначен (и несколько связанных, C <$^N> Большинство недавно назначенное значение C <$1>, C <$2>,..., т.е. C <$1>, C <$2>,..., связанные с правом скобка используется в матч). Для удобства, Perl устанавливает $+ для строки содержащей наивысший номер $1, $2,... который был присвоен (и несколько связанных, $^N со значениями $1, $2,... самые недавно присвоенные; т.е. $1, $2,... ассоциируются с самыми правыми закрывающимися скобками в шаблоне поиска).

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

Тесно связаны с переменными поиска $1, $2,..., Обратные ссылки \g1, \g2... Обратные ссылки являются соответствующими переменными, которые могут быть использованы внутри регекспа. Это действительно приятная особенность; то, что найденное в конце регулярного выражения зависит от того, что найдено ранее в регекспе. Предположим, что мы хотим найти слово, встречающееся в тексте два раза подряд, такое как 'the the'. Следующий регексп находит все пары 3-х буквенных слов с пробелом между ними:

    /\b(\w\w\w)\s\g1\b/;

То, что найдено в группирующих скобках присваивается \g1, таким образом такие 3-х буквенные последовательности используются для обоих частей.

Аналогичная задача по поиску слов, состоящих из 2-х одинаковых частей:

    % simple_grep '^(\w\w\w\w|\w\w\w|\w\w|\w)\g1$' /usr/dict/words
    beriberi
    booboo
    coco
    mama
    murmur
    papa

Регексп имеет единую группировку, которая рассматривает 4-х буквенные комбинации, а затем 3-х буквенные сочетания и т.д. и использует \g1, чтобы искать повторения. Хотя $1 и \g1 представляют собой то же самое, аккуратность должна быть в использовании соответствующих переменных поиска $1, $2,... только вне регекспа, а обратных ссылок \g1, \g2,... только внутри регекспа; не выполнение этих требований может привести к удивлению и неудовлетворительным результатам.

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

Подсчет открытых круглых скобок, чтобы получить правильный номер для обратной ссылки приводит к ошибкам, как только есть более чем одна группа захвата. Более удобная техника стала доступна с Perl 5.10: относительные обратные ссылки. Для обозначения сразу первой предыдущей группы захвата можно записать \g{-1}, но следующий последний доступен через C <\g{-2}> и так далее.

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

    $a99a = '([a-z])(\d)\g2\g1';   # найдет a11a, g22g, x33x, и так далее.

Теперь, когда мы сохранили этот шаблон как удобную последовательность, мы могли бы почувствовать соблазн использовать его в качестве части некоторого другого образца:

    $line = "code=e99e";
    if ($line =~ /^(\w+)=$a99a$/){   # неожиданное поведение!
        print "$1 is valid\n";
    } else {
        print "bad line: '$line'\n";
    }

Но шаблон не находится, по крайней мере не так, как можно было бы ожидать. Только после вставки интерполированного $a99a и глядя на итоговый полный текст регекспа становится очевидным, что обратные ссылки приводят к неприятным последствиям. Подвыражение (\w+) захватывает номер 1 и понижает группы в $a99a на один ранг. Этого можно избежать использованием относительных обратных ссылок:

    $a99a = '([a-z])(\d)\g{-1}\g{-2}';  # безопасно для интерполяции

Именованные обратные ссылки

Perl 5.10 также представил именованные группы захвата и именованные обратные ссылки. Чтобы присоединить имя к группе захвата, вы пишете, либо <(?<name...)>> или (?'name'...). Обратную ссылку можно потом записать, как \g{name}. Это допустимо для присоединения одного и того же имени более чем одной группе, но потом ссылаться можно будет только на самую левую скобку из одноименного набора. За пределами шаблона именованная группа захвата доступна через хэш %+.

Предположим, что нам нужно найти календарные даты, которые могут быть предоставлены в одном из трех форматы yyyy-mm-dd, mm/dd/yyyy или dd.mm.yyyy, мы можем написать три подходящих шаблона, когда мы используем 'd', 'm' и 'y' ('д', 'м' и 'г') соответственно в качестве имен групп, захватывая соответствующие компоненты даты. Поисковая операция сочетает в себе три шаблона как альтернативу:

    $fmt1 = '(?<y>\d\d\d\d)-(?<m>\d\d)-(?<d>\d\d)';
    $fmt2 = '(?<m>\d\d)/(?<d>\d\d)/(?<y>\d\d\d\d)';
    $fmt3 = '(?<d>\d\d)\.(?<m>\d\d)\.(?<y>\d\d\d\d)';
    for my $d qw( 2006-10-21 15.01.2007 10/31/2005 ){
        if ( $d =~ m{$fmt1|$fmt2|$fmt3} ){
            print "day=$+{d} month=$+{m} year=$+{y}\n";
        }
    }

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

Альтернативные нумерация групп захвата

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

    if ( $time =~ /(\d\d|\d):(\d\d)|(\d\d)(\d\d)/ ){
        # обработка часов и минут
    }

Обработка результатов требует дополнительных выражений if для определения где $1 и $2 или $3 и $4 содержат нужные данные. Будет проще, если мы могли бы использовать второй вариант как номера группы 1 и 2 Ну это именно то, что дает конструкция (?|...), поставленная вокруг целевой альтернативы. Вот расширенная версия предыдущего шаблона:

    if ( $time =~ /(?|(\d\d|\d):(\d\d)|(\d\d)(\d\d))\s+([A-Z][A-Z][A-Z])/ ){
        print "hour=$1 minute=$2 zone=$3\n";
    }

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

Информация о позиции

В дополнение к тому, что было найдено, Perl также предоставляет позицию того, что было найдено, как содержание массивов @- и @+. $-[0] является положением начала всего поиска и $+[0] является положением конца. Точно так же $-[n] это положение старта $n -го поиска и $+[n] является конечной позицией. Если $n не определен, то нет $-[n] и $+[n]. Тогда этот код

    $x = "Mmm...donut, thought Homer";
    $x =~ /^(Mmm|Yech)\.\.\.(donut|peas)/; # найдено
    foreach $expr (1..$#-) {
        print "Найдено $expr: '${$expr}' в позиции ($-[$expr],$+[$expr])\n";
    }

напечатает

    Найдено 1: 'Mmm' в позиции (0,3)
    Найдено 2: 'donut' в позиции (6,11)

Даже, если нет группировок в регекспе, еще можно узнать, что точно найдено в строке. Если вы используете их, Perl установит $` как часть строки до найденной, а $& как найденную строку, а в переменную $' часть строки после найденной строки. Пример:

    $x = "the cat caught the mouse";
    $x =~ /cat/;  # $` = 'the ', $& = 'cat', $' = ' caught the mouse'
    $x =~ /the/;  # $` = '', $& = 'the', $' = ' cat caught the mouse'

Во втором поиске, $` равно '', потому что регексп находит первый символ в строке и останавливается; он никогда не видел второй 'the'. Важно отметить, что использование $` и $' лишь немного замедляет процесс поиска регекспа, то $& замедляет его еще больше , потому что если они используются в одном регекспе в программе, они создаются для всех регекспов в программе. Таким образом, если целью приложения является производительность, их следует избегать. Если вам необходимо извлечь соответствующие подстроки, используйте @- и @+ вместо этого:

    $` тоже, что и substr( $x, 0, $-[0] )
    $& тоже, что и substr( $x, $-[0], $+[0]-$-[0] )
    $' тоже, что и substr( $x, $+[0] )

Начиная с Perl 5.10 могут использоваться переменные ${^PREMATCH}, ${^MATCH} и ${^POSTMATCH}. Они устанавливаются только, если присутствует модификатор /p . Следовательно, они не наказывают остальную часть программы.

Не захватывающие группировки

Группа, которую требуется связать в набор альтернатив может быть как с захватом, так и без. Если захват не нужен, то он создает лишние переменные к набору доступных для захвата групп, внутри, а также за пределами регекспа. Не захватывающие группировки, обозначаются (?:regexp) и по-прежнему позволяют регекспу рассматривать себя как единое целое, но не устанавливают группу для захвата в то же время. И захватывающие и не захватывающие группировки могут сосуществовать в одном и том же регекспе. Потому что не вытаскивающая, не захватывающая группировка быстрее, чем захватывающая. Не захватывающие группировки также удобны для выбора того, какие части регулярного выражения должны быть извлечены в переменные сопоставления:

    # найти число, $1-$4 установлены, но мы хотим только $1
    /([+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)/;

    # поиск значительно быстрее ,т.к. установлена только $1
    /([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;

    # найти число, в $1 = целое число, $2 = экспонента
    /([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]([+-]?\d+))?)/;

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

    $x = '12aba34ba5';
    @num = split /(a|b)+/, $x;    # @num = ('12','a','34','a','5')
    @num = split /(?:a|b)+/, $x;  # @num = ('12','34','5')

Поиск повторений

Примеры в предыдущей секции показывают раздражающую слабость. Мы только распознавали 3-буквенные слова или куски слов из 4-х букв или менее. Мы хотели бы распознать слова или, более широко, последовательности любой длины, не выписывая утомительные альтернативы такие, как \w\w\w\w|\w\w\w|\w\w|\w.

Для решения этой проблемы были созданы повторительные метасимволы ?, *, +, и {}. Они позволяют нам изменять число повторений для части регекспа, которым мы собираемся искать. Повторители идут сразу за символом, символьным классом или группировкой, которую мы хотим определить. У них есть следующие значения:

  • a? означает: найти 'a' 1 или 0 раз

  • a* означает: найти 'a' 0 или более раз, то есть число любое число раз

  • a+ означает: найти 'a' 1 или более раз , то есть как минимум один раз

  • a{n,m} означает: найти по крайней мере n раз, но не более m.

  • a{n,} означает: найти по крайней мере n или больше раз

  • a{n} означает: найти точно n раз

Вот несколько примеров: слово строчная, по крайней мере один пробел, и # любое количество цифр

    /[a-z]+\s+\d*/;  # найти слово в нижнем регистре, по крайней мере один пробел, и
                     # любое количество цифр
    /(\w+)\s+\g1/;    # матч двойное слово произвольной длины
    /y(es)?/i;       # найти 'y', 'Y', или 'yes' без учета регистра
    $year =~ /^\d{2,4}$/;  # удостоверьтесь, что год - по крайней мере 2, но не больше
                           # 4 цифр
    $year =~ /^\d{4}$|^\d{2}$/;    #  # лучшее совпадение; исключает дату из 3 цифр
    $year =~ /^\d{2}(\d{2})?$/;  #то же самое, написанное по-другому. Однако,
                                 # это захватывает последние две цифры в $1,
                                 # а другие нет.

    % simple_grep '^(\w+)\g1$' /usr/dict/words   # разве это не проще?
    beriberi
    booboo
    coco
    mama
    murmur
    papa

Для всех этих повторителей Perl будет пытаться искать строку настолько долго насколько это возможно пока все еще регексп будет этому соответствовать. Таким образом с /a?.../ Perl сначала попытается найти регексп с a, если это не удается, Perl будет пытаться искать регексп без a. Для повторителя * мы получаем следующее:

    $x = "the cat in the hat";
    $x =~ /^(.*)(cat)(.*)$/; # matches,
                             # $1 = 'the '
                             # $2 = 'cat'
                             # $3 = ' in the hat'

Как мы и ожидали, поиск находит только строку cat и останавливается на этом. Хотя этот регексп:

    $x =~ /^(.*)(at)(.*)$/; # найдет,
                            # $1 = 'the cat in the h'
                            # $2 = 'at'
                            # $3 = ''   (0 символов найдено)

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

Когда регексп может соответствовать строке несколькими различными способами, мы можем использовать принципы выше, чтобы предсказать, каким образом регексп будет искать:

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

  • Принцип 1: В случае альтернативы a|b|c..., если найдена крайняя левая альтернатива, то найден весь регексп.

  • Принцип 2: Повторители максимального поиска ?, *, + и {n,m} будут искать строку настолько долго (вперед) насколько это возможно и пока эта строка будет соответствовать всему регекспу.

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

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

Вот пример этих принципов в действии:

    $x = "The programming republic of Perl";
    $x =~ /^(.+)(e|r)(.*)$/;  # найдет,
                              # $1 = 'The programming republic of Pe'
                              # $2 = 'r'
                              # $3 = 'l'

Регексп ищет наиболее раннюю позицию в строке, 'T'. Кто-то может подумать, что e, будет самой левой альтернативой, которая будет найдена , но r производит наиболее длинную строку в первом повторителе.

    $x =~ /(m{1,2})(.*)$/;  # найдет,
                            # $1 = 'mm'
                            # $2 = 'ing republic of Perl'

Здесь самый ранний возможный поиск будет первая 'm' в programming. m{1,2} первый повторитель, таким образом он найдет максимальные mm.

    $x =~ /.*(m{1,2})(.*)$/;  # найдет,
                              # $1 = 'm'
                              # $2 = 'ing republic of Perl'

Здесь регексп ищет с начала строки. Первый повторитель .* забирает настолько много насколько это возможно , оставляя лишь одну 'm' для второго повторителя m{1,2}.

    $x =~ /(.?)(m{1,2})(.*)$/;  # найдет,
                                # $1 = 'a'
                                # $2 = 'mm'
                                # $3 = 'ing republic of Perl'

Здесь, .? съест максимально один символ в самой ранней возможной позиции строки, это будет 'a' в programming, Оставляя m{1,2} возможность найти оба m. Наконец,

    "aXXXb" =~ /(X*)/; # найдет $1 = ''

потому что он может найти ноль копий 'X' в начале строки. Если вы определенно хотите найти по крайней мере один 'X', используйте X+, не X*.

Иногда жадность не очень хороша. Время от времени мы хотели бы иметь повторители для поиска минимальной части строки, а не максимальной. Для этих целей Лари Волл создал минимальный поиск или не-жадные повторители ??, *?, +?, и {}?. К обычным повторителям добавляется ?. Они имеют следующие значения:

  • a?? означает: найти 'a' 0 или 1 раз. Попробуй 0 в начале, потом 1.

  • a*? означает: найти 'a' 0 или больше раз, т.е., любое число раз, но настолько мало насколько это возможно

  • a+? означает: найти 'a' 1 или больше раз, т.е., как минимум один, но настолько мало насколько это возможно

  • a{n,m}? означает: найти как минимум n раз, не больше, чем m раз, настолько мало насколько это возможно

  • a{n,}? означает: найти как минимум n раз, но настолько мало насколько это возможно

  • a{n}? означает: найти точно n раз. Т.к. мы находим точно n раз, a{n}? эквивалентно a{n} и есть только для нотационной консистенции.

Давайте посмотрим на пример выше, но с минимальными повторителями:

    $x = "The programming republic of Perl";
    $x =~ /^(.+?)(e|r)(.*)$/; # найдет,
                              # $1 = 'Th'
                              # $2 = 'e'
                              # $3 = ' programming republic of Perl'

Минимальная строка позволяет найти одновременно и начало строки ^ и первая скобка найдет Th, а в альтернативе e|r найдется e. Второй повторитель .* теперь свободен съесть оставшуюся часть строки.

    $x =~ /(m{1,2}?)(.*?)$/;  # найдет,
                              # $1 = 'm'
                              # $2 = 'ming republic of Perl'

Первую позиция строки, которую регексп находит это 'm' в programming. В этой позиции, минимальному m{1,2}? соответствует только один 'm'. Хотя второй повторитель .*? предпочитает поиск без символов, он ограничен якорем конца строки $ и находит всю оставшуюся строку.

    $x =~ /(.*?)(m{1,2}?)(.*)$/;  # найдет,
                                  # $1 = 'The progra'
                                  # $2 = 'm'
                                  # $3 = 'ming republic of Perl'

В этом регекспе, вы могли бы ожидать, что первый минимальный повторитель .*? найдет пустую строку, потому что он не ограничен якорем ^ в начале слова. Однако, здесь применяется принцип 0. Потому что, если возможно для регекспа найти от начала строки, он найдет с начала строки. Таким образом первый повторитель должен соответствовать всему до первого m. Второй минимальный повторитель находит только один m и третий повторитель соответствует остальной части строки.

    $x =~ /(.??)(m{1,2})(.*)$/;  # найдет,
                                 # $1 = 'a'
                                 # $2 = 'mm'
                                 # $3 = 'ing republic of Perl'

Как и в предыдущих регекспах, первый повторитель квантификатор .?? может соответствовать самой ранней позиции 'a' и он это делает. Второй повторитель жадный, так что он совпадает mm, и третий соответствует остальной части строки.

Мы можем изменить принцип 3 выше, принимая во внимание не-жадные (щедрые) повторители:

  • Принцип 3: Если есть два или более элемента в регекспе, крайний слева жадный (не жадный) повторитель, если таковые имеются, будет соответствовать наиболее длинной (короткой) части насколько это позволяет соответствовать всему регекспу. Следующий крайний слева жадный (не жадный) повторитель, если есть, будет пытаться соответствовать наиболее длинной (короткой) части оставшейся строки насколько это возможно, пока все еще позволяя соответствовать всему регекспу. И так далее, пока не будут выполнены все элементы регекспа.

Так же как чередование, повторители также чувствительны к поиску с возвратом. Вот пошаговый анализ примера

    $x = "the cat in the hat";
    $x =~ /^(.*)(at)(.*)$/; # найдет,
                            # $1 = 'the cat in the h'
                            # $2 = 'at'
                            # $3 = ''   (0 найдено)
0

Начинаем с первой буквы в строке 't'.

1

Первый повторитель '.*' начинает совпадать с целой строкой 'the cat in the hat'.

2

'a' в элементе регекспа 'at' не находится в конце строки. Возвращаемся на один символ назад.

3

'a' в элементе регекспа 'at' еще не совпадает с последним символом 't', таким образом возвращаемся еще на один символ.

4

Теперь мы можем найти 'a' и 't'.

5

Переходим к третьему элементу '.*'. Поскольку мы находимся в конце строки и '.*' может найтись 0 раз, то ему присваивается пустая строка.

6

Мы сделали это!

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

    /(a|b+)*/;

Проблема заключается в неопределенном числе вложенных повторителей (квантификаторов). Есть много различных способов секционирования строки на длину n между + и *: одно повторение с b+ длины n, два повторения с первым b+ длины k и второе с длиной n-k, m повторений чьи биты добавить до длины n, и др. На самом деле существует экспоненциальная количество способов разделить строку в зависимости от его длины. Регекспу может повезти и он найдет в начале процесса, но если он не найдет, Perl постарается проверить каждую возможность не сдаваясь. Так что будьте осторожнее с вложенными *, {n,m}, и +'s. Книга Mastering Regular Expressions Джеффри Фридла дает прекрасное обсуждение этого и других проблем эффективности.

Притяжательные повторители

Поиск с возвратом в ходе неустанного поиска может быть бессмысленной тратой времени. Рассмотрим простой шаблон

    /^\w+\s+\w+$/; # слово, пробел, слово

Всякий раз, когда это применяется к строке, которая не вполне соответствуют ожиданиям шаблона например "abc " или "abc def ", обработчик регулярных выражений будет отступать (выполнять возврат), примерно один раз для каждого символа в строке. Но мы знаем, что нет никакого пути вокруг принимая все из начальных символов слова, чтобы найти первое повторение, таким образом все пробелы должны быть съедены в средней части, и то же самое для второго слова.

С введением притяжательных повторителей в Perl 5.10 у нас есть способ давай инструкции движку регулярных выражений не возвращаться, с обычными повторителями +, добавляемые к ним. Это делает их скупыми ; после того, как они найдут успешное совпадение они не возвращаются обратно, чтобы найти другое решение. Они имеют следующие значения:

  • a{n,m}+ означает: найти как минимум n раз, но не более m раз, настолько много насколько возможно, и не дает ничего больше. a?+ это краткая запись для a{0,1}+

  • a{n,}+ означает: найти как минимум n раз, но настолько много насколько возможно и не дает ничего больше. a*+ это краткая запись для a{0,}+ и a++ это краткая запись для a{1,}+.

  • a{n}+ означает: найти точно n раз. Это только для нотационной консистенции.

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

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

    /"(?:[^"\\]++|\\.)*+"/;

Построение регекспа

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

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

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

Простейший случай — это целые числа. Они состоят из последовательности цифр, с необязательным знаком впереди. Цифры, которые мы можем представлять \d+ и знак может быть найден [+-]. Таким образом,регексп для целого числа

    /[+-]?\d+/;  # найдет целые числа

Число с плавающей точкой потенциально имеет знак, десятичную запятую, дробную часть и показатель степени. Один или более из этих частей являются не обязательными, поэтому нам нужно проверить различные возможности. Числа с плавающей запятой, которые находятся в надлежащей форме включают 123. 0,345,.34,-1e6 и 25.4E-72. Как и с целыми числами, знак спереди совершенно необязателен и может находится [+-]?. Мы можем увидеть, что если нет не показателя степени, числа с плавающей точкой должны иметь десятичную запятую, в противном случае они являются целыми числами. У нас мог бы возникнуть соблазн замоделировать это так \d*\.\d*, но это также будет соответствовать только одной десятичной точке, которая не является числом. Таким образом вот три случая числа с плавающей запятой без экспоненты

   /[+-]?\d+\./;  # 1., 321., etc.
   /[+-]?\.\d+/;  # .1, .234, etc.
   /[+-]?\d+\.\d+/;  # 1.0, 30.56, etc.

Они могут быть объединены в единый регексп с тройным чередованием:

   /[+-]?(\d+\.\d+|\d+\.|\.\d+)/;  # число с плавающей точкой, не с экспонентой

В этом выборе, важно поставить '\d+\.\d+' перед '\d+\.'. Если '\d+\.' идет первым, регексп успешно найдется и проигнорирует дробную часть числа.

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

    /^(optional sign)(integer | f.p. mantissa)(optional exponent)$/;

Экспонента это e или E, идущая перед цифрой. Таким образом регексп для экспоненты такой

   /[eE][+-]?\d+/;  # экспонента

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

   /^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/;  # Тадам!

Длинные регулярных выражений, как это могут впечатлить ваших друзей, но их может быть трудно расшифровать. В сложных ситуациях, как эта, модификатор поиска //x имеет неоценимое значение. Он позволяет поставить произвольное число пробелов и комментариев в регулярное выражение не затрагивая его смысл. Используя его, мы можем переписать наш 'расширенный' регексп в более приятной форме

   /^
      [+-]?         # сначала находим необязательный знак
      (             # потом находим целое или мантиссу - число с плавающей запятой:
          \d+\.\d+  # мантисса типа a.b
         |\d+\.     # мантисса типа a.
         |\.\d+     # мантисса типа .b
         |\d+       # целое в виде a
      )
      ([eE][+-]?\d+)?  # в конце, опционально, находим экспоненту
   $/x;

Если пробелы в основном не имеют значения, как вставить один из них в этот расширенной regexp? Ответ заключается в обратной косой черте '\ ' или вставить пробел, как в классе символов [ ]. То же самое идет для знаков фунта или решетки: используйте \# или [#]. Например Perl позволяет пробелы между знаком и мантиссой или целым числом и мы могли бы добавить это в наш регексп следующим образом:

   /^
      [+-]?\ *      # вначале, найдем опциональный знак *и пробел*
      (             # потом находим целое или мантиссу:
          \d+\.\d+  # мантисса типа a.b
         |\d+\.     # мантисса типа a.
         |\.\d+     # мантисса типа .b
         |\d+       # целое в виде a
      )
      ([eE][+-]?\d+)?  # в конце, опционально, находим экспоненту
   $/x;

В этой форме, это легче увидеть способ упростить чередование. Все варианты 1, 2 и 4 начинаются с \d+, это можно вынести за скобки:

   /^
      [+-]?\ *      # вначале, найдем опциональный знак
      (             # потом находим целое или мантиссу:
          \d+       # начинаем с цифры a ...
          (
              \.\d* # мантисса типа  a.b или a.
          )?        # ? заботиться о целом числе в форме a
         |\.\d+     # мантисса типа .b
      )
      ([eE][+-]?\d+)?  # в конце, опционально, находим экспоненту
   $/x;

или написанное в компактной форме:

    /^[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;

Это - наш финальный регексп. Чтобы резюмировать, мы построили regexp

  • определяя задачу подробно,

  • разделяя задачу на маленькие части,

  • переводя маленькие кусочки в регексп,

  • объединяя регекспы,

  • и оптимизировав финальный объединенный регексп.

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

Использование регулярных выражений в Perl

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

Мы уже представили соответствующий оператор по умолчанию это /регексп/ , а в случае произвольного разделителя m!регексп! . Мы использовали оператор привязки =~ и его отрицание !~ для проверки найденной строки. Операторы, связанные с оператором поиска, мы уже обсуждали, это единая линия //s, многострочная строка //m, без учета регистра С<//i> и модификатор расширенного синтаксиса //x. Есть несколько вещей, которые вы могли бы захотеть узнать об операторах поиска.

Запрет замены

Если вы измените $pattern после первой произошедшей замены, Perl это проигнорирует. Если вы не хотите замен вообще, используйте специальный разделитель m'':

    @pattern = ('Seuss');
    while (<>) {
        print if m'@pattern';  # найдёт строку '@pattern', не 'Seuss'
    }

Подобно строкам, m'' действует как апостроф на регексп; все другие разделители m действуют как цитаты. Если выражение оценивается как пустая строка, вместо этого используется регексп последнего успешного сопоставления. Поэтому у нас

    "dog" =~ /d/;  # найдет 'd'
    "dogbert =~ //;  # это найдет 'd' , как и предыдущий регексп

Глобальный поиск

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

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

    $x = "cat dog house"; # 3 слова
    $x =~ /^\s*(\w+)\s+(\w+)\s+(\w+)\s*$/; # найдет,
                                           # $1 = 'cat'
                                           # $2 = 'dog'
                                           # $3 = 'house'

Но что делать, если мы имеем неопределенное число слов? Для этого рода задач сделан модификатор //g. Чтобы извлечь все слова, образуют простой регексп (\w+) и цикл по всем найденным шаблонам в строке /(\w+)/g:

    use utf8;
    use Encode::Locale;

    if (-t) 
    {
        binmode(STDIN, ":encoding(console_in)");
        binmode(STDOUT, ":encoding(console_out)");
        binmode(STDERR, ":encoding(console_out)");
    }
    
    while ($x =~ /(\w+)/g) {
        print "Слово $1, оканчивается в позиции ", pos $x, "\n";
    }

напечатает

    Слово cat, оканчивается в позиции 3
    Слово dog, оканчивается в позиции 7
    Слово house, оканчивается в позиции 13

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

В списочном контексте //g возвращает список найденных группировок, или если нет групп, список совпадений во всем регекспе. Так что, если мы хотим найти только слова, мы можем использовать

    @words = ($x =~ /(\w+)/g);  # найдет,
                                # $words[0] = 'cat'
                                # $words[1] = 'dog'
                                # $words[2] = 'house'

Тесно связан с модификатором //g якорь \G . Якорь \G находит позицию, где предыдущий //g в поиске был выключен. \G позволяет нам легко сделать контекстно-чувствительный поиск:

    $metric = 1;  # использовать метрические единицы
    ...
    $x = <FILE>;  # читать в измерение
    $x =~ /^([+-]?\d+)\s*/g;  # получить величины
    $weight = $1;
    if ($metric) { # проверка ошибок
        print "Units error!" unless $x =~ /\Gkg\./g;
    }
    else {
        print "Units error!" unless $x =~ /\Glbs\./g;
    }
    $x =~ /\G\s+(widget|sprocket)/g;  # продолжаем обработку

Сочетание С<//g> и С<\G> позволяет позволяет нам обрабатывать последовательность бит за один раз и использовать произвольную логику Perl, чтобы решить, что делать дальше. В настоящее время, якорь \G полностью поддерживается только тогда, когда используется якорь для привязки к началу шаблона.

\G также неоценим в обработке отчетов фиксированной длины регекспами. Предположим, что у нас есть отрывок кодирования ДНК области, закодированной как письма о паре оснований ATCGTTGAAT... и мы хотим найти всю остановку кодоны TGA. В кодирующем регионе кодоны - 3-буквенные последовательности, таким образом, мы можем думать об отрывке ДНК как о последовательности 3-буквенных отчетов. наивный регексп

    # расширенный, это "ATC GTT GAA TGC AAA TGA CAT GAC"
    $dna = "ATCGTTGAATGCAAATGACATGAC";
    $dna =~ /TGA/;

не работает; это может соответствоватьTGA, но нет никакой гарантии, что поиск будет выровнен с границами кодона, например, подстрока GTT GAA дает успешный поиск. Лучшее решение

    while ($dna =~ /(\w\w\w)*?TGA/g) {  # отметьте минимальное *?
        print "Получил кодон остановки TGA в позиции", pos $dna, "\n";
    }

что напечатает

    Получил кодон остановки TGA в позиции 18
    Получил кодон остановки TGA в позиции 23

Позиция 18 хорошо, но позиция 23 поддельная. Что произошло?

Ответ - то, что наш регексп работает хорошо, пока мы не заканчиваем последний реальный поиск. Потом регексп не будет соответствовать синхронизированному TGA и начнет ступать вперед одно положение символа за один раз,а не то,что мы хотим. Решение состоит в том, чтобы использовать якорь \G , чтобы закрепить поиск к выравненному кодону:

    while ($dna =~ /\G(\w\w\w)*?TGA/g) {
        print "Got a TGA stop codon at position ", pos $dna, "\n";
    }

Напечатает

    Got a TGA stop codon at position 18

который является правильным ответом. Этот пример иллюстрирует, что важно найти не только то, что желаемо, но и отклонить то, что не желанно.

(Есть другие модификаторы регексп , которые доступны, такие как //o, но их специализированное использование вне объема этого введения.)

Поиск и замена

Регулярные выражения также играют большую роль в операциях поиска и замены в Perl. Поиск и замена осуществляется оператором s///. Общая форма s/регексп/замена/модификаторы, , здесь применяется все, что мы знаем о регулярных выражениях и модификаторах. замена является Perl строкой в двойных кавычках, которая заменяет в строке то, что найдет регексп. Оператор =~ также используется для связывания строки с s///. Если ищем в $_, $_ =~ можно опустить. Если есть совпадение, s/// возвращает количество сделанных замен; в противном случае он возвращает значение false. Вот несколько примеров:

    $x = "Time to feed the cat!";
    $x =~ s/cat/hacker/;   # $x содержит "Time to feed the hacker!"
    if ($x =~ s/^(Time.*hacker)!$/$1 now!/) {
        $more_insistent = 1;
    }
    $y = "'quoted words'";
    $y =~ s/^'(.*)'$/$1/;  # удалить одиночные кавычки,
                           # $y содержит "quoted words"

В последнем примере целая строка была найдена, но только часть в одинарных кавычках составила группу. С оператором s/// переменные $1, $2, и т.д. немедленно доступны для использования в выражении замены, таким образом, мы используем $1, чтобы заменить указанную закавыченную строку. С глобальным модификатором, s///g будет искать и заменять все найденные части регекспа в строке:

    $x = "I batted 4 for 4";
    $x =~ s/4/four/;   # не заменит для всех:
                       # $x содержит "I batted four for 4"
    $x = "I batted 4 for 4";
    $x =~ s/4/four/g;  # заменит все:
                       # $x содержит "I batted four for four"

Если вы предпочитаете 'regex' над 'regexp' в этом учебнике, вы можете использовать следующую программу для замены:

    % cat > simple_replace
    #!/usr/bin/perl
    $regexp = shift;
    $replacement = shift;
    while (<>) {
        s/$regexp/$replacement/g;
        print;
    }
    ^D

    % simple_replace regexp regex perlretut.pod

В простой_замене мы используем модификатор s///g, чтобы заменить все найденные части строки на всем блоке искомого текста. (Даже, если регулярное выражение появляется в цикле, Perl достаточно умен, чтобы скомпилировать его один раз.) Как с простым_grep, оба print и s/$regexp/$replacement/g используют $_ не явно.

Если вы не хотите использовать s/// для изменения оригинальной переменной вы можете использовать не разрушаемый модификатор s///r. Он изменяет поведение, так, что s///r возвращает конечную измененную строку (вместо числа замен):

    $x = "I like dogs.";
    $y = $x =~ s/dogs/cats/r;
    print "$x $y\n";

Этот пример напечатает "I like dogs. I like cats". Учтите, что начальная переменная $x не была изменена. Общий результат замены вместо этого сохраняется в $y. Если замены не происходит, то возвращается исходная строка:

    $x = "I like dogs.";
    $y = $x =~ s/elephants/cougars/r;
    print "$x $y\n"; # напечатает "I like dogs. I like dogs."

Еще одна интересная штука в том, что флаг s///r позволяет делать цепочки замен:

    $x = "Cats are great.";
    print $x =~ s/Cats/Dogs/r =~ s/Dogs/Frogs/r =~ s/Frogs/Hedgehogs/r, "\n";
    # напечатает "Hedgehogs are great."

s///e - модификатор выполнения, существующий специально для поиска и замены. s///e воспринимает заменяемый текст,как код Perl, а не строку в двойных кавычках. Значение, которое возвращает код подставляется вместо найденной подстроки. s///e полезно, если вам нужно сделать некоторые вычисления в процессе замены текста. В этом примере рассчитывается частота символов в строке:

    $x = "Bill the cat";
    $x =~ s/(.)/$chars{$1}++;$1/eg;  # конечный $1 заменяет символ на самого себя
    print "frequency of '$_' is $chars{$_}\n"
        foreach (sort {$chars{$b} <=> $chars{$a}} keys %chars);

Это напечатает

    frequency of ' ' is 2
    frequency of 't' is 2
    frequency of 'l' is 2
    frequency of 'B' is 1
    frequency of 'c' is 1
    frequency of 'e' is 1
    frequency of 'h' is 1
    frequency of 'i' is 1
    frequency of 'a' is 1

Как и в операторе поиска m//, оператор s/// может использовать другие разделители, Например, s!!! и s{}{} и даже s{}//. Если используются одинарные кавычки s''', тогда регексп regexp и замена рассматриваются как строки в одиночных кавычках и тогда не происходит подстановки переменных. s/// в списочном контексте возвращает то же самое, как и в скалярном контексте, т.е. число найденных элементов.

Функция split

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

    $x = "Calvin and Hobbes";
    @words = split /\s+/, $x;  # $word[0] = 'Calvin'
                               # $word[1] = 'and'
                               # $word[2] = 'Hobbes'

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

    $x = "/usr/bin/perl";
    @dirs = split m!/!, $x;  # $dirs[0] = ''
                             # $dirs[1] = 'usr'
                             # $dirs[2] = 'bin'
                             # $dirs[3] = 'perl'
    @parts = split m!(/)!, $x;  # $parts[0] = ''
                                # $parts[1] = '/'
                                # $parts[2] = 'usr'
                                # $parts[3] = '/'
                                # $parts[4] = 'bin'
                                # $parts[5] = '/'
                                # $parts[6] = 'perl'
                                

Так как первый символ $x регексп находит, split присоединяет пустой начальный элемент к списку.

Если вы дочитали так далеко, Поздравляем! Теперь у вас есть все основные инструменты, необходимые для использования регулярных выражений для решения широкого круга проблем обработки текста. Если это ваш первый раз через учебник, то почему бы не остановится и поиграть с регулярными выражениями какое-то время... Часть 2 касается более эзотерических аспектов регулярных выражений и тех концепций, которые, безусловно, не нужны в самом начале

Часть 2: Электроинструменты (Power Tools)

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

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

Подробнее о символах, строк и о символьных классах

Существует ряд эскейп последовательностей и символьных классов, которые мы пока еще не охватили.

Есть несколько escape-последовательностей, которые преобразуют символы или строки в верхний и нижний регистр и они также доступны внутри шаблонов. \l и \u преобразуют следующий символ в нижний или верхний регистр, соответственно:

    $x = "perl";
    $string =~ /\u$x/;  # найдет 'Perl' в $string
    $x = "M(rs?|s)\\."; # внимание двойной бекслеш
    $string =~ /\l$x/;  # найдет 'mr.', 'mrs.', и 'ms.',

\L или \U указывают на преобразование регистра, пока не встретится завершающее \E или бросаются другие \U или \L:

    $x = "This word is in lower case:\L SHOUT\E";
    $x =~ /shout/;       # найдет
    $x = "I STILL KEYPUNCH CARDS FOR MY 360"
    $x =~ /\Ukeypunch/;  # найдет punch card string

Если нет \E, регистр меняется до конца строки. Регексп \L\u$word или \u\L$word конвертирует первый символ у $word в верхний регистр и оставшиеся символы в нижний регистр.

Управляющие символы могут быть заэскейплены при помощи \c, то есть символ control-Z будет найден с помощью \cZ. Эскейп последовательность \Q...\E является кавычками, или защищает не алфавитные символы. Например,

    $x = "\QThat !^*&%~& cat!";
    $x =~ /\Q!^*&%~&\E/;  # проверка на грубый язык

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

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

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

Что это значит для регулярных выражений? Ну пользователям регекспов не нужно знать много о внутреннем представлении строк в Perl . Но они должны знать 1) как для представляются символы Юникода в регекспе и 2),что операция поиска будет относиться к строке для поиска как последовательность из символов, а не байтов. Ответ на 1), что символы Юникода больше, чем chr(255) представлены использованием \x{hex} нотации, потому что \x hex (без фигурных скобок) не идет дальше, чем 255. (Начиная с Perl 5.14, если вы поклонник восьмеричной системы, вы можете также использовать \o{oct}.)

    /\x{263a}/;  # найдет Юникодный символ лица в виде смайла :)

ЗАМЕТЬТЕ: В Perl 5.6.0, раньше считалось, что необходимо сказать use utf8 , чтобы использовать любые функции Юникода. Это больше не так: для почти всей обработки Юникода, явная прагма utf8 не нужна. (Единственный случай, когда это имеет значение – если ваш Perl скрипт находится в Юникоде и кодировка UTF-8, тогда явное указание use utf8 необходимо.)

Выяснение шестнадцатеричной последовательности Юникодного символа, который вы хотите или расшифровка чужого шестнадцатеричного Юникодного регекспа может быть также интересно, как программирование в машинном коде. Так что другой способ указания символов Юникода заключается в использовании именованных символов эскейп последовательности \N{имя}. имя — это имя для символа Юникода , указанного в стандарте Юникода. Например, если мы хотим узнать, что представляют собой знак зодиака планеты Меркурий, мы можно использовать

    $x = "abc\N{MERCURY}def";
    $x =~ /\N{MERCURY}/;   # найдет

Можно также использовать "короткие" имена:

    print "\N{GREEK SMALL LETTER SIGMA} называется сигма.\n";
    print "\N{greek:Sigma} это сигма в верхнем регистре.\n";

Можно также ограничить имена определенным алфавитом, указав прагму charnames:

    use charnames qw(greek);
    print "\N{sigma} это Греческая сигма\n";

Индекс имен символов доступен он-лайн у Юникодного Консорциума, http://www.unicode.org/charts/charindex.html; пояснительные материалы со ссылками на другие ресурсы есть на http://www.unicode.org/standard/where.

Ответом на требование 2) является то, что регексп (в основном) использует символы Юникода. "По большей части" предназначен для обеспечения грязной обратной совместимости, но начиная с Perl 5.14, любой регексп скопилированный в области use feature 'unicode_strings' (которая автоматически включена в сферу начиная с use 5.012 или выше) переключает "По большей части" на "Всегда". Если вы хотите обрабатывать Юникод должным образом, вам следует обеспечить, что 'unicode_strings' включен. Внутренне это кодируется с помощью UTF-8 или родной 8 байтовой кодировкой, в зависимости от истории строки, но концептуально это последовательность символов, не байтов. Смотрите perlunitut как на учебник для этого.

Давайте теперь обсудим классы символов Юникода. Для символов Юникода существует именованные классы символов Юникода, представленные эксейп-последовательностью \p{name}. Тесно связан \P{name} класс символов, который представляет собой отрицание \p{name} класса. Для примера, чтобы найти символы нижнего и верхнего регистра,

    $x = "BOB";
    $x =~ /^\p{IsUpper}/;   # найдет символьный класс в верхнем регистре
    $x =~ /^\P{IsUpper}/;   # не найдет символьный класс в верхнем регистре
    $x =~ /^\p{IsLower}/;   # не найдет, здесь символьный класс символов нижнего регистра
    $x =~ /^\P{IsLower}/;   # найдет, символьный класс, отрицающий нижний регистр

( "Is" является необязательным.)

Вот связь между некоторыми классами Perl и традиционными классами Юникода:

    Имя класса Юникода имя класса Perl или регулярное выражение

    IsAlpha          /^[LM]/
    IsAlnum          /^[LMN]/
    IsASCII          $code <= 127
    IsCntrl          /^C/
    IsBlank          $code =~ /^(0020|0009)$/ || /^Z[^lp]/
    IsDigit          Nd
    IsGraph          /^([LMNPS]|Co)/
    IsLower          Ll
    IsPrint          /^([LMNPS]|Co|Zs)/
    IsPunct          /^P/
    IsSpace          /^Z/ || ($code =~ /^(0009|000A|000B|000C|000D)$/
    IsSpacePerl      /^Z/ || ($code =~ /^(0009|000A|000C|000D|0085|2028|2029)$/
    IsUpper          /^L[ut]/
    IsWord           /^[LMN]/ || $code eq "005F"
    IsXDigit         $code =~ /^00(3[0-9]|[46][1-6])$/

ВЫ можете также использовать официальный класс Юникода с \p и \P, например \p{L} для Юникодных 'символов' ('letters'), \p{Lu} для символов в верхнем регистре , или \P{Nd} для не цифр. Если name это только один символ, скобки можно опустить. Например, \pM - это символьный класс Юникодных знаков ('marks'), например, знака ударения (диакритические). Для получения полной информации смотри perlunicode.

Юникод также был разделен на различные наборы символов которые вы можете протестировать \p{...} (входит) и \P{...} (не входит). Чтобы проверить символ на вхождение (или нет) в элемент скрипта вы должны использовать имя скрипта, например \p{Latin}, \p{Greek}, или \P{Katakana}.

То, что мы описали пока является единой формой \p{...} класса символов. Существует также составные формы, с которыми вы можете столкнуться. Эти выглядят как \p{name=value} или \p{name:value} (знак равенства и двоеточия могут использоваться как синонимы). Это более общее представление, чем одинарная форма, и на самом деле для большинства одиночных форм Perl просто дает определенные ярлыки для общих составных форм. Наример сценарии в предыдущем пункте могут быть написан эквивалентны \p{Script=Latin}, \p{Script:Greek}, и \P{script=katakana} (регистр имеет значения между фигурными скобками {}). Вам возможно никогда не придется использовать составные формы, но иногда это необходимо и их использование может сделать ваш код более понятным.

\X — это аббревиатура для класса символов, который состоит из Юникодных расширенных графема кластеров. Они представляют собой "логический символ": то, что представляется, как один символ, но может быть внутренне представлено больше чем одним символом. Например, с помощью полных имен Юникода, например, A + COMBINING RING — графема кластер, основанный на символе A и сочетания символов COMBINING RING, который переводится в датском на A с кружком на вершине, как и слово ангстрем(Angstrom).

Полную и последнюю информацию о кодировке Юникод см. последний стандарт Юникода, или веб-сайт консорциума Unicode http://www.unicode.org. Хорошая статья здесь http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G30602.

Если и этих классов не достаточно, Perl также определяет POSIX-стиль классов символов. Они имеют форму [:name:], где name имя класса POSIX. Классы POSIX, alpha, alnum, ascii, cntrl, digit, graph, lower, print, punct, space, upper, и xdigit,и два расширения, word(Perl расширение для поиска \w) и blank(расширение GNU). Модификатор //a ограничивает эти соответствия только в диапазоне ASCII; в противном случае они могут совпадать, так же, как соответствующие им классы Perl Unicode: [:upper:] такой же, как \p{IsUpper}, и т.д. (есть некоторые исключения и ошибки с этим; Смотрите полное обсуждение в perlrecharclass.) [:digit:], [:word:], и [:space:] соответствуют знакомым \d, \w и \s классам символов. Для инвертирования класса POSIX, поставьте ^ перед именем, так что, например, [:^digit:] соответствует \D и, под Юникодом, \P{IsDigit}. Классы символов Unicode и POSIX могут быть использованы так же, как \d, за исключением символов POSIX классы которых могут использоваться только внутри класса символов:

    /\s+[abc[:digit:]xyz]\s*/;  # найдет a,b,c,x,y,z, или цифру
    /^=item\s[[:digit:]]/;      # найдет '=item',
                                # за которым пробел и цифра
    /\s+[abc\p{IsDigit}xyz]\s+/;  # найдет a,b,c,x,y,z, или цифру
    /^=item\s\p{IsDigit}/;        # найдет '=item',
                                  # за которым пробел и цифра

Вау! Это уже все знаки и классы символов.

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

В 1 Части мы упомянули, что Perl компилирует регекспы в компактную последовательность опкодов. Таким образом скомпилированное регулярное выражение — это структура данных, которую можно сохранить один раз и использовать снова и снова. Кавычки регекспа qr// а точнее: qr/строка/ компилируют строку как регулярное выражение и преобразуют результат в форму, которую можно можно присвоить переменной:

    $reg = qr/foo+bar?/;  # reg содержит откомпилированный регексп

Тогда $reg может быть использован в регекспе:

    $x = "fooooba";
    $x =~ $reg;     # найдет, то же, что и /foo+bar?/
    $x =~ /$reg/;   # то же, альтернативная форма

$reg также может быть интерполирован в больший регексп:

    $x =~ /(abc)?$reg/;  # все еще найдет

С оператором поиска m, кавычки регекспа могут иметь разнообразные разделители , т.е., qr!!, qr{} или qr~~. Апострофы в качестве разделителей (qr'') препятствуют интерполяции.

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

    % cat > grep_step
    #!/usr/bin/perl
    # grep_step - найдет <число> регекспов, один после другого
    # использование: multi_grep <number> regexp1 regexp2 ... file1 file2 ...

    $number = shift;
    $regexp[$_] = shift foreach (0..$number-1);
    @compiled = map qr/$_/, @regexp;
    while ($line = <>) {
        if ($line =~ /$compiled[0]/) {
            print $line;
            shift @compiled;
            last unless @compiled;
        }
    }
    ^D

    % grep_step 3 shift print last grep_step
    $number = shift;
            print $line;
            last unless @compiled;

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

Составление регулярных выражений во время выполнения программы (runtime)

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

    % cat > multi_grep
    #!/usr/bin/perl
    # multi_grep - найдет любое <число> регекспов
    # usage: multi_grep <number> regexp1 regexp2 ... file1 file2 ...

    $number = shift;
    $regexp[$_] = shift foreach (0..$number-1);
    $pattern = join '|', @regexp;

    while ($line = <>) {
        print $line if $line =~ /$pattern/;
    }
    ^D

    % multi_grep 2 shift for multi_grep
    $number = shift;
    $regexp[$_] = shift foreach (0..$number-1);

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

    % cat > keymatch
    #!/usr/bin/perl
    $kwds = 'copy compare list print';
    while( $command = <> ){
        $command =~ s/^\s+|\s+$//g;  # обрезаем лидирующие и конечные пробелы
        if( ( @matches = $kwds =~ /\b$command\w*/g ) == 1 ){
            print "command: '@matches'\n";
        } elsif( @matches == 0 ){
            print "no such command: '$command'\n";
        } else {
            print "not unique: '$command' (could be one of: @matches)\n";
        }
    }
    ^D

    % keymatch
    li
    command: 'list'
    co
    not unique: 'co' (could be one of: copy compare)
    printer
    no such command: 'printer'

Вместо того, чтобы пытаться найти соответствие входных данных и ключевых слов, мы найдем объединенный набор ключевых слов и сопоставим его входным данным (инпуту). Операция поиска по шаблону $kwds =~ /\b($command\w*)/g делает несколько вещей в одно и то же самое время. Чтобы быть уверенным, что команда начинается с ключевого слова, укажем (\b). Это можно сократить из-за добавленного \w*. Это говорит нам число найденных случаев (scalar @matches) и все ключевые слова, которые были фактически найдены.Вряд ли вы могли бы попросить больше.

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

Начиная с этого раздела, мы будем обсуждать набор расширенных шаблонов Perl. Это расширения для традиционного синтаксиса регулярных выражение , которые обеспечивают новые мощные инструменты для поиска по шаблону. Мы уже видели расширения в виде минимального соответствия конструкции ??, *?, +?, {n,m}?, и {n,}?. Большинство из расширений ниже имеют форму (?char...), где char — это символ, который определяет тип расширения.

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

    /(?# Match an integer:)[+-]?\d+/;

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

Большинство модификаторов, таких как //i, //m, //s и //x (или любой их комбинации) также могут быть встроены в регексп, с помощью (?i), (?m), (?s) и (?x). К примеру,

    /(?i)yes/;  # найдет 'yes' независимо от регистра
    /yes/i;     # то же самое
    /(?x)(          # свободная версия регкспа поиска целого числа (integer)
             [+-]?  # найдет опциональный знак
             \d+    # найдет последовательность цифр
         )
    /x;

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

    $pattern[0] = '(?i)doctor';
    $pattern[1] = 'Johnson';
    ...
    while (<>) {
        foreach $patt (@pattern) {
            print if /$patt/;
        }
    }

Вторым преимуществом является то, что встроенные модификаторы (за исключением //p, который изменяет весь регексп) влияют только на регексп внутри группы, в которой содержится встроенный модификатор. Поэтому группировка может использоваться для локализации эффектов модификаторов:

    /Answer: ((?i)yes)/;  # найдет 'Answer: yes', 'Answer: YES', etc.

Встроенные модификаторы могут также отключить любые модификаторы уже применяемые к регекспу с помощью (?-i). Модификаторы также могут быть объединены в одно выражение, например, (?s-i) включает режим одной линии и отключение регистра.

Встроенные модификаторы также могут быть добавлены к не захватывающим группировкам. (?i-m:regexp)— это не захватывающая группа, которая ищет регексп без учета регистра и выключает многострочный режим.

Заглядывание вперед и назад

Этот раздел рассказывает о заглядываниях вперед и назад. Сначала, немного теории.

В регулярных выражениях Perl большинство элементов регекспа "съедает" определенную часть строки, когда они совпадают. Например регексп элемент [abc}] съедает один символ строки, когда он находит совпадение, в смысле, что Perl перемещается на следующую позицию символа в строке после найденного символа. Есть некоторые элементы, однако, которые не съедают символы (предварительная позиция символа), если они совпадают. Примером, который мы видели до настоящего времени - были якори. Якорь ^ соответствует началу строки, но не съедает символы. Аналогичным образом, якорь границы слова \b находит там,где и знак соответствия \w и следующий симол, но он не ест символы. Якоря являются примерами утверждения нулевой ширины: нулевой ширины, потому что они не потребляют символы и утверждения, потому что они тестируют некоторые свойства строки. В контексте нашей прогулки в лесу по аналогии с поиском регекспом, большинство элементов регексп перемещает нас по тропе, но у якоря мы останавливаемся на мгновение и проверяем наше окружение. Если местная окружающая среда проходит проверку, мы можем двигаться вперед. Но если местное окружение не удовлетворяет нас, мы должны отступить.

Проверка окружающей среды влечет за собой заглядывание вперед по следу, оглядывание назад, или оба действия сразу. ^ оглядывается назад, чтобы увидеть, что перед символом ничего нет. $ смотрит вперед, чтобы увидеть, что нет знаков после. \b смотрит и вперед и назад, чтобы увидеть если знаки с обеих сторон отличаются по их "не словесности" (то есть слово переходит в не слово).

Утверждения при взгляде вперед и назад - обобщенное якорное понятие. Смотрение вперед и назад - утверждения нулевой ширины это позволит нам определить, какие знаки мы хотим проверить. Утверждение при взгляде вперед обозначается (?=regexp), а утверждение при взгляде назад обозначается (?<=fixed-regexp). Вот некоторые примеры

    $x = "I catch the housecat 'Tom-cat' with catnip";
    $x =~ /cat(?=\s)/;   # найдет 'cat' в 'housecat'
    @catwords = ($x =~ /(?<=\s)cat\w+/g);  # найдет,
                                           # $catwords[0] = 'catch'
                                           # $catwords[1] = 'catnip'
    $x =~ /\bcat\b/;  # найдет 'cat' в 'Tom-cat'
    $x =~ /(?<=\s)cat(?=\s)/; # не найдет; нет изолированного 'cat' в
                              # середине $x

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

    $x = "foobar";
    $x =~ /foo(?!bar)/;  # не найдет, 'bar' следует за 'foo'
    $x =~ /foo(?!baz)/;  # найдет, 'baz' не следует 'foo'
    $x =~ /(?<!\s)foo/;  # найдет, не присутствует \s перед 'foo'

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

Вот пример, где строка содержит слова, разделённые пробелами, цифрами и тире. Используя один /\s+/ у вас ничего не получится, потому, что пробелы не требуются между тире, или словом или тире. Устанавливаем дополнительные места разделения устанавливая утверждения на разведку вперед и назад:

    $str = "one two - --6-8";
    @toks = split / \s+              # начинается с пробелов
                  | (?<=\S) (?=-)    # любой не пробельный символ, следующий за '-'
                  | (?<=-)  (?=\S)   #  '-' следующий после не пробела
                  /x, $str;          # @toks = qw(one two - - - 6 - 8)

Использование независимых подвыражений для предотвращения поиска с возвратом(backtracking)

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

    $x = "ab";
    $x =~ /a*ab/;  # найдёт

Это, очевидно, совпадает, но в процессе поиска, подвыражение a* сначала схватывает a. Это, однако, не позволило бы соответствовать всему регекспу, таким образом, после поиска с возвратом, a* в конце концов возвращает a и соответствует пустой строке. Здесь то, чему соответствует a* зависит от остальной части регекспа.

Сравните это с независимым подвыражением:

    $x =~ /(?>a*)ab/;  # не найдет!

Независимое подвыражение (?>a*) не беспокоится об остальном регекспе, таким образом оно видит a и хватает его. Тогда оставшийся регексп ab уже не может совпасть. Потому что (?>a*) является независимым, здесь нет поиска с возвратом(backtracking) и независимое подвыражение (independent subexpression) не отдает наверх a. Таким образом поиск регекспа в целом не удается. То же самое происходит с полностью независимыми регулярными выражениями:

    $x = "ab";
    $x =~ /a*/g;   # найдет, съест 'a'
    $x =~ /\Gab/g; # не найдет, не доступных 'a'

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

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

    $x = "abc(de(fg)h";  # несбалансированные скобки
    $x =~ /\( ( [^()]+ | \([^()]*\) )+ \)/x;

Регексп находит открытую скобку, один или много знаков "или" и закрытую скобку. Альтернатива ("или") бывает 2-х типов? первая альтернатива [^()]+ находит подстроку без скобок ,а вторая альтернатива \([^()]*\) находит подстроку, разделенную скобками. Проблема с этим регекспом в том, что он патологический: он имеет вложенные неопределенные формы умножителей (a+|b)+. Мы обсуждали в первой части о том,как вложенные квантификаторы могут занимать экспоненциально много времени для выполнения, если там не ничего не должно быть найдено. Для предотвращения экспоненциального раздутия, нам нужно предотвращения бесполезный возврат (backtracking) в определенный момент. Это может быть сделано путем ограждающих внутреннего квантификатора как независимого подвыражения:

    $x =~ /\( ( (?>[^()]+) | \([^()]*\) )+ \)/x;

Здесь, (?>[^()]+) ломает дегенерацию разбиения строки, съедая настолько большую строку насколько это возможно и удерживая её. Тогда неудачный поиск происходит намного быстрее.

Условные выражения

Условное выражение это форма если-тогда-иначе (if-then-else) выражения, которое позволяет выбрать шаблон для поиска, основанный на некотором условии. Существует 2 типа условных выражений: (?(условие)да-регексп) и (?(условие)да-регексп|нет-регексп). (?(условие)да-регексп) это то же, что и 'if () {}' выражение в Perl. Если условие истина (true), то да-регексп будет найдет. Если условие ложь (false), то да-регексп будет пропущен и Perl передвинется к следующему регекспу в эелементе. Вторая форма -это тоже, что и 'if () {} else {}' выражение в Perl. Если условие истинно, то да-регексп будет найдет , иначе нет-регексп будет найден.

Условие может иметь несколько форм. Первая форма-просто целое число в скобках (integer). Это правда, если соответствующая обратная ссылка \integer была найдена ранее в регекспе. То же самое вещь может быть сделано с именем, связанным с группой захвата, написанным, как <<(<name>)>> или <('имя')>. Вторая форма-голые утверждение нулевой ширины (?...), либо вперед, назад или в коде утверждения (рассматривается в следующем разделе). Третий набор форм содержит тесты, которые возвращают значение true, если выражение выполняется в рамках рекурсии ((R)) или является вызываемой из некоторых групп захвата, ссылающихся либо по номеру ((R1), (R2),...) или по имени ((R&name)).

Форма цифры или имени в условии позволяет нам выбрать более гибко, что я вляется базовым шаблоном и, что найдено ранее в регекспе. В от поиск слов в форме "$x$x" или "$x$y$y$x":

    % simple_grep '^(\w+)(\w+)?(?(2)\g2\g1|\g1)$' /usr/dict/words
    beriberi
    coco
    couscous
    deed
    ...
    toot
    toto
    tutu

Просмотр условий назад позволяет, наряду с обратными ссылками, на ранней стадии поиска влиять на более поздний поиск. Например,

    /[ATGC]+(?(?<=AA)G|C)$/;

найдет последовательность DNA такую, что она либо заканчивается на AAG, или на другие комбинации пар и C. Обратите внимание, что форма (?(?<=AA)G|C) и не (?((?<=AA))G|C); для просмотра вперед, назад или кода утверждения, скобки вокруг условия не требуются.

Определение именованных шаблонов

Некоторые регулярные выражения используют идентичные подшаблоны в нескольких местах. Начиная с Perl 5.10, это возможно определяя именованные подшаблоны (подмаски;)) в секции шаблона, так что они могут вызываться по имени в любом месте шаблона (для поиска). Для определения именованного шаблона используется синтаксический паттерн (?(DEFINE)(?<name>pattern)...). Вставка именованного шаблона записывается как (?&name).

Пример ниже иллюстрирует эту функцию, используя шаблон для числа с плавающей запятой, который был представлен ранее. Три подшаблона, которые используются несколько раз – необязательный знак, последовательности цифр для целого числа и десятичной дроби. DEFINE (ОПРЕДЕЛЕНИЕ) группу в конце шаблона содержит их определение. Обратите внимание что шаблон десятичной дроби находиться на первом месте, где мы можем повторно используйте шаблон целого (integer).

   /^ (?&osg)\ * ( (?&int)(?&dec)? | (?&dec) )
      (?: [eE](?&osg)(?&int) )?
    $
    (?(DEFINE)
      (?<osg>[-+]?)         # optional sign
      (?<int>\d++)          # integer
      (?<dec>\.(?&int))     # decimal fraction
    )/x

Рекурсивные шаблоны

Эта функция (введена в Perl 5.10) значительно расширяет мощность Perl поиска по шаблону. Ссылаясь на другие захваченные группы в любом месте шаблона с помощью конструкции (?group-ref), шаблон в указанной группе используется как независимый подшаблон вместо группы ссылающейся сама на себя. Так как групповая ссылка может содержаться внутри группы, к которой он относится, то теперь можно применять поиск по шаблону для задачи, для которой до сих пор требуется рекурсивный парсер. (it is now possible to apply pattern matching to tasks that hitherto required a recursive parser)

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

    /(?: (\w) (?...Здесь будет палиндром...) \g{-1} | \w? )/x

Добавляя \W* на концах для ликвидации того, что будет проигнорировано, мы уже имеют полный шаблон:

    my $pp = qr/^(\W* (?: (\w) (?1) \g{-1} | \w? ) \W*)$/ix;
    for $s ( "saippuakauppias", "A man, a plan, a canal: Panama!" ){
        print "'$s' is a palindrome\n" if $s =~ /$pp/;
    }

Внутри (?...) могут быть использованы, как абсолютные, так и относительные обратные ссылки. Весь шаблон может быть перевставлен с (?R) или (?0). Если вы предпочитаете имя для вашей группы, то вы можете использовать (?&name) для рекурсии в эту группу.

Немного магии: выполнение кода в регулярном выражении Perl

Как правило регекспы являются частью Perl выражения. Выражения выполнения кода позволяют произвольному коду Perl быть частью регекспа. Выражение выполнения кода обозначается как (?{code}), где строка code является выражение на языке Perl.

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

Выражение с кодом являются утверждением нулевой ширины и оно возвращают значение, зависящее от окружающей их среды. Есть две возможности: либо выражение с кодом используется в качестве условного в условном выражении (?(condition)...), или нет. Если выражение с кодом условное, код вычисляется и результат (то есть, результат последнего оператора) используется для определения правды или лжи. Если выражение с кодом не используется в качестве условного, утверждение всегда вычисляет значение true и результат помещается в специальную переменную $^R. Переменная $^R затем может быть использована в выражениях с кодом позже в регекспе. Ниже приведены некоторые простые примеры:

    $x = "abcdef";
    $x =~ /abc(?{print "Hi Mom!";})def/; # найдет,
                                         # напечатает 'Hi Mom!'
    $x =~ /aaa(?{print "Hi Mom!";})def/; # не найдёт,
                                         # нет 'Hi Mom!'

Обратите особое внимание на следующий пример:

    $x =~ /abc(?{print "Hi Mom!";})ddd/; # не найдет,
                                         # нет 'Hi Mom!'
                                         # но почему?

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

    $x =~ /abc(?{print "Hi Mom!";})[dD]dd/; # не находит,
                                            # но _печатает_ 

Хм. Что здесь произошло? Если вы следили, вы знаете, что шаблон выше должен быть по эффективности быть (почти) таким же самое, как и предпоследний; заключение d в классе символов не собирается менять то, что он найдёт. Так почему же первый не печатает, а второй печатает?

Ответ заключается в оптимизации, которую делает движок регексов (regex engine). В первом случае весь движок видит простой старые символы (наряду с конструкцией ?{}). Он достаточно умен, чтобы понимать, что строка «ddd» не совпадает с нашей целевой строкой до фактического запуска шаблон. Но во втором случае, мы обманули его, заставляя его думать,что наш шаблон является более сложным. Он смотрит, видит наш символьный класс и решает, что ему придется запускать шаблон, чтобы определить соответствует ему строка или нет, В процессе выполнения он печатает выражение прежде чем он узнает, что полный регексп не соответствует шаблону.

Чтобы увидеть, как движок делает оптимизацию, см. секцию "Прагмы и отладки" ("Pragmas and debugging") ниже.

Больше фана с ?{}:

    $x =~ /(?{print "Hi Mom!";})/;       # найдёт,
                                         # напечатает 'Hi Mom!'
    $x =~ /(?{$c = 1;})(?{print "$c";})/;  # найдёт,
                                           # напечатает '1'
    $x =~ /(?{$c = 1;})(?{print "$^R";})/; # найдёт,
                                           # напечатает '1'
                                           

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

    $x = "aaaa";
    $count = 0;  # инициализация количества 'a'
    $c = "bob";  # тест, если $c испорчен
    $x =~ /(?{local $c = 0;})         # инициализация счетчика
           ( a                        # находим 'a'
             (?{local $c = $c + 1;})  # увеличиваем счетчик
           )*                         # делать так любое число раз,
           aa                         # но найти 'aa' в конце
           (?{$count = $c;})          # скопировать счетчик $c в $count
          /x;
    print "число 'a' равно $count, переменная \$c равна '$c'\n";

Это напечатает

    число 'a' равно 2, переменная $c равна 'bob'

Если мы заменим (?{local $c = $c + 1;}) на (?{$c = $c + 1;}), изменения в переменной будут нет восстановимы вовремя обратного поиска, и мы получим

    число 'a' равно 4, переменная $c равна 'bob' 

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

    $x = "aaaa";
    $x =~ /(a(?{print "Yow\n";}))*aa/;

произведет

   Yow
   Yow
   Yow
   Yow

Результат $^R автоматически локализуется, так что он будет вести себя должным образом во время поиска с возвратом.

В этом примере используется выражение с кодом в условном смысле для поиска определенного артикля, 'the' в английском или «der|die|das» в немецком языке:

    $lang = 'DE';  # используем Немецкий
    ...
    $text = "das";
    print "matched\n"
        if $text =~ /(?(?{
                          $lang eq 'EN'; # язык английский?
                         })
                       the |             # если да, то ищем 'the'
                       (der|die|das)     # иначе ищем 'der|die|das'
                     )
                    /xi;

Обратите внимание, что синтаксис здесь C <(?(?{...})yes-regexp|no-regexp)>, не C <(?((?{...}))yes-regexp|no-regexp)>. Другими словами, в случае использования выражения с кодом(code expression), нам не нужны дополнительные скобки вокруг условия.

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

    $bar = 5;
    $pat = '(?{ 1 })';
    /foo(?{ $bar })bar/; # компилируется успешно, $bar не интерполируется
    /foo(?{ 1 })$bar/;   # компилируется успешно, $bar интерполируется
    /foo${pat}bar/;      # ошибка компиляции!

    $pat = qr/(?{ $foo = 1 })/;  # педкомпиляция регекспа с кодом
    /foo${pat}bar/;      # компилируется успешно!

Если в регекспе есть переменная, которая интерполируется в выражении с кодом, то Perl рассматривает выражение как ошибку. Если выражение с кодом предварительно скомпилировано в переменную, тогда однако, интерполяции проходит успешно. Вопрос в том, почему это Ошибка?

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

    $regexp = <>;       # читать регексп, предоставленный пользователем
    $chomp $regexp;     # избавиться от возможной новой строки
    $text =~ /$regexp/; # искать $regexp в $text

Если переменная $regexp содержит выражение c кодом, то пользователь может выполните произвольный код Perl. Например некоторые шутники могут искать system('rm -rf *'); для удаления ваших файлов. В этом смысл, сочетание интерполяции и выражений с кодом код заражает ваш регексп. Таким образом, по умолчанию, использовать интерполяцию и выражение с кодом в одном и том же регекспе не допускается. Если вы не обеспокоены по поводу злонамеренных пользователей, эту проверку безопасности можно обойти путем вызова use re 'eval':

    use re 'eval';       # открывает дверь для опасностей
    $bar = 5;
    $pat = '(?{ 1 })';
    /foo${pat}bar/;      # компилируется успешно

Другая форма выражений с кодом — шаблон поиска с кодовым выражением. Шаблон c кодированным выражением, это как обычное выражение с кодом, за исключением того, что результат выполнения кода представляется как регулярное выражение и и по нему сразу выполняется поиск. Простой пример

    $length = 5;
    $char = 'a';
    $x = 'aaaaabb';
    $x =~ /(??{$char x $length})/x; # найдет, 5 'a'

Этот последний пример содержит и обычный шаблон и шаблон поиска с кодовым выражением. (This final example contains both ordinary and pattern code expressions.) Он определяет, является ли двойная последовательность 1101010010001... последовательностью Фибоначчи 0,1,1,2,3,5,..., начиная с 1-го элемента: (It detects whether a binary string 1101010010001... has a Fibonacci spacing 0,1,1,2,3,5,... of the 1's):

    $x = "1101010010001000001";
    $z0 = ''; $z1 = '0';   # начальные условия
    print "Это последовательность Фибоначчи\n"
        if $x =~ /^1         # находим начальную '1'
                    (?:
                       ((??{ $z0 })) # найдет несколько '0'
                       1             # и потом '1'
                       (?{ $z0 = $z1; $z1 .= $^N; })
                    )+   # повторить, если нужно
                  $      # на этом все
                 /x;
    printf "Наибольшая цифра в последовательности %d\n", length($z1)-length($z0);

Запомните, что в $^N установлено то, что было найдено последней группой захвата. Это напечатает

    Это последовательность Фибоначчи
    Наибольшая цифра в последовательности 5

Ха! Попробуйте это в вашем саду различных регексп пакетов... (Ha! Try that with your garden variety regexp package...)

Обратите внимание, что переменные $z0 и $z1, не изменяются во время компиляции регекспа , как это происходит для обычными переменными вне выражения с кодом. Скорее, весь блок с кодом анализируется как perl код во время компиляции perl кода, содержащего строку шаблона регулярного выражения. (Rather, the whole code block is parsed as perl code at the same time as perl is compiling the code containing the literal regexp pattern.)

Регексп без модификатора //x будет таким

    /^1(?:((??{ $z0 }))1(?{ $z0 = $z1; $z1 .= $^N; }))+$/

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

Управляющие глаголы(control verbs) во время поиска с возвратом (backtracking)

Perl 5.10 представил ряд глаголов управления, предназначенных для обеспечения большего контроля над процессом поиска с возвратом, непосредственно влияя на двигатель регекспа и предоставляя методы мониторинга. Как и все функции в этой группе - эта возможность экспериментальна и может быть изменена или удалена в будущей версии Perl, заинтересованный читатель может прочитать "Управляющие глаголы поиска с возвратом" in perlre для более подробного описания.

Ниже приведен лишь один из примеров, иллюстрирующий управляющий глагол (*FAIL), что может быть сокращенно до (*F). Если он будет вставлен в регексп это вызовет то, что регексп вернет ложь, так же, как, если бы шаблон поиска не был найден в строке. Обработка регекспа продолжается, как и после «нормального» неудачного поиска, так что, к примеру, движок переходит к следующей позиции в строке или пробует другую альтернативу. Как и неудавшийся поиск после этого не сохраняется группа захвата и не производятся результаты, эту возможность необходимо использовать в комбинации с внедренным кодом.

   %count = ();
   "supercalifragilisticexpialidocious" =~
       /([aeiou])(?{ $count{$1}++; })(*FAIL)/i;
   printf "%3d '%s'\n", $count{$_}, $_ for (sort keys %count);

Шаблон начинается с сопоставления подмножества буквы класса. Всякий раз, когда буква находится, выполняется выражение $count{'a'}++;, увеличивая счетчик букв. Затем (*FAIL) делает то, что он говорит, и обработчик регулярных выражений продолжает работать согласно учебнику: настолько долго, пока не наступит конец строки, положение вперед перед поиском для другой гласной. (Then (*FAIL) does what it says, and the regexp engine proceeds according to the book: as long as the end of the string hasn't been reached, the position is advanced before looking for another vowel.) Таким образом, соответствие или не соответствие не делает никакой разницы и двигатель регексп продолжает работать до тех пор, пока не проверена вся строка. (Примечательно, что альтернативное решение использует что-то вроде

   $count{lc($_)}++ for split('', "supercalifragilisticexpialidocious");
   printf "%3d '%s'\n", $count2{$_}, $_ for ( qw{ a e i o u } );

это значительно медленнее.)

Прагмы и отладка

Поговорим об отладке, существует несколько прагм для управления и отладки регулярных выражений в Perl. Мы уже столкнулись с одной прагмой в предыдущем разделе, use re 'eval';, которая позволяет интерполяцию переменных и выражению с кодом сосуществовать в одном регекспе. Другие прагмы

    use re 'taint';
    $tainted = <>;
    @parts = ($tainted =~ /(\w+)\s+(\w+)/; # @parts теперь помечена

Здесь меченая и испорченная переменная являются синонимами! (мой перевод слова tainted, еще предлагают - крашеная;)) Прагма taint делает так, что подстроки, найденные в меченой переменной также становятся мечеными(tainted). Обычно это не так, т.к. регекспы часто используются для получения безопасных бит из испорченной (tainted) переменной. Используйте taint, когда вы не извлекаете безопасные биты, но выполняете другую обработку. Обе прагмы taint и eval лексически ограничены , это означает то, что они действуют только до конца блока, включающего эти прагмы.

    use re '/m';  # или любые другие флаги
    $multiline_string =~ /^foo/; # /m подразумевается

Прагма re '/flags' (введена в Perl 5.14) включает флаги указанного регулярного выражения до конца лексической области. См. "'/flags' mode" in re для больших деталей.

    use re 'debug';
    /^(.*)$/s;       # вывод отладочной информация

    use re 'debugcolor';
    /^(.*)$/s;       # вывод отладочной информации в цвете

Глобальные прагмы debug и debugcolor позволяют получить детальную отладочную информацию о компиляции регекспа и его исполнении. debugcolor - такой же, как и debug, за исключением того, что информация отображается в цвете на терминалы, которые могут отображать termcap (terminal capability - отрытая библиотека для терминалов) цветовые последовательности. Вот пример вывода:

    % perl -e 'use re "debug"; "abc" =~ /a*b+c/;'
    Compiling REx 'a*b+c'
    size 9 first at 1
       1: STAR(4)
       2:   EXACT <a>(0)
       4: PLUS(7)
       5:   EXACT <b>(0)
       7: EXACT <c>(9)
       9: END(0)
    floating 'bc' at 0..2147483647 (checking floating) minlen 2
    Guessing start of match, REx 'a*b+c' against 'abc'...
    Found floating substr 'bc' at offset 1...
    Guessed: match at offset 0
    Matching REx 'a*b+c' against 'abc'
      Setting an EVAL scope, savestack=3
       0 <> <abc>             |  1:  STAR
                               EXACT <a> can match 1 times out of 32767...
      Setting an EVAL scope, savestack=3
       1 <a> <bc>             |  4:    PLUS
                               EXACT <b> can match 1 times out of 32767...
      Setting an EVAL scope, savestack=3
       2 <ab> <c>             |  7:      EXACT <c>
       3 <abc> <>             |  9:      END
    Match successful!
    Freeing REx: 'a*b+c'

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

    Compiling REx 'a*b+c'
    size 9 first at 1
       1: STAR(4)
       2:   EXACT <a>(0)
       4: PLUS(7)
       5:   EXACT <b>(0)
       7: EXACT <c>(9)
       9: END(0)

описывает этап компиляции. STAR(4) означает, что это избранный объект, в данном случае 'a', и если она совпадает, то идем на строку 4, то есть PLUS(7). Средние линии описывают некоторые эвристики и выполненную оптимизацию перед поиском:

    floating 'bc' at 0..2147483647 (checking floating) minlen 2
    Guessing start of match, REx 'a*b+c' against 'abc'...
    Found floating substr 'bc' at offset 1...
    Guessed: match at offset 0

затем выполняется поиск и остальные строки описывают этот процесс:

    Matching REx 'a*b+c' against 'abc'
      Setting an EVAL scope, savestack=3
       0 <> <abc>             |  1:  STAR
                               EXACT <a> can match 1 times out of 32767...
      Setting an EVAL scope, savestack=3
       1 <a> <bc>             |  4:    PLUS
                               EXACT <b> can match 1 times out of 32767...
      Setting an EVAL scope, savestack=3
       2 <ab> <c>             |  7:      EXACT <c>
       3 <abc> <>             |  9:      END
    Match successful!
    Freeing REx: 'a*b+c'

Каждый шаг имеет форму n <x> <y>, где <x> - это часть найденной строки и части <y> , которая еще не найдена. | 1: STAR говорит, что Perl в 1 строке с компиляцией упоминают выше. См. "Отладка Регулярных Выражений" in perldebguts для больших подробностей.

Альтернативным методом отладки регулярных выражений является включение включение print в регексп. Здесь предоставляется подробный отчет об обратных ссылках в случае оператора или:

    use utf8;
    use Encode::Locale;

    if (-t) 
    {
        binmode(STDIN, ":encoding(console_in)");
        binmode(STDOUT, ":encoding(console_out)");
        binmode(STDERR, ":encoding(console_out)");
    }
    "that this" =~ m@(?{print "Начали в позиции ", pos, "\n";})
                     t(?{print "t1\n";})
                     h(?{print "h1\n";})
                     i(?{print "i1\n";})
                     s(?{print "s1\n";})
                         |
                     t(?{print "t2\n";})
                     h(?{print "h2\n";})
                     a(?{print "a2\n";})
                     t(?{print "t2\n";})
                     (?{print "Закончили в позиции ", pos, "\n";})
                    @x;

напечатает

    Начали в позиции 0
    t1
    h1
    t2
    h2
    a2
    t2
    Закончили в позиции 4

ОШИБКИ

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

СМОТРИТЕ ТАКЖЕ

Это всего лишь учебное пособие. Для полной истории регулярных выражений смотри справку в perlre.

Для получения дополнительной информации об операторах поиска m// и замены s/// см "Regexp Quote-Like Operators" in perlop. Для информация об операции split см "split" in perlfunc.

Для превосходного всестороннего ресурса по уходу и кормлению регулярных выражений, см. книгу Mastering Regular Expressions by Jeffrey Friedl (published by O'Reilly, ISBN 1556592-257-3).

АВТОР И АВТОРСКОЕ ПРАВО

Copyright (c) 2000 Mark Kvale Все права защищены.

Этот документ может распространяться на тех же условиях, что и Perl.

Благодарности

Вдохновленный примером stop codon DNA пришедшим из ZIP примеров кода в главе 7 книги Mastering Regular Expressions.

Автор хотел бы поблагодарить Jeff Pinyan, Andrew Johnson, Peter Haworth, Ronald J Kimball, и Joe Smith за все их полезные комментарии.

ПЕРЕВОДЧИКИ

  • Николай Мишин <mishin@cpan.org>