В \w. В множество \w входят все строчные и прописные латинские буквы, десятичные цифры и знак подчерка: [a-zA-Z0-9_]. К этому множеству относятся все символы, считающиеся буквами в той локальной установке, которая используется. Для ActiveState
use locale;
и все русские буквы (включая буквы ё и Ё) в кодировке Windows-1251 также будут считаться буквами.
Если вы под буквами понимаете что-то другое (например, вы не считаете символ подчерка за букву), то определите соответствующий класс. Если вас интересуют все непробельные символы, то используйте \S.
\w соответствует ровно одной "букве". Для слов применяйте \w+. Слово всегда ограничено с обеих сторон \b - границами слов. Действие этих якорей также зависит от локальной установки операционной системы. Для гарантии того, что совпадение со словом начнется и закончится на его границе, можно применять \b\w+\b. Чтобы уничтожить ненужные сохраненные состояния, можно также использовать \b(?>\w+)\b.
В \b. В некоторых языках программирования для этих символов существуют отдельные
(?<!\w)(?=\w)
Слева нет буквы, а справа она есть.
А вот
(?<=\w)(?!\w)
Слева есть буква, а справа буквы нет.
Я надеюсь, вы уже понимаете разницу между выражениями (?!\w) и (?=\W). В первом случае справа может и ничего не стоять (конец текста), а второй случай требует, чтобы справа стоял символ, который не является буквой.
По умолчанию, i отключен и сравнение символов чувствительно к регистру. Если вам все равно, большими или маленькими буквами написано то, что вы ищете, то применяйте i: m/…/i. Бывает так, что что-то отыскивается с учетом регистра символов, а что-то - без такого учета. В этом случае можно использовать интервальное задание
/ab(cd(?-i)ef(?i:gh)ij)kl/i;
В этом i, т.е. сравнение производится без учета регистра символов. По такому правилу отыскиваются символы ab и cd. Затем внутри (?-i) выключает i, и символы ef отыскиваются с учетом регистра. Внутри скобочной конструкции (?i:gh) опять включается режим сравнения без учета регистра символов, и символы gh отыскиваются без учета регистра. Символы ij стоят после этой конструкции, а в этом месте восстанавливается режим -i, который был задан выражением (?-i), и символы ij отыскиваются с учетом регистра. Символы kl отыскиваются
опять без учета регистра, т.к. стоят за закрывающей скобкой, за которой кончается область действия выражения (?-i) и восстанавливается действие глобального i.
Также заметьте, что i зависит от локальной установки системы, ведь он действует только на сравнение букв.
\A, \Z и \z не зависят от установок ^ и $, работа которых зависит от m. В зоне, где m отключен, ^ соответствует \A, а $ - \Z. Рассмотрим такой пример:
$_="a\r\n"; print '1' if m'a\r$\n';
Здесь я использовал в качестве ограничителей $\. $ стоит перед символом \n, который является последним в тексте, и на печать выйдет единица.
Если переставить символ $ перед символом \r:
$_="a\r\n"; print '1' if m'a$\r\n';
то поиск закончится неудачей, потому что здесь $ не стоит непосредственно перед символом \n.
Теперь вернемся к первому примеру и добавим в конце текста какой-нибудь символ:
$_="a\r\nb"; print '1' if m'a\r$\n';
В этом случае поиск вновь завершится неудачно, потому что символ \n здесь уже не последний в тексте. Но если поставить m, то поиск закончится удачей:
$_="a\r\nb"; print '1' if m'a\r$\n'm;
Теперь такой пример:
$_="a\n"; print '1' if /a\n^/m;
Единица напечатана не будет, потому что ^ не совпадает после символа \n, если он стоит последним в тексте, и m здесь не поможет. Но если после \n поставить еще символ:
$_="a\n\n"; print '1' if m'a\n^'m;
то совпадение будет найдено. Теперь в последнем примере уберем m:
$_="a\n\n"; print '1' if /a\n^/;
Совпадения m якорь ^ совпадает только в начале текста.
Для поиска множественных совпадений можно применить g и скалярный контекст. Например:
while ('12345' =~ /\d/g) { print "Найдена еще цифра: $\n" }
Будет напечатано:
Найдена еще цифра: 1 Найдена еще цифра: 2 Найдена еще цифра: 3 Найдена еще цифра: 4 Найдена еще цифра: 5
Иногда для поиска множественных совпадений нужно искать следующее сразу от конца предыдущего совпадения, аналогично привязке к началу текста \A. Для этого есть специальный \G, о котором мы поговорим позже.
g и списковый контекст позволяет запомнить сразу все совпадения в тексте:
my @a='12345' =~ /\d/g; print join ',',@a;
Получаем
1,2,3,4,5
С g происходит поиск всех прототипов while:
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $count=0;
while ($s =~ /тел\.\s+([\d-]+)/gi)
{ if (++$count == 3)
{ print "Третий номер: $1\n";
}
}
На печать выйдет строка
Третий номер: 4-1122
В $1 последовательность из цифр и знака минус, которая стоит после фрагмента "тел. ". g обеспечивает поиск всех прототипов шаблона друг за другом, сохраняя в переменной $s смещение конца предыдущего совпадения. При очередном успехе оператор поиска возвращает единицу, что заставляет цикл while продолжать работать. После того, как будет найден последний номер телефона, оператор поиска вернет пустую строку, и оператор while закончит выполнение. Также можно было бы после печати третьего телефона поставить оператор last.
i нужен, т.к. не все фрагменты "тел." набраны строчными буквами. Но тогда нужна русская локаль, чтобы i действовал также на русские буквы.
Иначе в этом примере совпадение начнется со второго телефонного номера и на печать ничего не выйдет.
С таким же успехом можно было использовать цикл for:
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
for (my $count=0; $s =~ /тел\.\s+([\d-]+)/gi;)
{ if (++$count == 3)
{ print "Третий номер: $1\n";
last;
}
}
Наконец, можно использовать встроенный код g, иначе после нахождения первого телефона поиск закончится:
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $count=0;
my @a=$s =~ /тел\.\s+([\d-]+)
(?{
if (++$count == 3)
{ print "Третий номер: $1\n";
}
})/gix;
Это, конечно, не так красиво из-за того, что пришлось присваивать все найденные телефоны массиву.
Теперь рассмотрим замену n-го найденного фрагмента текста.
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $count=0;
$s =~ s/(тел\.\s+)([\d-]+)/
++$count == 3 ? "${1}9-9999" : "$1$2"
/egi;
print $s;
Напечатается строка
Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 9-9999
Оператор подстановки с g заменяет все найденные фрагменты текста глобально, и ему для этого не нужен списочный контекст, как оператору поиска.
Мы хотели третий номер телефона заменить на 9-9999, но после каждого найденного номера выполняется замена, поэтому то, что не нужно менять, должно быть заменено "на себя". Для этого мы взяли все перед номером телефона в переменную $1, а все остальное - в переменную $2. Если номер телефона не равен 3, то мы найденный фрагмент текста (который соответствует всему $1$2, а в третьем случае в замене вместо номера телефона подставляем 9-9999. Для разделения имени переменной от цифр используем фигурные скобки. Не забываем также поставить e.
Как быть, когда нужно заменить n-й от конца найденный фрагмент текста, если заранее неизвестно, сколько таких фрагментов будет найдено? Рассмотрим такую идею.
Призываем в помощь конструкцию опережающей проверки и записываем n отсчитывается с нуля. Пусть это n хранится в переменной $n. Попробуем вначале такую программу для решения задачи:
#!/usr/bin/perl -w
use strict;
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $tel='тел\.\s+[\d-]+';
my $n=1;
$s =~ s/(тел\.\s+)[\d-]+ # ищем слово "тел." + номер
(?=(?:.*?$tel){$n} # за которым $n раз идет "тел." + номер
(?!.*?$tel) # после чего не встречается "тел." +номер
)/${1}9-9999/isx;
print $s;
Поясню, как она должна работать по первоначальному замыслу.
Т.к. литерал тел\.\s+[\d-]+ в $tel, которую будем подставлять. Сформулируем задачу в терминах тел\.\s+[\d-]+, за которым (фрагментом) в тексте встречается ровно $n таких же фрагментов. Номер телефона в таком фрагменте мы меняем на 9-9999.
Чтобы в этом фрагменте всё, кроме номера телефона, сохранилось, мы берем это остальное в скобки и получаем (тел\.\s+)[\d-]+, с которого начинается (?:.*?$tel){$n}. ."*" впереди него означает, что эти $n фрагментов не обязательно должны идти сразу за первым фрагментом и друг за другом, между ними могут быть посторонние включения, которые поглощаются конструкцией ".*?". После того, как эти $n фрагментов поглощены, не должно стоять такого же фрагмента текста с любой позиции после этих $n фрагментов, это проверяет условие (?!.*?$tel). Оба этих (?:.*?$tel){$n} и (?!.*?$tel) включены в позиционную проверку (?=…). Т.е. за найденным номером телефона должен быть фрагмент текста, который стоит внутри всей этой проверки (?=…).
Что ж, как будто бы все верно, начинаем проверять работу этой программы. Задаем $n=0 и видим результат:
Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 9-9999
Верно! Заменился номер телефона, который стоит нулевым справа. Далее даем $n значение 1 и смотрим результат… Вот неожиданность: заменился первый телефон
Тел. 9-9999. Другой тел. 3-2233, а вот еще один тел. 4-1122
а должен был бы тот, что посередине! При $n=2 - та же картина… Видимо, где-то закралась ошибка. Можете вы найти (и исправить) ее самостоятельно? Тогда читайте дальше.
Разберем работу этого $n=1. После того, как нашелся первый номер телефона по (тел\.\s+)[\d-]+, началось заглядывание вперед, и (?:.*?$tel){$n} поглотил второй телефонный номер. За ним началась проверка (?!.*?$tel), которая закончилась неудачей, т.к. после второго телефонного номера есть еще номер. "Не беда! - говорит механизм поиска соответствия. - Буду увеличивать значение минимального .*? в (?:.*?$tel){$n}, авось поможет." И начинает его увеличивать и пробовать эти значения. Когда этот
Тел. 2-3344. Другой тел. 3-2233, а вот еще один т|ел. 4-1122
весь
, а вот еще один тел. 4-1122
т.е. все до конца текста, и после этого проверка (?!.*?$tel) пройдет успешно. Налицо совпадение всего .*? начал поглощать символы, которые относились к номеру телефона, а он не должен был этого делать. Надо заменить его на правильный
Когда речь шла о пропуске символов до закрывающей угловой скобки при поиске ссылки, то все было просто: [^>]*, но здесь уже не один символ, поэтому конструкция [^$tel]*, вообще говоря, не годится, у нас не просто множество символов, а часть текста. Вот практический прием пропуска символов до данного фрагмента текста:
(?:(?!$tel).)*$tel
Мы каждый раз в цикле проверяем, находится ли в текущей позиции фрагмент текста $tel, и если нет, то берем следующий символ точкой. А после этого цикла должен идти текст $tel. Такой цикл хотя и медлителен, но он гарантирует, что мы не проскочим мимо искомого фрагмента текста.
Вся программа теперь выглядит так:
#!/usr/bin/perl -w
use strict;
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $tel='тел\.\s+[\d-]+';
my $n=2;
$s =~ s/(тел\.\s+)[\d-]+ # ищем слово "тел." + номер
(?=(?:(?:(?!$tel).)*$tel){$n} # за которым $n раз идет "тел." + номер
(?!.*?$tel) # после чего не встречается "тел." +номер
)/${1}9-9999/isx;
print $s;
Она верно заменяет нужный телефонный номер, а если задано слишком большое значение для $n, которого нет, замена не производится. Обратите внимание, что в (?!.*?$tel) мы не стали заменять конструкцию .*? на новую, т.к. здесь она работает правильно: если впереди есть текст, соответствующий .*?$tel, то он будет найден и негативная опережающая проверка (?!.*?$tel) вернет ложь.
Если вы в данное
[\d-]+ можно заключить в атомарные скобки, т.к. нет смысла возвращать цифры номера телефона, это не приведет к совпадению.
Другой способ заменить n-е от конца совпадение - повторять в цикле while поиск номера телефона, взяв его в $-[1] и $+[1]. Затем отсчитать от конца массивов $n, получить смещение начала и конца нужного телефонного номера и воспользоваться функцией substr, которой можно присваивать значение.
Для привязки к началу текста можно использовать \A или ^ без m.
Вот как можно проверить, что текст не начинается с точки:
if ($text =~ /^\./) { print "Текст не должен начинаться с точки!" }
Так можно вставить по одному пробелу в начало каждой строки текста:
$_=<<EOF; Первая строка. Вторая строка. Третья строка. EOF s/^/ /gm;
Без m пробел будет вставлен лишь в начало первой строки.
Пустые строки удаляются оператором замены с таким
$_=<<EOF; Первая строка. Вторая строка. Третья строка. EOF s/^\n//gm; print $_;
Получаем на выходе:
Первая строка. Вторая строка. Третья строка.
Может показаться, что оператор s/^$//gm тоже должен удалить пустые строки, но он не сделает замены. Удалиться должен реальный символ, а не совпадение с началом и концом строки, поэтому сперва он должен быть найден. А что-то вставить в пустую строку таким оператором s вполне возможно.
При преобразовании текста в HTML нужно заменять символы на amp; Это делается просто:
s//amp;/g
ведь амперсанд не является
Предположим, что нам надо найти последнее число в большом тексте. Здесь можно воспользоваться таким приемом: использовать конструкцию .* с s, которая мгновенно захватит весь текст и выйдет на его конец. Т.к. совпадение с числом не будет найдено, то * будет отдавать по одному символу, пока не отыщется совпадение. Как будто все просто. Попробуем такой пример:
$_=<<EOF; Первая строка 123. Вторая строка 456. Третья строка 789. EOF /.*(\d+)/s; print $1;
На печать выходит лишь цифра девять: 9. Удивлены? 9. Этого достаточно для совпадения \d+. Чтобы получить правильный результат, надо потребовать, чтобы перед \d+ не было цифры:
$_=<<EOF; Первая строка 123. Вторая строка 456. Третья строка 789. EOF /.*(?<!\d)(\d+)/s; print $1;
Печатает 789.
Чтобы привязать \z (для привязки к истинному концу текста) или \Z ( $ без m ):
$_=<<EOF; Первая строка 123. Вторая строка 456. Третья строка 789 EOF /(\d+)$/; print $1;
Печатает 789. Как видим, привязка к концу текста нашла последнее число без захвата всего текста и без заглядывания назад. Но если после последнего числа могут быть символы, то
$_=<<EOF; Первая строка 123. Вторая строка 456. Третья строка 789. EOF /(\d+)\D*$/; print $1;
Опять получаем 789, но поиск будет медленнее из-за увеличивающегося числа проверок.
У вебмастеров иногда возникает задача укоротить длинные URL, которые вводят в форму участники форумов. Из-за длинных ссылок расползается дизайн страниц. Предположим, кто-то отослал на форум такой текст:
Рекомендую посетить: http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/
На самом деле ссылка может оказаться еще длинее. В HTML-коде страницы форума эта ссылка должна появиться в виде URL:
Рекомендую посетить: <a href="http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/"
target="_blank">
http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/</a>
Внутри тега a ссылку, естественно, обрезать не надо, тем более, что в видимом тексте ее не будет, а вот то, что стоит перед тегом </a>, будет видно, и если это "слово" будет слишком длинным, то это может испортить дизайн. Надо после 50-го символа такой ссылки вставить три точки и получить такой текст:
Рекомендую посетить: <a href="http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/"
target="_blank">
http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.co…</a>
Другие длинные слова (не ссылки) надо также урезать. Мало ли что может ввести пользователь… Я нашел такое решение:
s/(\A|\s)(?!href=")(\S{50})\S+/$1$2.../g;
Предполагаем, что текст находится в переменной $_. href=, то проверяется, есть ли после этого "слово" длиной 50 символов, после которого стоит непробельный символ. Если да, то происходит замена всего, что найдено на $1 - пробельный символ перед длинным "словом", 50 символов с начала этого "слова" на $1, эти же 50 символов с начала длинного "слова", а последующие непробельные символы заменяются на три точки. Если бы в начале шаблона вместо (\A|\s) стояло просто (\s), то длинное "слово", стоящее в самом начале текста, не было бы укорочено.
Вот еще один вариант с использованием кода
s/((\S{50})\S+)/index($1,'href=') > -1 ? $1 : "$2..."/ge;
Ищем 50 непробельных символов, сразу за которыми есть хотя бы один непробельный символ. Эти 50 символов берем в переменную $2, а все длинное "слово" берем в переменную $1. В части замены проверяем, есть ли фрагмент текста href= в перемнной $1.
Если он есть, то заменяем найденное на $1, т.е. на себя, а если нет, то заменяем найденное на текст $2, за которым идут три точки.
Это был практический пример задачи, которую я решал по просьбе одного вебмастера.
Предположим, мы хотим узнать, есть ли в данном тексте непробельный символ между тегами. Предполагается, что теги правильно закрыты и нетеговых символов < и > не встречается. Вот первое решение:
$_=' <pppp>' x 13000;
$_.='<table>a';
if (/[^\s<>](?![^<>]*>)/g)
{ print "1\n".pos;
} else
{ print "0\n";
}
Вначале мы создаем длинную строку (больше 90000 символов) из тегов, в конец которой вставляем букву a вне тегов. В [^<>]*>. В случае нахождения такого символа программа печатает единицу и смещение этого символа от начала текста.
Вот другое решение, которое в отличие от первого настроено быстро пропускать все символы, которые нас не интересуют, т.е. пробельные и теги <> вместе с их содержимым:
/\A(?>(?:(?>\s*)<(?>[^>]*)>)*)\S/g
Оно кажется сложным из-за наличия трех
/\A(?:\s*<[^>]*>)*\S/g
Внутри скобок мы пропускаем последовательности пробельных символов \s* и конструкции <[^>]*>. Обратите внимание, что между этими подшаблонами излишне ставить знак альтернативы |. Вначале стоит привязка \A, чтобы поиск не начался внутри тега.
Какой метод поиска выбрать, чтобы скорость работы программы была максимальной, - искать ли нужный фрагмент или быстро пропускать все до него, - зависит от того, насколько плотно сидят нужные фрагменты в тексте.
В \w. В множество \w входят все строчные и прописные латинские буквы, десятичные цифры и знак подчерка: [a-zA-Z0-9_]. К этому множеству относятся все символы, считающиеся буквами в той локальной установке, которая используется. Для ActiveState
use locale;
и все русские буквы (включая буквы ё и Ё) в кодировке Windows-1251 также будут считаться буквами.
Если вы под буквами понимаете что-то другое (например, вы не считаете символ подчерка за букву), то определите соответствующий класс. Если вас интересуют все непробельные символы, то используйте \S.
\w соответствует ровно одной "букве". Для слов применяйте \w+. Слово всегда ограничено с обеих сторон \b - границами слов. Действие этих якорей также зависит от локальной установки операционной системы. Для гарантии того, что совпадение со словом начнется и закончится на его границе, можно применять \b\w+\b. Чтобы уничтожить ненужные сохраненные состояния, можно также использовать \b(?>\w+)\b.
В \b. В некоторых языках программирования для этих символов существуют отдельные
(?<!\w)(?=\w)
Слева нет буквы, а справа она есть.
А вот
(?<=\w)(?!\w)
Слева есть буква, а справа буквы нет.
Я надеюсь, вы уже понимаете разницу между выражениями (?!\w) и (?=\W). В первом случае справа может и ничего не стоять (конец текста), а второй случай требует, чтобы справа стоял символ, который не является буквой.
По умолчанию, i отключен и сравнение символов чувствительно к регистру. Если вам все равно, большими или маленькими буквами написано то, что вы ищете, то применяйте i: m/…/i. Бывает так, что что-то отыскивается с учетом регистра символов, а что-то - без такого учета. В этом случае можно использовать интервальное задание
/ab(cd(?-i)ef(?i:gh)ij)kl/i;
В этом i, т.е. сравнение производится без учета регистра символов. По такому правилу отыскиваются символы ab и cd. Затем внутри (?-i) выключает i, и символы ef отыскиваются с учетом регистра. Внутри скобочной конструкции (?i:gh) опять включается режим сравнения без учета регистра символов, и символы gh отыскиваются без учета регистра. Символы ij стоят после этой конструкции, а в этом месте восстанавливается режим -i, который был задан выражением (?-i), и символы ij отыскиваются с учетом регистра. Символы kl отыскиваются
опять без учета регистра, т.к. стоят за закрывающей скобкой, за которой кончается область действия выражения (?-i) и восстанавливается действие глобального i.
Также заметьте, что i зависит от локальной установки системы, ведь он действует только на сравнение букв.
\A, \Z и \z не зависят от установок ^ и $, работа которых зависит от m. В зоне, где m отключен, ^ соответствует \A, а $ - \Z. Рассмотрим такой пример:
$_="a\r\n"; print '1' if m'a\r$\n';
Здесь я использовал в качестве ограничителей $\. $ стоит перед символом \n, который является последним в тексте, и на печать выйдет единица.
Если переставить символ $ перед символом \r:
$_="a\r\n"; print '1' if m'a$\r\n';
то поиск закончится неудачей, потому что здесь $ не стоит непосредственно перед символом \n.
Теперь вернемся к первому примеру и добавим в конце текста какой-нибудь символ:
$_="a\r\nb"; print '1' if m'a\r$\n';
В этом случае поиск вновь завершится неудачно, потому что символ \n здесь уже не последний в тексте. Но если поставить m, то поиск закончится удачей:
$_="a\r\nb"; print '1' if m'a\r$\n'm;
Теперь такой пример:
$_="a\n"; print '1' if /a\n^/m;
Единица напечатана не будет, потому что ^ не совпадает после символа \n, если он стоит последним в тексте, и m здесь не поможет. Но если после \n поставить еще символ:
$_="a\n\n"; print '1' if m'a\n^'m;
то совпадение будет найдено. Теперь в последнем примере уберем m:
$_="a\n\n"; print '1' if /a\n^/;
Совпадения m якорь ^ совпадает только в начале текста.
Для поиска множественных совпадений можно применить g и скалярный контекст. Например:
while ('12345' =~ /\d/g) { print "Найдена еще цифра: $\n" }
Будет напечатано:
Найдена еще цифра: 1 Найдена еще цифра: 2 Найдена еще цифра: 3 Найдена еще цифра: 4 Найдена еще цифра: 5
Иногда для поиска множественных совпадений нужно искать следующее сразу от конца предыдущего совпадения, аналогично привязке к началу текста \A. Для этого есть специальный \G, о котором мы поговорим позже.
g и списковый контекст позволяет запомнить сразу все совпадения в тексте:
my @a='12345' =~ /\d/g; print join ',',@a;
Получаем
1,2,3,4,5
С g происходит поиск всех прототипов while:
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $count=0;
while ($s =~ /тел\.\s+([\d-]+)/gi)
{ if (++$count == 3)
{ print "Третий номер: $1\n";
}
}
На печать выйдет строка
Третий номер: 4-1122
В $1 последовательность из цифр и знака минус, которая стоит после фрагмента "тел. ". g обеспечивает поиск всех прототипов шаблона друг за другом, сохраняя в переменной $s смещение конца предыдущего совпадения. При очередном успехе оператор поиска возвращает единицу, что заставляет цикл while продолжать работать. После того, как будет найден последний номер телефона, оператор поиска вернет пустую строку, и оператор while закончит выполнение. Также можно было бы после печати третьего телефона поставить оператор last.
i нужен, т.к. не все фрагменты "тел." набраны строчными буквами. Но тогда нужна русская локаль, чтобы i действовал также на русские буквы.
Иначе в этом примере совпадение начнется со второго телефонного номера и на печать ничего не выйдет.
С таким же успехом можно было использовать цикл for:
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
for (my $count=0; $s =~ /тел\.\s+([\d-]+)/gi;)
{ if (++$count == 3)
{ print "Третий номер: $1\n";
last;
}
}
Наконец, можно использовать встроенный код g, иначе после нахождения первого телефона поиск закончится:
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $count=0;
my @a=$s =~ /тел\.\s+([\d-]+)
(?{
if (++$count == 3)
{ print "Третий номер: $1\n";
}
})/gix;
Это, конечно, не так красиво из-за того, что пришлось присваивать все найденные телефоны массиву.
Теперь рассмотрим замену n-го найденного фрагмента текста.
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $count=0;
$s =~ s/(тел\.\s+)([\d-]+)/
++$count == 3 ? "${1}9-9999" : "$1$2"
/egi;
print $s;
Напечатается строка
Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 9-9999
Оператор подстановки с g заменяет все найденные фрагменты текста глобально, и ему для этого не нужен списочный контекст, как оператору поиска.
Мы хотели третий номер телефона заменить на 9-9999, но после каждого найденного номера выполняется замена, поэтому то, что не нужно менять, должно быть заменено "на себя". Для этого мы взяли все перед номером телефона в переменную $1, а все остальное - в переменную $2. Если номер телефона не равен 3, то мы найденный фрагмент текста (который соответствует всему $1$2, а в третьем случае в замене вместо номера телефона подставляем 9-9999. Для разделения имени переменной от цифр используем фигурные скобки. Не забываем также поставить e.
Как быть, когда нужно заменить n-й от конца найденный фрагмент текста, если заранее неизвестно, сколько таких фрагментов будет найдено? Рассмотрим такую идею.
Призываем в помощь конструкцию опережающей проверки и записываем n отсчитывается с нуля. Пусть это n хранится в переменной $n. Попробуем вначале такую программу для решения задачи:
#!/usr/bin/perl -w
use strict;
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $tel='тел\.\s+[\d-]+';
my $n=1;
$s =~ s/(тел\.\s+)[\d-]+ # ищем слово "тел." + номер
(?=(?:.*?$tel){$n} # за которым $n раз идет "тел." + номер
(?!.*?$tel) # после чего не встречается "тел." +номер
)/${1}9-9999/isx;
print $s;
Поясню, как она должна работать по первоначальному замыслу.
Т.к. литерал тел\.\s+[\d-]+ в $tel, которую будем подставлять. Сформулируем задачу в терминах тел\.\s+[\d-]+, за которым (фрагментом) в тексте встречается ровно $n таких же фрагментов. Номер телефона в таком фрагменте мы меняем на 9-9999.
Чтобы в этом фрагменте всё, кроме номера телефона, сохранилось, мы берем это остальное в скобки и получаем (тел\.\s+)[\d-]+, с которого начинается (?:.*?$tel){$n}. ."*" впереди него означает, что эти $n фрагментов не обязательно должны идти сразу за первым фрагментом и друг за другом, между ними могут быть посторонние включения, которые поглощаются конструкцией ".*?". После того, как эти $n фрагментов поглощены, не должно стоять такого же фрагмента текста с любой позиции после этих $n фрагментов, это проверяет условие (?!.*?$tel). Оба этих (?:.*?$tel){$n} и (?!.*?$tel) включены в позиционную проверку (?=…). Т.е. за найденным номером телефона должен быть фрагмент текста, который стоит внутри всей этой проверки (?=…).
Что ж, как будто бы все верно, начинаем проверять работу этой программы. Задаем $n=0 и видим результат:
Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 9-9999
Верно! Заменился номер телефона, который стоит нулевым справа. Далее даем $n значение 1 и смотрим результат… Вот неожиданность: заменился первый телефон
Тел. 9-9999. Другой тел. 3-2233, а вот еще один тел. 4-1122
а должен был бы тот, что посередине! При $n=2 - та же картина… Видимо, где-то закралась ошибка. Можете вы найти (и исправить) ее самостоятельно? Тогда читайте дальше.
Разберем работу этого $n=1. После того, как нашелся первый номер телефона по (тел\.\s+)[\d-]+, началось заглядывание вперед, и (?:.*?$tel){$n} поглотил второй телефонный номер. За ним началась проверка (?!.*?$tel), которая закончилась неудачей, т.к. после второго телефонного номера есть еще номер. "Не беда! - говорит механизм поиска соответствия. - Буду увеличивать значение минимального .*? в (?:.*?$tel){$n}, авось поможет." И начинает его увеличивать и пробовать эти значения. Когда этот
Тел. 2-3344. Другой тел. 3-2233, а вот еще один т|ел. 4-1122
весь
, а вот еще один тел. 4-1122
т.е. все до конца текста, и после этого проверка (?!.*?$tel) пройдет успешно. Налицо совпадение всего .*? начал поглощать символы, которые относились к номеру телефона, а он не должен был этого делать. Надо заменить его на правильный
Когда речь шла о пропуске символов до закрывающей угловой скобки при поиске ссылки, то все было просто: [^>]*, но здесь уже не один символ, поэтому конструкция [^$tel]*, вообще говоря, не годится, у нас не просто множество символов, а часть текста. Вот практический прием пропуска символов до данного фрагмента текста:
(?:(?!$tel).)*$tel
Мы каждый раз в цикле проверяем, находится ли в текущей позиции фрагмент текста $tel, и если нет, то берем следующий символ точкой. А после этого цикла должен идти текст $tel. Такой цикл хотя и медлителен, но он гарантирует, что мы не проскочим мимо искомого фрагмента текста.
Вся программа теперь выглядит так:
#!/usr/bin/perl -w
use strict;
use locale;
my $s='Тел. 2-3344. Другой тел. 3-2233, а вот еще один тел. 4-1122';
my $tel='тел\.\s+[\d-]+';
my $n=2;
$s =~ s/(тел\.\s+)[\d-]+ # ищем слово "тел." + номер
(?=(?:(?:(?!$tel).)*$tel){$n} # за которым $n раз идет "тел." + номер
(?!.*?$tel) # после чего не встречается "тел." +номер
)/${1}9-9999/isx;
print $s;
Она верно заменяет нужный телефонный номер, а если задано слишком большое значение для $n, которого нет, замена не производится. Обратите внимание, что в (?!.*?$tel) мы не стали заменять конструкцию .*? на новую, т.к. здесь она работает правильно: если впереди есть текст, соответствующий .*?$tel, то он будет найден и негативная опережающая проверка (?!.*?$tel) вернет ложь.
Если вы в данное
[\d-]+ можно заключить в атомарные скобки, т.к. нет смысла возвращать цифры номера телефона, это не приведет к совпадению.
Другой способ заменить n-е от конца совпадение - повторять в цикле while поиск номера телефона, взяв его в $-[1] и $+[1]. Затем отсчитать от конца массивов $n, получить смещение начала и конца нужного телефонного номера и воспользоваться функцией substr, которой можно присваивать значение.
Для привязки к началу текста можно использовать \A или ^ без m.
Вот как можно проверить, что текст не начинается с точки:
if ($text =~ /^\./) { print "Текст не должен начинаться с точки!" }
Так можно вставить по одному пробелу в начало каждой строки текста:
$_=<<EOF; Первая строка. Вторая строка. Третья строка. EOF s/^/ /gm;
Без m пробел будет вставлен лишь в начало первой строки.
Пустые строки удаляются оператором замены с таким
$_=<<EOF; Первая строка. Вторая строка. Третья строка. EOF s/^\n//gm; print $_;
Получаем на выходе:
Первая строка. Вторая строка. Третья строка.
Может показаться, что оператор s/^$//gm тоже должен удалить пустые строки, но он не сделает замены. Удалиться должен реальный символ, а не совпадение с началом и концом строки, поэтому сперва он должен быть найден. А что-то вставить в пустую строку таким оператором s вполне возможно.
При преобразовании текста в HTML нужно заменять символы на amp; Это делается просто:
s//amp;/g
ведь амперсанд не является
Предположим, что нам надо найти последнее число в большом тексте. Здесь можно воспользоваться таким приемом: использовать конструкцию .* с s, которая мгновенно захватит весь текст и выйдет на его конец. Т.к. совпадение с числом не будет найдено, то * будет отдавать по одному символу, пока не отыщется совпадение. Как будто все просто. Попробуем такой пример:
$_=<<EOF; Первая строка 123. Вторая строка 456. Третья строка 789. EOF /.*(\d+)/s; print $1;
На печать выходит лишь цифра девять: 9. Удивлены? 9. Этого достаточно для совпадения \d+. Чтобы получить правильный результат, надо потребовать, чтобы перед \d+ не было цифры:
$_=<<EOF; Первая строка 123. Вторая строка 456. Третья строка 789. EOF /.*(?<!\d)(\d+)/s; print $1;
Печатает 789.
Чтобы привязать \z (для привязки к истинному концу текста) или \Z ( $ без m ):
$_=<<EOF; Первая строка 123. Вторая строка 456. Третья строка 789 EOF /(\d+)$/; print $1;
Печатает 789. Как видим, привязка к концу текста нашла последнее число без захвата всего текста и без заглядывания назад. Но если после последнего числа могут быть символы, то
$_=<<EOF; Первая строка 123. Вторая строка 456. Третья строка 789. EOF /(\d+)\D*$/; print $1;
Опять получаем 789, но поиск будет медленнее из-за увеличивающегося числа проверок.
У вебмастеров иногда возникает задача укоротить длинные URL, которые вводят в форму участники форумов. Из-за длинных ссылок расползается дизайн страниц. Предположим, кто-то отослал на форум такой текст:
Рекомендую посетить: http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/
На самом деле ссылка может оказаться еще длинее. В HTML-коде страницы форума эта ссылка должна появиться в виде URL:
Рекомендую посетить: <a href="http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/"
target="_blank">
http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/</a>
Внутри тега a ссылку, естественно, обрезать не надо, тем более, что в видимом тексте ее не будет, а вот то, что стоит перед тегом </a>, будет видно, и если это "слово" будет слишком длинным, то это может испортить дизайн. Надо после 50-го символа такой ссылки вставить три точки и получить такой текст:
Рекомендую посетить: <a href="http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.com/fasfasfasdf/"
target="_blank">
http://www.lsafhwoeufqfsjvdkghalhasdghasglhasgl.co…</a>
Другие длинные слова (не ссылки) надо также урезать. Мало ли что может ввести пользователь… Я нашел такое решение:
s/(\A|\s)(?!href=")(\S{50})\S+/$1$2.../g;
Предполагаем, что текст находится в переменной $_. href=, то проверяется, есть ли после этого "слово" длиной 50 символов, после которого стоит непробельный символ. Если да, то происходит замена всего, что найдено на $1 - пробельный символ перед длинным "словом", 50 символов с начала этого "слова" на $1, эти же 50 символов с начала длинного "слова", а последующие непробельные символы заменяются на три точки. Если бы в начале шаблона вместо (\A|\s) стояло просто (\s), то длинное "слово", стоящее в самом начале текста, не было бы укорочено.
Вот еще один вариант с использованием кода
s/((\S{50})\S+)/index($1,'href=') > -1 ? $1 : "$2..."/ge;
Ищем 50 непробельных символов, сразу за которыми есть хотя бы один непробельный символ. Эти 50 символов берем в переменную $2, а все длинное "слово" берем в переменную $1. В части замены проверяем, есть ли фрагмент текста href= в перемнной $1.
Если он есть, то заменяем найденное на $1, т.е. на себя, а если нет, то заменяем найденное на текст $2, за которым идут три точки.
Это был практический пример задачи, которую я решал по просьбе одного вебмастера.
Предположим, мы хотим узнать, есть ли в данном тексте непробельный символ между тегами. Предполагается, что теги правильно закрыты и нетеговых символов < и > не встречается. Вот первое решение:
$_=' <pppp>' x 13000;
$_.='<table>a';
if (/[^\s<>](?![^<>]*>)/g)
{ print "1\n".pos;
} else
{ print "0\n";
}
Вначале мы создаем длинную строку (больше 90000 символов) из тегов, в конец которой вставляем букву a вне тегов. В [^<>]*>. В случае нахождения такого символа программа печатает единицу и смещение этого символа от начала текста.
Вот другое решение, которое в отличие от первого настроено быстро пропускать все символы, которые нас не интересуют, т.е. пробельные и теги <> вместе с их содержимым:
/\A(?>(?:(?>\s*)<(?>[^>]*)>)*)\S/g
Оно кажется сложным из-за наличия трех
/\A(?:\s*<[^>]*>)*\S/g
Внутри скобок мы пропускаем последовательности пробельных символов \s* и конструкции <[^>]*>. Обратите внимание, что между этими подшаблонами излишне ставить знак альтернативы |. Вначале стоит привязка \A, чтобы поиск не начался внутри тега.
Какой метод поиска выбрать, чтобы скорость работы программы была максимальной, - искать ли нужный фрагмент или быстро пропускать все до него, - зависит от того, насколько плотно сидят нужные фрагменты в тексте.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.