≪
≫

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

Теперь рассмотрим замену n-го найденного фрагмента текста.

use locale;

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

my $count=0;

$s =~ з/(тел.s+)([d-]+)/

++$count == 3 ? “${ Ц9-9999” : l'$1$2”

/eg і;

print $s;

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

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

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

Мы хотели третий номер телефона заменить на 9-9999, но после каждого найденного номера выполняется замена, поэтому то, что не нужно менять, должно быть заменено «на себя». Для этого мы взяли все перед номером телефона в переменную $1, а все остальное — в переменную $2. Если номер телефона не равен 3, то мы найденный фрагмент текста (который соответствует всему регулярному выражению!) меняем на строку $1$2, а в третьем случае в замене вместо номера телефона подставляем 9-9999. Для разделения имени переменной от цифр используем фигурные скобки. Не забываем также поставить модификатор е.

Как быть, когда нужно заменить n-й найденный от конца фрагмент текста, если заранее неизвестно, сколько таких фрагментов будет найдено? Рассмотрим такуто идею.

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

#!/usr/bin/perl -w

use strict;

use locale;

my $s=’Ten. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122’; my $tel=’тел.s+[d-]+’;

my $n=1;

$s =~ s/(Ten.s+)[d-]+ # ищем слово “тел." + номер

  • (?=(?:.*?$tel){$n) # за которым $п раз идет “тел.” + номер
  • (?!.*?$tel) й после чего не встречается “тел.” +номер )/${1}9-9999/isx;

print $s;

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

Т.к. литерал meji.s+[d-]+ в регулярном выражении будет встречаться несколько раз, то оформим его в виде переменной 57е/, которую будем подставлять. Сформулируем задачу в терминах регулярных выражений: нам надо найти фрагмент, соответствующий шаблону meji.s+[d-]+, за которым (фрагментом) в тексте встречается ровно 5л таких же фрагментов. Номер телефона в таком фрагменте мы меняем на 9-9999.

Чтобы в этом фрагменте всё, кроме номера телефона, сохранилось, мы берем это остальное в скобки и получаем подшаблон (mesi.s+)[d-J+, с которого начинается регулярное выражение. За фрагментом текста, соответствующим этому шаблону, должен идти фрагмент

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

Что ж, как будто бы все верно, начинаем проверять работу этой программы. Задаем $п=0 и видим результат:

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

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

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

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

Разберем работу этого регулярного выражения при 5'л=7. После того как нашелся первый номер телефона по подшаблону. началось

заглядывание вперед, и подшаблон (?:*?$tel){$n} поглотил второй телефонный номер. За ним началась проверка (?!*?$tel), которая закончилась неудачей. т.к. после второго телефонного номера есть еще номер. «Не беда! — говорит механизм поиска соответствия. — Буду увеличивать значение минимального квантификатора .*?в подшаблоне (?;.*?$tel){$n}, авось поможет». И начинает его увеличивать, и пробовать эти значения. Когда этот квантификатор захватит все до вертикальной черты в следующей строке:

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

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

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

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

символ, поэтому конструкция вообще говоря, не годится, у нас

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

(?:(?!)*$tel

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

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

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

use locale;

my $s=’Ten. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122’; my $tel=’тел.s+[d-]+’;

my $n=2;

$s =~ s/(Ten.s+)[d-]+ it ищем слово “тел.” + номер

(?=(?:(?:(?!$tel). )*$tel){$n} it за которым $n раз идет it "тел.” + номер (?!.*?$tel) it после чего не встречается "тел.” +номер )/${1}9-9999/isx;

print $s;

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

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

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

 
Посмотреть оригинал