Опубликован: 18.06.2007 | Доступ: свободный | Студентов: 1354 / 35 | Оценка: 4.14 / 3.29 | Длительность: 12:44:00
ISBN: 978-5-94774-604-4
Лекция 6:

Предотвращение зацикливания при поиске и замене. Якорь \G, итеративный поиск с модификаторами g и gc

< Лекция 5 || Лекция 6: 1234 || Лекция 7 >

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

Мнимый символ \G означает позицию конца предыдущего совпадения. Это аналог функции pos, но только внутри регулярного выражения. Этот метасимвол впервые появился в Perl для проведения глобального поиска и замены. Он совпадает с позицией, в которой закончилась предыдущая итерация поиска с модификатором g. Свое значение этот якорь хранит также и после окончания работы оператора поиска/замены, как и функция pos. При первой итерации поиска/замены или после сброса текущей позиции поиска в начало текста \G совпадает в начале текста как и метасимвол \A. При принудительной переустановке текущей позиции поиска при обращении к функции pos это якорь соответствует установленной позиции. Рассмотрим примеры, которые показывают, как работает якорь \G и чем он может быть полезен.

Мы уже рассматривали примеры поиска в скалярном контексте с модификатором g. При обращению к другому или тому же оператору с модификатором g и той же переменной с целевым текстом отыскивается следующее совпадение. Но оно ищется без привязки к концу предыдущего совпадния и может быть найдено с любой позиции после конца предыдущего совпадения. Иногда бывает нужно, чтобы следующий поиск был привязан к концу последнего совпадения и чтобы поиск заканчивался неудачей, если поиск следующего образца начинается не с этой начальной позиции. Это аналогично привязке к началу текста \A.

Например, мы ищем слова, разделенные вертикальной чертой:

$_='|ab|cd   |ef';
while (/\|(\w+)/g) { print "$1\n" }

Этот пример выведет

ab
cd
ef

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

$_='|ab|cd   |ef';
while (/\G\|(\w+)/g) { print "$1\n" }

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

ab
cd

Якорь \G надежно работает, если он стоит в самом начале регулярного выражения, которое не имеет высокоуровневой конструкции выбора альтернативы. Если такая конструкция имеется, то якорь \G надо выносить за скобки:

/\G(?: шаблон1 | шаблон2 | … )/

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

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

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

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

!a!b!c!d!

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

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

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

!abcd

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

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

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

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

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

bcd

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

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

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

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

a b c d
Found a

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

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

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

a b c d
a b c d

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

< Лекция 5 || Лекция 6: 1234 || Лекция 7 >
Константин Бражников
Константин Бражников
Россия
Mike .
Mike .
Россия