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

Специальные переменные, используемые в регулярных выражениях

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

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

В языках программирования существуют глобальные и закрытые (private) переменные, которые объявляются директивой my (…). В Perl специальные глобальные переменные, такие, как $_, $1, @ARGV, не объявляются и доступны из любой точки программы. Если вы не используете директиву use strict (или use strict 'vars' ) и объявляете в программе переменные ( $a и т.д.), то эти переменные будут глобальными для данного пакета. Если вы употребили директиву use strict, вы должны будете объявлять эти переменные директивой our.

Переменные my имеют лексическую видимость и видны в минимальном блоке { … }, в котором находится директива my. (Это утверждение также относится к блоку кода Perl (?{…} ) внутри регулярного выражения.

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

$_=1;
{ local $_=2;
  print "$_\n";
}
print "$_\n";

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

2
1

Внутри блока программа работает с копией переменной $_, которая имеет то же имя, а при выходе из блока эта копия уничтожается, и программе становится доступна переменная $_, которая существовала до входа в этот блок. Директива local иногда применяется в подпрограммах, хотя в них логичнее создавать внутренние переменные директивой my. Директива local не применяется к переменным, созданным директивой my.

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

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

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

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

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

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

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

    Теперь рассмотрим такой пример:

    my $a=1;
    $_='a';
    /(a)/;
    print "$1\n";
    /(b)/;
    print "$1\n";

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

    a
    a

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

    Теперь рассмотрим более интересный пример:

    my $a=1;
    $_='ab';
    /(a)/;
    print "$1\n";
    if ($a)
     { /(b)/;
       print "$1\n";
     }
    print "$1\n";

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

    a
    b
    a

    Во внешнем регулярном выражении переменная $1 получает значение a, в регулярном выражении внутри блока эта переменная получает значение b. Затем при выходе из блока мы пытаемся использовать это последнее присвоенное значение. И тут нас поджидает сюрприз (не знаю, приятный или нет): вне блока переменная $1 опять имеет свое старое значение. Это может породить труднонаходимые ошибки в программе, если не позаботиться о сохранении необходимых специальных переменных в переменных my.

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

    $_='ab';
    /(a)/;
    print "$1\n";
    subr();
    print "$1\n";
    
    sub subr()
    { /(b)/;
      print "$1\n";
    }

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

    a
    b
    a

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

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

    Регулярные выражения в 5-ой версии Perl не поддерживают именованного сохранения найденных фрагментов текста, а поддерживают только переменные с номером. Это создает неудобства, а при использовании объектов регулярных выражений, которые сохраняют найденные фрагменты текста, тяжело модифицировать эти объекты, если надо ввести новую сохраняющую переменную. В 6-й версии Perl должно появиться именованное сохранение, когда вместо номера переменной можно задать ей произвольное имя и обращаться к такой переменной по имени. А пока для этого остается использовать встроенный код и специальную переменную $^N. Например, у нас есть объект регулярного выражения, который сохраняет URL:

    my $re=qr/<a\s+href="([^"]+)"/i;

    Мы можем его использовать в коде

    $_='<a href="http://www.intuit.ru">';
    print $1 if /$re/;

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

    http://www.intuit.ru

    URL получается в переменной $1. Если объект регулярного выражения $re входит кирпичиком в более крупное регулярное выражение, то нумерованные переменные использовать рискованно, т.к. при вставке новых объектов или редактировании существующих нумерация может сбиться и URL может оказаться уже не в переменной $1, а в другой нумерованной переменной. Мы можем объявить переменную $url и всегда сохранять в ней найденный URL, воспользовавшись тем, что переменная $^N является копией нумерованной переменной, которая соответствует последней паре захватывающих скобок:

    my $url;
    my $re=qr/<a\s+href="([^"]+)(?{$url=$^N})"/i;
    $_='<a href="http://www.intuit.ru">';
    print $url if /$re/;

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

    http://www.intuit.ru

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

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

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

    Perl не создает каждый раз при использовании регулярного выражения специальные переменные $1, $+, $` и т.д., он просто создает копию целевого текста и эти переменные ссылаются на фрагменты текста в этой копии. Это экономит время, ведь не всегда эти переменные потом будут затребованы программистом.

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

    Будет, т.к. существуют еще переменные $`, $ и $'. Perl не может решить, к какому регулярному выражению применяются эти переменные, и будет создавать копию целевого текста каждый раз, несмотря на отсутствие захватывающих скобок. По этой причине переменные $`, $ и $' называются "вредными". Кроме того, их применение замедляет работу программы.

    Транслятор просматривает всю программу и все используемые ею модули на предмет наличия этих "вредных" переменных. И если он их не находит, то выпоняет оптимизацию программы: не создает копию целевого текста для регулярных выражений, которые не используют захватывающих скобок. Базовые модули, которые входят в поставку Perl, за исключением модуля English, не используют этих переменных.

    "Вредные" переменные $`, $ и $' можно имитировать с помощью массивов @- и @+. (Предполагаем, что целевой текст находится в переменной $_ ):

  • $` соответствует substr($_, 0, $-[0])
  • $ соответствует substr($_, $-[0], $+[0] - $-[0])
  • $' соответствует substr($_, $+[0])
  • Страницы:

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

    В языках программирования существуют глобальные и закрытые (private) переменные, которые объявляются директивой my (…). В Perl специальные глобальные переменные, такие, как $_, $1, @ARGV, не объявляются и доступны из любой точки программы. Если вы не используете директиву use strict (или use strict 'vars' ) и объявляете в программе переменные ( $a и т.д.), то эти переменные будут глобальными для данного пакета. Если вы употребили директиву use strict, вы должны будете объявлять эти переменные директивой our.

    Переменные my имеют лексическую видимость и видны в минимальном блоке { … }, в котором находится директива my. (Это утверждение также относится к блоку кода Perl (?{…} ) внутри регулярного выражения.

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

    $_=1;
    { local $_=2;
      print "$_\n";
    }
    print "$_\n";

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

    2
    1

    Внутри блока программа работает с копией переменной $_, которая имеет то же имя, а при выходе из блока эта копия уничтожается, и программе становится доступна переменная $_, которая существовала до входа в этот блок. Директива local иногда применяется в подпрограммах, хотя в них логичнее создавать внутренние переменные директивой my. Директива local не применяется к переменным, созданным директивой my.

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

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

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

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

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

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

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

    Теперь рассмотрим такой пример:

    my $a=1;
    $_='a';
    /(a)/;
    print "$1\n";
    /(b)/;
    print "$1\n";

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

    a
    a

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

    Теперь рассмотрим более интересный пример:

    my $a=1;
    $_='ab';
    /(a)/;
    print "$1\n";
    if ($a)
     { /(b)/;
       print "$1\n";
     }
    print "$1\n";

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

    a
    b
    a

    Во внешнем регулярном выражении переменная $1 получает значение a, в регулярном выражении внутри блока эта переменная получает значение b. Затем при выходе из блока мы пытаемся использовать это последнее присвоенное значение. И тут нас поджидает сюрприз (не знаю, приятный или нет): вне блока переменная $1 опять имеет свое старое значение. Это может породить труднонаходимые ошибки в программе, если не позаботиться о сохранении необходимых специальных переменных в переменных my.

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

    $_='ab';
    /(a)/;
    print "$1\n";
    subr();
    print "$1\n";
    
    sub subr()
    { /(b)/;
      print "$1\n";
    }

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

    a
    b
    a

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

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

    Регулярные выражения в 5-ой версии Perl не поддерживают именованного сохранения найденных фрагментов текста, а поддерживают только переменные с номером. Это создает неудобства, а при использовании объектов регулярных выражений, которые сохраняют найденные фрагменты текста, тяжело модифицировать эти объекты, если надо ввести новую сохраняющую переменную. В 6-й версии Perl должно появиться именованное сохранение, когда вместо номера переменной можно задать ей произвольное имя и обращаться к такой переменной по имени. А пока для этого остается использовать встроенный код и специальную переменную $^N. Например, у нас есть объект регулярного выражения, который сохраняет URL:

    my $re=qr/<a\s+href="([^"]+)"/i;

    Мы можем его использовать в коде

    $_='<a href="http://www.intuit.ru">';
    print $1 if /$re/;

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

    http://www.intuit.ru

    URL получается в переменной $1. Если объект регулярного выражения $re входит кирпичиком в более крупное регулярное выражение, то нумерованные переменные использовать рискованно, т.к. при вставке новых объектов или редактировании существующих нумерация может сбиться и URL может оказаться уже не в переменной $1, а в другой нумерованной переменной. Мы можем объявить переменную $url и всегда сохранять в ней найденный URL, воспользовавшись тем, что переменная $^N является копией нумерованной переменной, которая соответствует последней паре захватывающих скобок:

    my $url;
    my $re=qr/<a\s+href="([^"]+)(?{$url=$^N})"/i;
    $_='<a href="http://www.intuit.ru">';
    print $url if /$re/;

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

    http://www.intuit.ru

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

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

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

    Perl не создает каждый раз при использовании регулярного выражения специальные переменные $1, $+, $` и т.д., он просто создает копию целевого текста и эти переменные ссылаются на фрагменты текста в этой копии. Это экономит время, ведь не всегда эти переменные потом будут затребованы программистом.

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

    Будет, т.к. существуют еще переменные $`, $ и $'. Perl не может решить, к какому регулярному выражению применяются эти переменные, и будет создавать копию целевого текста каждый раз, несмотря на отсутствие захватывающих скобок. По этой причине переменные $`, $ и $' называются "вредными". Кроме того, их применение замедляет работу программы.

    Транслятор просматривает всю программу и все используемые ею модули на предмет наличия этих "вредных" переменных. И если он их не находит, то выпоняет оптимизацию программы: не создает копию целевого текста для регулярных выражений, которые не используют захватывающих скобок. Базовые модули, которые входят в поставку Perl, за исключением модуля English, не используют этих переменных.

    "Вредные" переменные $`, $ и $' можно имитировать с помощью массивов @- и @+. (Предполагаем, что целевой текст находится в переменной $_ ):

  • $` соответствует substr($_, 0, $-[0])
  • $ соответствует substr($_, $-[0], $+[0] - $-[0])
  • $' соответствует substr($_, $+[0])
  • Вернуться к учебному плану