Добавил:
Староста Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Рег.выражения в ПЕРЛ

..txt
Скачиваний:
2
Добавлен:
20.01.2019
Размер:
206.12 Кб
Скачать
#20110511 pre4_Str Регулярные выражения в Perl

There come a time in the affairs of a man when he
has to take the bull by the tail and face the situation.
-- W.C.Fields
(приветствие по fortune от #20110426)

11. Введение.


11.1. Общий обзор.

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


Операторы:

1. Поиск совпадений (match):

m/RE/imsxog - 6 независимых модификаторов, которые могут
использоваться в любой комбинации.
m/RE/gc - модификатор `с' используется только совместно с `g'

Модификаторы /i /m /s /x /o относятся к регулярному выражению,
а модификаторы /g и /gc меняют режим работы самого оператора
поиска.

2. Подстановка (substitute):

s/RE/REPLACEMENT/imsxoge

Совпавшая с RE подстрока замещается строкой REPLACEMENT.

Модификаторы /i /m /s /x /o относятся к регулярному выражению,
а модификаторы /g и /e меняют режим работы самого оператора
подстановки.

3. Расщепление строки

split /RE/, EXPR, LIMIT
split /RE/, EXPR
split /RE/
split

4. Создание объекта регулярных выражений.

qr/RE/

5. Транслитерация.

tr/SEARCHLIST/REPLACEMENTLIST/cds

В операторах m//, s///, split, qr// зона между ограничителями RE
(там где написано RE) является зоной интерполирующего контекста
двойных кавычек, т.е. перед употреблением RE производится
интерполяция бекслешь-последовательностей и подстановка значений
скаляров и массивов (или срезов), т.е. имен, начинающихся с
небекслешированного $ или @.

В качестве символа-ограничителя RE может использоваться любой не
буквенно-цифровой и непробельный ASCII-символ (код до 127), это
будет первый символ после наименования оператора m, s или qr,
не считая пробельных. Однако в качестве типового ограничителя
используется / , поэтому этот символ и использован в синтаксисе
операторов в man perlre и в данной методичке. При использовании
этого типового ограничителя наименование оператора m можно
опустить, этот оператор поиска по RE будет подразумеваться по
умолчанию. Т.е. эквивалентные выражения:

/RE/ эквивалентно m/RE/

После любого совпадения RE в тексте (т.е. явного или неявного
результативного поиска соответствий оператором m//, в том числе и в
операторе s///) заново устанавливаются значения специальных
(предопределенных) переменных perl :

$1, $2, ... ${10}, ${11}, .... - подстроки исследуемого текста, совпавшие с
подвыражениями в сохраняющих круглых скобках, число в наименовании
переменной - это номер слева направо открывающей круглой скобки в RE.

$` - подстрока исследуемого текста от его начала до точки совпадения
RE в целом.
$& - подстрока исследуемого текста, совпавшая с RE в целом.
$' - подстрока исследуемого текста справа от совпавшей части исследуемого
текста до конца исследуемого текста.

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

Эти переменные являются глобальными переменными, неявно локализованными
в охватывающей динамической области видимости. Эта фраза является
цитатой из книги Лари Уолла, Тома Кристиансена и Джона Орванта
"Прогаммирование на Perl" (издание 3, стр.187 параграф "Операторы
поиска по шаблону), я думаю, пока эта формулировка Вам непонятна.
Я бы ее вообще не приводил, если бы один из студентов не обратился
с вопросом почему не работает составленная им простая программа.
Цель этой фразы в данном месте методички просто показать, что не
все так просто и загадоные явления могут случиться и в простых
программах. Совет, как же быть. Если вы хотите использовать значения
этих переменных в дальнейшем, сразу же присвойте значения тех,
которые вам потом понадобятся в обычные необъявленные переменные,
они обладают той глобальностью, которая имеется в С и в прочих
языках программирования. Например:

$left=$`; $match=$&; $right=$'; $parth1=$1, $parth2=$2, ..

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

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


#!/usr/bin/perl -w

######1
$var = "aaaa 22 bbb 55555 ccc 4444 ddd 333 eee";

print "var=|$var|\n";

$r = ( $var =~ /(\d+)\s+(\D+)\s+(\d+)\s+(\D+)\s+(\d+)/ ); #1

print $r ? "RE совпало\n" : " RE НЕ совпало\n";

print "|\$`|\$&|\$'| :\n";
print "|$`|$&|$'|\n\n";

print " 1=|$1|" if defined $1;
print " 2=|$2|" if defined $2;
print " 3=|$3|" if defined $3;
print " 4=|$4|" if defined $4;
print " 5=|$5|" if defined $5;

print "\n\n";

######2
$var="aaaa 22 bbb 55555 ccc4444 ddd 333 eee";

print "var=|$var|\n";

$r = ( $var =~ /(\d+)\s+(\D+)\s+(\d+)\s+(\D+)\s+(\d+)/ ); #2

print $r ? "RE совпало\n" : " RE НЕ совпало\n";

print "|\$`|\$&|\$'| :\n";
print "|$`|$&|$'|\n\n";

print " 1=|$1|" if defined $1;
print " 2=|$2|" if defined $2;
print " 3=|$3|" if defined $3;
print " 4=|$4|" if defined $4;
print " 5=|$5|" if defined $5;

print "\n\n";

######3
$var="aaaa22bbb55555ccc4444ddd 333 eee";

print "var=|$var|\n";

$r = ( $var =~ /(\D+)\s+(\d+)\s+(\D+)/ ); #3

print $r ? "RE совпало\n" : " RE НЕ совпало\n";

print "|\$`|\$&|\$'| :\n";
print "|$`|$&|$'|\n\n";

print " 1=|$1|" if defined $1;
print " 2=|$2|" if defined $2;
print " 3=|$3|" if defined $3;
print " 4=|$4|" if defined $4;
print " 5=|$5|" if defined $5;

print "\n\n";


Рассмотрим данные, выведенные этой программой.

var=|aaaa 22 bbb 55555 ccc 4444 ddd 333 eee|
RE совпало
|$`|$&|$'| :
|aaaa |22 bbb 55555 ccc 4444| ddd 333 eee|

1=|22| 2=|bbb| 3=|55555| 4=|ccc| 5=|4444|

var=|aaaa 22 bbb 55555 ccc4444 ddd 333 eee|
RE НЕ совпало
|$`|$&|$'| :
|aaaa |22 bbb 55555 ccc 4444| ddd 333 eee|

1=|22| 2=|bbb| 3=|55555| 4=|ccc| 5=|4444|

var=|aaaa22bbb55555ccc4444ddd 333 eee|
RE совпало
|$`|$&|$'| :
|aaaa22bbb55555ccc4444|ddd 333 eee||

1=|ddd| 2=|333| 3=|eee|

Сначала переменной $var присваиавается ( и распечатыватся) такое
значение, в котором RE (#1) совпадает и тогда три переменые $`,
$&, $' и 5 переменных соотвествующих 5-ти круглым скобкам
получают свои значения, которые распечатываются.

Потом в испытываемой строке ликвидируется пробел между "ccc" и
"4444", поэтому обязательный пробел \s+ между не-цифрой \D и
цифрой \d не может совпасть, следовательно RE (#2) НЕ-совпадает.
При этом все специальные переменные $`, $&, $', $1, $2, $3, ...
сохраняют свои прежние значения, полученные при предыдущем совпадении
RE.

Потом используется другая испытываемая строка и сокращенное RE,
которое совпадает в самом конце испытываемой строки (в начале не
хватает пробелов для совпадения с обязательыми пробелами \s+).
Переменные получают значения в соотвествии с новым совпадением, а
неиспользованные $4 и $5 приобретают значения undef.

Какой вывод следует сделать из этого примера?

Прежде чем использовать специальные переменные $`,$&,$',$1,$2,...
после применения к строке регулярного выражения обязательно нужно
проверить истинность возврата оператора =~ , и использовать их
только в случае если очередное применение RE вернуло истину.
Если возвращена ЛОЖЬ, то эти переменные содержат устарелые данные
последнего применения RE, когда выражение совпало. Эти специальные
переменные являются Read-Only переменными, так что сбросить их в
какие-то начальные значения можно только задав какой-нибудь
искусственный оператор успешного совпадения RE, который сбросит
все специальные переменные в пустые значения или undef-значения.
Например можно использовать любой из следующих операторов, где
RE совпадает в пустой строке:

"" =~ /^/; # Якорь начала строки совпадает в любой строке
"" =~ /$/; # Якорь конца строки совпадает в любой строке
"" =~ /^$/; # RE пустой строки совпадает в пустой строке
"" =~ /.?/; # Квантификатор ? допускает 0 повторений, поэтому
# и это RE совпадает в пустой строке

Любой из этих операторов присвоит переменным $`,$&,$' пустые строки,
а переменные $1,$2,$3,.... станут неопределенными.

В последнем примере результат применений RE, это строки помеченные
#1 #2 #3, - истина, если RE совпало и ложь, если не совпало -
присваивается скаляру $r.

Вставьте в программу примера сразу после 2-го и 3-го применения
RE проверку скаляра $r , и если он ложь, то задайте выполнение
любого из операторов, сбрасывающих специальные переменные:

"" =~ /^/ if !$r;

Этот оператор сбросит пусто и undef все специальные переменные после
строки, помеченной #2, где они не соотвествуют результатам поиска,
а остались от предыдущего успешного поиска. После строки, помеченной
#3 $r будет истинным, значит !$r - ложным и новые правильные
значения специальных переменных не сбросятся, а распечатаются.




11.2. Специальные переменные и сохраняющие круглые скобки PerlRe.

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

Упражнение 11_2_1:

Определить встречается ли в исследуемой строке подряд два одинаковых
символа.

use locale;
$_ = "Это исследуемая строка";
print "$_\n";
if (/(.)\1/) {print " - встретилось подряд два символа '$1'\n" }
else { print " - подряд два одинаковых символа не втречаются\n"}

Пояснения:
1. Символ / является стандартным ограничителем RE, поэтому если в
Perl-программе встречается в подходящем контексте элемент
/EXPR/, то EXPR трактуется как регулярное выражение, т.е. /RE/.

2. Если перед открывающим ограничителем /RE/ не указан оператор
(m// - поиск, s/// - поиск с заменой или qr// - литерал RE),
то по умолчанию предполагается оператор m// - поиск.

3. Если для операторов поиска m// (включая и неявный /RE/)
или поиска с заменой s/// не задано в какой строке
производить поиск или поиск с заменой (т.е. нет оператора
привязки к исследуемой строке =~ или !~), то по умолчанию
привязка производится оператором =~ к строке, содержащейся
в стандартной переменной $_ .

Правила 1. и 2. действуют только в случае использования
стандартного ограничителя RE - символа / , правило 3 - в
случае любого произвольно выбранного ограничителя RE.

На основании правил 1 - 3 условие if в программе 11_2_1.pl
в более подробной записи означает следующее:

if ( $_ =~ m/(.)\1/ ) { ...

Оператор поиска m// в скалярном контексте (а логический контекст
это один из видов скалярного контекста) обрабатывает строку
слева направо до первого совпадения и возвращает True, а если
до конца строки совпадений не обнаружено, то False. Оператор
привязки =~ возвращает то, что вернул оператор поиска m//,
а оператор привязки !~ - отрицание того, что вернул оператор
m//.

Теперь пояснения к самому регулярному выражению /(.)\1/ .

1. Точка является одним из 12 метасимволов RE и совпадает с
любым символом в исследуемой строке, кроме символа новой
строки.

2. Подстрока исследуемой строки, с которой совпало подвыражение
в круглых скобках (SubRE) , запоминается во внутренней
переменной \1 , которая дает возможность использовать
запомненную подстроку правее в том же RE, но вне данного
RE эта переменная недоступна. Это же значение запоминается
и в специальной переменной $1 и будет доступно вне RE
вплоть до следующего применения любого RE в операторах m//
или s///. Если в RE используется несколько пар скобок,
то соответственно задействуются переменные \1, \2, \3, ...
и $1, $2, $3, ... , где число - это порядковый номер
открывающей скобки слева направо в RE, включая и вложенные
скобки.


Теперь программа 11_2_1.pl должна быть полностью понятна, запустите ее
и убедитесь в том, что она обнаруживает две кириллические буквы "с",
стоящие подряд.

Закомментируйте команду, в которой исследуемый текст присваивается
переменной $_ и замените аналогичной, но с другим текстом, чтобы
в нем не было подряд двух одинаковых символов (учтите, что два
пробела подряд, это тоже два одинаковых символа подряд). Запустите
программу и проверьте, что и этот противоположный результат программа
также обнаруживает.

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

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


Упражнение 11_2_2:

$_ = "555 123456 abc def 998877 fffff0000 ";
print "$_\n";
if ( /\D((\d+)(\D+)(\d+))\D+/ )
{print " - совпало: |$&|\n";
print " - слева: |$`|\n";
print " - справа: |$'|\n";
print " - \$1 : |$1|\n" if $1;
print " - \$2 : |$2|\n" if $2;
print " - \$3 : |$3|\n" if $3;
print " - \$4 : |$4|\n" if $4;
}
else { print " - не совпало\n"}

Пояснения:

В программе используются стандартные классы PerlRE:

\d - совпадает с любым одним цифровым символом 0-9.
\D - совпадает с любым одним символом, кроме десяти цифровых 0-9.

В программе используются метасимвол-квантификатор PerlRE:

+ - повторяет предшествующий ему атомарный элемент RE один или
более раз;
атомарным называется элемент RE, имеющий ненулевую
ширину;
в данной программе все квантифицируемые элементы
имеют ширину в 1 символ;
квантификатор + является "жадным", старается поглотить
в исследуемой строке как можно больше символов.

Tаким образом \d+ совпадает с 1 или более цифр и старается
поглотить их как можно больше; \D+ совпадает с 1 или более
нецифровыми символами и также старается поглотить их как можно
больше.

Запустите программу 11_2_2.pl и объясните результаты.

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

$_ = " 555 123456 abc def 998877 fffff0000 ";

Запустите программу и объясните изменение всех результатов.

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

$_ = "555123456 abc def 998877 fffff0000 ";

Запустите программу и объясните изменение всех результатов.

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

$_ = "555123456 abc def 998877 fffff0000";

Запустите программу и объясните изменение всех результатов.

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


Для пояснения понятия "атомарный элемент RE" приведем еще примеры:

Упражнение 11_2_2b:

use locale;
$var = "тупым ножом тонко не отрежешь, поточить требуется";
$var =~ /\b(то.)+/i;


В этом RE метазнак \b - это граница слова, т.е. грань нулевой
ширины между символом слова и не символом слова (т.е. конец
слова), или между не символом слова и символом слова (т.е. начало
слова), а также в начале строки, если первым символом строки
является символ слова или в конце строки, если последним символом
является символ слова. Это не атомарный элемент RE, поскольку
его ширина 0. К неатомарным элементам не может применяться
никакой квантификатор (повторитель), будет run-time warning.
Заметим, что буквы кириллицы будут являться символами
слова только в зоне действия прагмы (use locale;).

Напротив, элемент в круглых скобках является атомарным, его ширина
три символа: кириллические буквы 'т','о' и любой символ, кроме
newline. К этому элементу применен квантификатор + (повторять
один или более раз).

В приведенной строке RE совпадет с позиции 12 с подстрокой "тон"
($&), слева останется подстрока "тупым ножом " ($`), справа -
"ко не отрежешь, поточить требуется" ($'). Значением переменной
$1 будет "тон".

Если удалить прагму "use locale", совпадения RE в этой строке
вообще не будет, поскольку кириллические символы уже не будут
являться символами слова и ни одной границы слова в строке не
будет, следовательно, элемент \b не совпадет ни с одной позиции
строки от 0 и до последней, 49-й.

Если заменить в RE \b на \B (это не-граница слова, т.е. грань нулевой
ширины между двумя символами слова или двумя не-символами слова или
в начале строки если первый ее символ не символ слова, или в конце
строки, если последний ее символ не символ слова), то без прагмы
use locale совпадение произойдет с 12 позиции, как было рассмотрено.

Если вернуть прагму use locale, но оставить \B:

use locale;
$var = "тупым ножом тонко не отрежешь, поточить требуется";
$var =~ /\B(то.)+/i;

Совпадение произойдет с позиции 33:
$`= "тупым ножом тонко не отрежешь, по"
$& = $1 = "точ"
$' = "ить требуется";



Теперь вернем прагму use locale и элемент \b, но заменим
исследуемую строку на следующую:

use locale;
$var = "То-тоже, тупым ножом тонко не отрежешь, поточить требуется";
$var =~ /\b(то.)+/i;

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

Благодаря use locale кириллица - это буквы, т.е. символы слова
и каждой букве существует парная в другом регистре. Благодаря
модификатору /i (игнорирование регистра) с элементом /т/i в
RE совпадает и "т", и "Т" в исследуемой строке.

RE совпадет с позиции 0 с подстрокой "То-тож" ($&), слева
останется пустая строка ($`), справа -
"е тупым ножом тонко не отрежешь, поточить требуется" ($').
Значение переменной $1 будет заменяться накрываемым мерной
реечкой содержанием исследуемой строки при каждом ее перекидыванииш,
и окончательное содержание этой переменной, соответствующей
круглым скобкам, будет "тож" - подстрока, захваченная при
последнем повторении квантифицированного элемента в круглых
скобках.


Упражнение 11_2_3:

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

В Perl можно прочитать весь файл за одну операцию в скалярную
переменную, если присвоить значение undef специальной переменной
Perl $/, в которой задается разделитель записей текстовых файлов.
После прочтения файла в скаляр нужно восстановить прежнее значение
этой переменной "\n", которую она имела по умолчанию.

Второе характерное только для PerlRE свойство, существенно
упрощающее нашу задачу - последовательный поиск. Если оператор
поиска m/RE/g с модификатором /g применятся в скалярном контексте
к какой-то строке ( $sfile ), то для этой строки заводится
указатель, перемещающийся за последний символ найденного совпадения
при каждом применении оператора scalar( $sfile =~ m/RE/g ). При
каждом следующем выполнении поиска, он будет продолжаться с позиции
этого указателя до следующего ближайшего совпадения. Если совпадений
больше нет, оператор поиска вернет False, а указатель будет сброшен в
0 позицию строки. Положение указателя возвращается функцией pos($sfile).

Модификатор /i задает игнорирование регистра букв при поиске, в зоне
где установлена use locale символы кириллицы koi8-r тоже является буквами
и игнорирование регистра действует и на русские буквы.

В результате на Perl решение этой задачи достаточно изящно:

use locale;

$/=undef; # читаем весь файл, заданный аргументом командной
$sfile = <>; # строки запуска программы, в одну мультистроку $sfile
$/="\n" ;

print "Длина мульти-строки = ", length( $sfile ), "\n";
# функция length рассматривает свой аргумент,
# как строку и возвращает ее длину (число символов)

# находим все повторяющиеся подряд идущие русские слова

while ( $sfile =~ m/\b([ю-Ъ]+)[\s.;,:!?]+\1\b/ig )
{ print "pos=",pos($sfile),": $&\n"}


Пояснения регулярного выражения /\b([ю-Ъ]+)[\s.;,:!?]+\1\b/ig

Круглые скобки захватывают русское слово - одну или более кириллических
букв. Класс [ю-Ъ] включает 64 символа (32 кириллические буквы
нижнего регистра и 32 соответствующих буквы верхнего регистра, т.е.
все, кроме ёЁ, если эти буквы тоже используются, нужно их дописать
дополнительно в квадратные скобки класса). Такая странная запись
класса используется для UNIX, где в кодировке koi8-r минимальное
значение из 64 кодов имеет код буквы "ю" , а максимальное - код "Ъ".
Метасимвол-квантификатор + задает повторение предшествующего атомарного
элемента RE (т.е. класса кириллических букв [ю-Ъ]) один или более раз.

Далее следует класс символов, которые, как мы считаем, могут
разделять эти два повторяющихся слова [\s.;,:!?]. В этот класс
включены все символы стандартного Perl-класса пробельных символов
\s, в который входят:
- пробел,
- символ табуляции "\t",
- символ новой строки "\n"
- символ возварат каретки "\r"
- символ перехода на новую страницу "\f"
К этим 4 символам мы добавили еще 6, которые, как мы считаем,
недостаточно разделяют соседние одинаковые слова, чтобы вполне быть
уверенными, что они не продублированы ошибочно. Это символы точки,
точки с запятой, запятой, двоеточия, восклицательного и вопросительного
знаков. Класс этот квантифицирован метасимволом +, так что между
дублирующими словами может стоять один и более символов этого
класса. Кстати, в данном абзаце есть две пары дублирующих слов,
которые наше RE поймает. Найдите их с помощью рассмотренного RE.

За разделяющим символом или символами следует то самое русское
слово, которое было захвачено круглыми скобками \1 . Перед
захватывающими скобками и после захваченной подстроки \1 мы
поставили мета-последовательность границы слова \b - это грань
нулевой ширины между символом слова и символом не-слова или между
началом всей исследуемой строки и символом слова или между символом
слова и концом всей исследуемой строки. Для \b символ слова это
не то что мы задали для русского слова [ю-Ъ], а стандартный класс
символов слова [a-zA-Z_0-9], но в зоне программы, где установлена
use locale этот класс расширен кириллическими буквами koi8-r
[A-Za-z_0-9ю-ЪёЁ]. Таким образом левый \b в нашем RE, поскольку
после него требуются символы слова, соответствует либо началу всей
строки, если она начинается с символа слова, либо началу слова в
середине строки. Правый \b в нашем RE, поскольку слева от
него стоят символы слова, соответствует либо концу слова в середине
строки, либо концу всей строки, если он следует сразу же за
последним символом слова, содержащегося в переменной \1 .

Если не поставить левого \b, то RE будет определять что то лишнее
как будто бы это дублирующие слова, приняв фрагмент в конце первого
слова за отдельное слово, например в первой строке данного абзаца
при наложении RE с позиции между буквами "ч" и "т" будет получено
совпадение и выдано как дублирующие слова "то то". Если же левый \b
поставлен, он не даст RE совпасть, поскольку между "ч" и "т" нет
границы слова.

Если не поставить правого \b, то RE будет определять лишнее как будто
бы это дублирующие слова, приняв начало второго слова за целое слово.
Например, в тексте "клон клонировался плохо" при наложении RE между
символом кавычек и буквой "к" будет получено совпадение и выдано как
дублирующие слова "клон клон". Если же правый \b поставлен, он не даст
RE совпасть, поскольку между символами "н" и "и" нет границы слова.

Если в нашем RE не будет ни левого, ни правого \b, то объединятся
все ошибки отсутствия левого и правого, обсуждавшиеся ранее, и
еще добавятся новые ошибки, две из которых вы сами найдете в первых
двух строках данного абзаца, считайте это важным упражнением. Нужно
дать ответ с каких позиций накладывается RE, когда происходят эти
совпадения и что выдается в каждом из этих двух случаев как дублирующие
слова. Запишите ваш ответ ручкой на бумажке, чтобы через несколько
минут, когда запустите программу для обработки данного файла испытать
более глубокое удовлетворение, если увидите, что все абсолютно, до
всех запятых и пробелов совпадает.

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

- смещение экрана от начала файла в строках:
номер_верхней_строки - номер_нижней_строки / общее_число_строк_в_файле

- смещение последнего символа нижней строки от начала файла в байтах:
номер_байта_от_начала_файла / общее число байт в файле

- смещение последнего символа экрана в процентах от длины файла.

Определите эти смещения для текущего места в файле данной методички.

Приступим к экспериментам с программой 11_2_3.pl:

1. Создайте программу 11_2_3.pl и запустите ее, задав в качестве
аргумента файл данной методички. Если он у вас в текущем директории и
называется pre3, то строка запуска программы:

./11_2_3.pl pre3

Будет получено 6-7 совпадений с выдачей номера позиции сразу же
за каждым совпадением RE и распечаткой совпавшего фрагмента файла.
Пользуясь вышеописанным способом и утилитой less подлистайте
файл к каждому из совпадений и найдите их в контексте файла.
Разберитесь для каждого элемента в RE с чем в файле он совпадает,
при необходимости перечитывая снова и снова текст методички от
начала упражнения 11_2_3. И сделать это нужно для каждого из этих
6-8 совпадений. Это важно, без этого дальнейшее изучение RE
будет пустой тратой времени. Если после нескольких попыток,
с внимательным прочтением всего предшествующего текста методички
какое-то совпадение оказывается вам не по силам, обращайтесь к
преподавателю. Но имейте в виду, что пока вы самостоятельно не
начнете разбираться в совпадениях, толку от вашего обучения нет.

2. В пункте 1 программа правильно искала русские слова в данном
понимании что такое русское слово и что такое дублирование слов.
(Существует множество различных пониманий этих понятий, а строго
каждое из этого множества пониманий можно выразить только с
помощью RE - и для этого RE используются тоже). Теперь будем
по-разному портить наше RE, позволяя проходить некоторому числу
ложных совпадений, т.е. пропуская шум с точки зрения нашего
понимания. Продублируйте в программе строку с RE, верхнюю
закомментируйте а в нижней уберите левый \b. Запустите программу
и проработайте аналогично п.1 все дополнительно появившиеся шумовые
совпадения, находя их в текте и разбираясь, почему они прошли как
дублирующие слова.

3. Продублируйте опять строку с RE и на этот раз оставьте без
символа комментария строку, где есть левый \b, но нет правого
\b. Проработайте все новые появившиеся шумовые совпадения
аналогично п.1.

4. Продублируйте еще раз строку с RE и оставьте без комментария
ту, где в RE нет ни левого, ни правого \b и проанализируйте,
находя в тексте все дополнительные совпадения к тем, что были в
пунктах 1.,2.,3.

5. Верните первый вариант RE в программу (имеются оба якоря границы
слова и левый и правый) и обработайте этой программой самый большой
файл из всех методичек - pl2_Str:

./11_2_3.pl /cell/public/8sem/2008/lab/pl2_Str

Сколько найдено совпадений с RE?

Продублируйте строку с RE, верхнюю закомментируйте, а в нижней
уберите модификатор /i (/g оставьте, чтобы это попрежнему был
последовательный поиск). Запустите программу с тем же аргументом,
для обработки методички pl2_Str.
На сколько уменьшилось число совпадений?
Каких совпадений не стало и почему?



12. Метасимволы и метапоследовательности символов (метазнаки).

Perl по классификации POSIX использует расширенные RE, соответсвенно
имеется 12 метасимволов, имеющий особое значение и нуждающихся в
бэкслешировании, чтобы литерально представлять себя в RE:

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

Коротко (но не полно, подробности - позднее) поясним эти 12 метасимволов.

Три последних символа выполняют структурирующую роль в RE.
Вертикальная черта объединяет левое и правое выражение в единое RE
из пары альтернатив ( OR ). С каждой позиции исследуемой строки
проверяется совпадение каждой альтернативы слева направо.

Круглые скобки выделяют в RE подвыражение subRE для достижения
следующих целей:

- применения квантификатора к целому атомарному subRE, а не одному
предшествующему атомарному элементу RE. Атомарный - это имеющий
ненулевую ширину.

- ограничение альтернативных subRE, которые, если не взять в
скобки альтернативную OR-группу, простираются до границ целого RE.

- для сохранения подстроки исследуемой строки, совпавшей с взятой
в скобки subRE, в соотвествующей номеру скобки переменной $1, $2, ...,

Например регулярное выражение /по(лови|л.|л|ст)на/ совпадет в строках:

- дополна ( $1 == "л" )
- поляна ( $1 == "ля" )
- половина ( $1 == "лови" )
- постная ( $1 == "ст" )

Результат применения RE может зависеть от последовательности
альтернатив в OR-группе. Если в этом примере переставить альлернативу
/л/ в OR-группе левее /л./ или левее /лови/, то левая альтернатива
/л/ совпадет со случаями срабатывания более длинных альтернатив
и выдаст True и завершит поиск раньше, чем дело дойдет до проверки
альтернатив правее. Совпадение для первых 3 строк будет получено, но
в переменной $1 всегда будет только "л".

Вопрос на самопроверку:
Чем завершится поиск, если альтернатива /л/ останется на своем
месте, а поменяются местами первые две альтернативы в OR-группе.

Метасимволы ^ , $ - неатомарные, они совпадают с гранями нулевой
ширины, соотвественно в начале и в конце исследуемой строки. Если
строка оканчивается символом newline, $ совпадает с гранью нулевой
ширины и до, и после символа newline. В вышеприведенном примере
RE /^по(лови|л.|л|ст)на/ не совпадет в строке "дополна", а
RE /по(лови|л.|л|ст)на$/ не совпадет в строке "постная".

Метасимволы *, ? + и { - это квантификаторы (повторители).
Они задают число повторений предшествующего атомарного элемента RE:

* - 0 или более раз
? - 0 или 1 раз
+ - 1 или более раз
{ - открывает зону задания интервального квантификатора,
например:
{3,7} - от 3 до 7 раз,
{5,} - 5 или более раз,
{4} - ровно 4444 раза,

Например, если так модифицировать регулярное выражение
рассматриваемого примера /по(лови|(л.)*|л|ст)на/i ,
то оно совпадет в ранее указанных строкак строках, а также в:

- Полина ( $1 == "ли" , $2 == "ли" )
- полулунатик ( $1 == "леле" , $2 == "ле" )
- понапрасну ( $1 == "" , $2 == undef )
- полулолана ( $1 == "лулола" , $2 == "ла" )

Регулярное выражение /по(лови|(л.)+|л|ст)на/i совпадет только
в 3 из последних 4-х строк (не совпадет в строке "понапрасну",
потому что когда квантификатор *, допускающий 0 повторений заменили
на +, требующий хотя бы одно повторение квантифицированного
элемента RE, то исчезла возможность OR-группе совпасть с подстрокой
нулевой длины).

Регулярное выражение /по(лови|(л.)?|л|ст)на/i совпадет только
в 2 из последних 4-х строк (не совпадет в строках "полулунатик" и
"полулолана", потому что квантификатор ? допускает 0 или 1 повторение,
но не 2 для совпадения в "полулунатик" и не 3 для совпадения в
"полулолана").

Метасимвол [ открывает класс, который совпадает в строке с одним
символом, любым из заданных в классе.

Метасимвол точка . совпадает (без модификатора /s) с одним любым
символом, кроме newline. Это тоже класс символов, самый широкий.
Пример использования метасимвола точка был и в этом пункте, и
ранее, в 11_2_1. Остается только бекслешь \ , рассмотрим далее
использование этого метасимвола

Метасимвол - это символ, имеющий некоторое особое значение. Чтобы
представить метасимвол в RE литерально, как самого себя, нужно
экранировать его, поставив перед ним бэкслешь, которая отменяет
особое значение следующего за ней метасимвола и он будет литерально
представлять самого себя. Например, чтобы литерально представить
символ бэкслешь, нужно перед ней поставить еще один символ бекслешь.
Первый из них будет экранирующим, второй будет представлять
литерально символ \.

Требуется больше, чем 12 различных особых значений, и для тех
случаев, для которых не предусмотрено метасимволов, используются
метапоследовательности символов (другое название - метазнаки).

Большинство 2-символьных метапоследовательностей начинаются с
бэкслеш, например:

\t - символ табуляции , метапоследовательность двойных кавычек;
\d - любая цифра [0-9], метазнак PerlRE, стандартный класс символов;
\s - любой пробельный символ [\ \t\n\f\r]

Если бекслешь-последовательность не распознана как метапоследовательность
(символ за бекслешью еще не задействован ни в каком метазнаке),
то символ бэкслешь перед обычным символом (не метасимволом) ровным
счетом ничего не означает и просто игнорируется, как будто его
нет. Чтобы представить литерально сам символ бекслешь в RE, он
удваиавается. Например следующие два написания RE эквивалентны:

/^каб\.\s*\d+\b/
/^\к\а\б\.\s*\d+\b/

Это RE будет совпадать в строке с подстроками в самом начале строк:

"каб. 78 хирург"
"каб.4-терапевт"
"каб.
386 склад" # внутри строки имеется символ newline"

Метасимвол ^ - совпадает только в самом начале строки, поэтому
"якорит" с этой позиции строки наложение данного RE. Далее требуется
литеральное совпадение 4-х символов (3 кириллических букв "к" "а" "б"
и точки). В метазнаках могут быть задействованы только ASCII-символы
(коды от 32 до 127), поэтому кириллица точно не задействована в настоящий
момент и не будет задействована, значит бекслеши перед этими символами
как бы не существуют и ровным счетом ничего не меняют. Бекслешь перед
точкой необходим, он превращает метасимвол точку, совпадающую в строке
с любым символом, кроме newline, в литеральную точку, совпадающую в
строке с точкой. Далее идет метазнак

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

*? - минимальный квантификатор (2-символьная метапоследовательность);
(?: - несохраняющие скобки (3-символьная метапоследовательность);
(?<= - ретроспективная проверка (4-символьная метапоследовательность);

Много 2-символьных метазнаков начинаются с бэкслеши:

\w - класс символов слова: 52 символа латиницы, символ подчеркивания
и 10 цифровых символов, итого 63 символа. В зоне действия
прагмы (use locale;) добавляются еще 66 символов кириллицы,
итого этот класс увеличивается до 129 символов. Этот метазнак
RE совпадает в исследуемой строке с одним любым символом
этого множества.

\W - инверсный класс символов, не явяющихся символами слова:
256 - 63 = 193 символа или (в зоне use locale;) 256 - 129 =
127 символов.

\b - граница слова, это может быть либо начало слова:
грань нулевой ширины перед первым символом слова в самом
начале исследуемой строки слова), т.е. между элементами
/^\w/ (, или в середине исследуемой строки, т.е. между
/\W\w/,
либо конец слова:
грань нулевой ширины перед первым символом слова в самом
конце исследуемой строки, т.е. между элементами /\w\z/,
или в середине исследуемой строки /\w\W/.
Точнее, если исследуемая строка начинается с символа слова,
то позиция 0 совпадает и с ^ , и с \A , и с \b. Если
исследуемая строка оканчивается символом слова, то позиция
с максимальным номером совпадает и с $ , и с \z , и с \Z, и
с \b .

\B - не граница слова, это грань может быть либо грань нулевой
ширины между двумя символами слова (позиция внутри слова),
либо грань нулевой ширины между двумя не-символами слова
(позиция вне слова), либо на границах строки, если граничными
символами строки являюется не символы слова. Например, в
строке " хватит болтать." /\B/ совпадает и в начале строки
/^/, поскольку пробел - не символ слова, и в конце строки
/$/, поскольку точка - не символ слова.



12.1 Сводка всех метазнаков Perl (из документации)

Для понимания работы и лучшего владения аппаратом RE необходимо
понимать, что на стадии выполнения (run-time) RE обрабатывется
за 2 прохода. На первом проходе RE обрабатывается как оператор
двойных кавычек:

- интерполируются значения скаляров, массивов и срезов (т.е. имен,
начинающихся с $ и с @),

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

Подставляются значения метазнаков двойных кавычек, приведенные
в предшествующей методичке pl2_Str:

\t tab (HT, TAB)
\n newline (NL)
\r return (CR)
\f form feed (FF)
\a alarm (bell) (BEL)
\e escape (ESC)
\033 octal char (ESC)
\x1b hex char (ESC)
\c[ control char (ESC)

# следующие 2 в курсе не рассматриваем, это для unicode
# \x{263a} wide hex char (SMILEY)
# \N{name} named Unicode character

Единственным метазнаком двойных кавычек, который в RE имеет
иное значение является

\b backspace (BS)
# остается атомарным элементом только в зоне класса символов

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

Работают в RE также все функции интерполяции двойных кавычек:

\l lowercase next char
\u uppercase next char
\L lowercase till \E
\U uppercase till \E
\E end case modification
\Q quote non-word characters till \E

Единственное, что отлично от истинного оператора двойных кавычек,
это невозможность включить в \Q-последовательность литералы
$ и @, они не будут "закавычены" с помощью \Q, их нужно явно
бекслешить, чтобы представить литерально.

В perlRE используются дополнительно следующие метазнаки для обозначения
атомарных элементов ширины 1 символ - это следующие стандартные классы:

\s A whitespace character [ \t\n\r\f]
\S A non-whitespace character [^ \t\n\r\f]
\d A digit [0-9]
\D A nondigit [^0-9]
\w A word character [a-zA-Z0-9_]
\W A non-word character [^a-zA-Z0-9_]

В зоне, где включена прагма use locale при выполненной локализации
операционной системы последние два класса, кроме латиницы, включают
также и буквы национального алфавита, для России:

use locale;
\w A word character [a-zA-Z0-9_ю-ЪёЁ]
\W A non-word character [^a-zA-Z0-9_ю-ЪёЁ]

Стандартные классы perl могут включаться в состав обычного класса
(в квадратных скобках), например:

[a-fA-F\d] - класс 16-ричных цифр

# эти шесть метазнаков для работы в unicode в курсе не
# рассматриваются
# \C Match a byte (with Unicode, '.' matches a character)
# \pP Match P-named (Unicode) property
# \p{...} Match Unicode property with long name
# \PP Match non-P
# \P{...} Match lack of Unicode property with long name
# \X Match extended unicode sequence


Все якоря Perl (элементы нулевой ширины):

^ Match string start (or line, if /m is used)
$ Match string end (or line, if /m is used) or before newline
\b Match word boundary (between \w and \W)
\B Match except at word boundary (between \w and \w or \W and \W)
\A Match string start (regardless of /m)
\Z Match string end (before optional newline)
\z Match absolute string end
\G Match where previous m//g left off


Следующие функции Perl имеют отношение к текстовой обработке и
используют RE или понятия RE:

lc Lowercase a string
lcfirst Lowercase first char of a string
uc Uppercase a string
ucfirst Titlecase first char of a string

pos Return or set current match position
quotemeta Quote metacharacters
reset Reset ?pattern? status
study Analyze string for optimizing matching

split Use regex to split a string into parts

The first four of these are like the escape sequences "\L", "\l", "\U",
and "\u". For Titlecase, see "Titlecase".

TERMINOLOGY

Titlecase

Unicode concept which most often is equal to uppercase, but for certain
characters like the German "sharp s" there is a difference.



13. Атомарные и неатомарные элементы RE.

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

В RE имеются и элементы не атомарные, которые совпадают не с
символами исследуемого текста, а с гранями нулевой ширины (позициями)
между символами или на границах исследуемого текста. Например
циркумфлекс ^ является метасимволом - якорем начала строки, т.е.
обозначает границу нулевой ширины перед первым символом исследуемой
строки. Метасимвол $ - якорь конца строки, совпадает с границей
нулевой ширины после последнего символа исследуемой строки.

Упражнение 13_1: Пример поиска в тексте слова "сумма" :

print qq{Пример поиска в тексте слова "сумма" :\n};

print "Введите строку\n>>"; chomp( $lot = <STDIN> ); # вводим
строку текста с терминала if ( $lot =~ m/сумма/ ) { # ищем в
строке слово сумма
print qq{Есть слово "сумма" \n};
} else {
print qq{Cлово "сумма" не найдено \n};
}

Запустите несколько раз программу 13_1.pl и вводите строки в которых
встречается слово сумма и не встречается это слово. Убедитесь, что
программа работает правильно.

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

if ( $lot =~ m/\с\у\м\м\а/ ) { .... }


В логическом условии if используется оператор m// - поиск совпадения.
Оператор =~ связывает оператор поиска со скалярной переменной,
содержащей исследуемый текст. Оператор =~ можно прочитать "обработать
с помощью" и возвращает этот оператор то, что возвращает оператор,
с помощью которого производится обработка, в данном случае оператор
поиска совпадения m//. Поскольку все это происходит в скалярном
контексте ( логический контекст if - это частный случай скалярного
контекста), то оператор поиска (а значит и оператор связывания =~
возвращает 1 (истина), если найдено совпадение и "" (ложь) в
противном случае. В списковом контексте возврат оператора поиска
будет другим и это будет рассмотрено позже.

Регулярное выражение находится между ограничителями //. Это
регулярное выражение состоит из 5 атомарных элементов - символов,
каждый из которых литерально представляет самого себя. Каждый из
этих атомарных элементов должен совпасть с идущими подряд символами
исследуемого текста. Проверки делаются последовательным наложением
RE сначала от границы нулевой ширины перед первым символом исследуемого
текста. При отсуствии совпадения всех элементов проверка делается
от границы нулевой ширины между 1 и 2 символом исследуемой строки,
затем от границы между 2 и 3 символом, и т.д. до первого совпадения.
Как только совпадение произошло, дальнейшие проверки прекращаются
и возвращается результат 1 (истина). Если позиция наложения RE выходит
за правую границу за последним символом исследуемой строки и
совпадения не произошло - возвращается "" (ложь).

Кроме атомарных элементов RE существуют элементы, которые не
"покрывают" (или не "поглощают") ни одного символа исследуемого
текста. Это якоря, квантификаторы и проверки.

Квантификатор помещается только после атомарного элемента RE и
задает число повторений этого атомарного элемента. Квантификаторы
рассмотрим в отдельном пункте.

Проверки задают условия, которым должен удовлетворять исследуемый
текст правее места наложения RE (опережающая проверка) или левее
места наложения RE (ретроспективная проверка). Проверкам также
посвятим отдельный пункт.



14. Якоря.

Якорь - элемент RE нулевой ширины, совпадающий только с определенной
(или определенными) позициями исследуемой строки,

метасимвол ^ в RE совпадает c границей нулевой ширины перед первым
символом исследуемого текста

метасимвол $ в RE обозначает границу нулевой ширины за последним
символом исследуемого текста. Если последним символом
исследуемой строки является символ newline (по
умолчанию \n), то этот якорь совпадает также и перед
ним.

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

$/ = undef;
$multstring = <INFILE>;
$/ = "\n";

Для обработки мультистроки с помощью perlRE можно использовать модификатор
/m . Если используется RE с этим модификатором:

$multstring =~ /RE/m

то правила совпадения якорей ^ и $ изменятся:

^ - в /RE/m совпадает с гранью нулевой ширины перед самым первым
символом мультистроки, а также после каждого внутреннего
(кроме завершающего всю мультистроку, если он имеется) символа
newline.

$ - в /RE/m совпадает с гранью нулевой ширины перед каждым (и
внутренним, и последним) символом newline, а также после
последнего символа всей мультистроки, даже в том случае, если
это символ newline.


метапоследовательность \b в RE обозначает границу слова
(границу нулевой ширины либо перед первым символом слова,
либо за последним символом слова). Словом называется
последовательность алфавитно-цифровых символов в исследуемом
тексте. Алфавитно-цифровые символы - это все буквы в
верхнем и нижнем регистре, все цифры и знак подчеркивания.
Кириллица будет считаться буквами только если выполнялась
прагма `use locale;'. Можно также сказать, что якорь \b
совпадает между любыми двумя соседними символоами текста,
один из которых является символом класса \w (символом
слова), а другой является символом класса \W (любой символ,
кроме символов слова). Кроме того якорь \b совпадает в
самом начале исследуемой строки (там же, где якорь ^),
если она начинается с символа слова, и в самом конце
иссследуемой строки (там же, где якорь $), если последним
символом в ней является символ слова. Примечание. В
символьном классе метапоследовательность \b имеет другой
смысл - там она обозначает символ backspace.

Если требуется, чтобы слово "сумма" находилось в самом начале
исследуемого текста, содержащегося в переменной $lot (см. пример
в начале пункта 12.1), то нужно использовать якорь `^'.

if ( $lot =~ m/^сумма/ ) { . . . }

Чтобы исключить совпадения с частью слов (суммарный, осуммарить),
можно использовать якорь границы словa:

if ( $lot =~ m/\bсумма\b/ ) { . . . }

В PerlRE нет отдельных якорей для начала и конца слова в отличие от
egrep ( \< и \> ), имеется только якорь границы слова \b.

Другие якоря PerlRE будут рассматриваться в пунктах, связанных с их
использованием.

Упражнение 14_1.pl:

Удобнее набрать один раз тестируемые при помощи RE строки в файле,
чем вводить их каждый раз с клавиатуры, как это делалось в программе
13_1.pl. Создадим программу 14_1.pl так, чтобы в цикле построчно
читался файл с именем, например tstline14_1 и применялся поиск по
RE к каждой строке и выводились либо только строки, в которых
совпадение найдено, либо только строки в которых совпадений нет.
В тестируемом файле tstline14_1 все строки снабдим номерами #N
(номера можно ставить в начале, или в середине или в конце строки,
потому что искомое слово будет размещено в некоторых строках в
самом начале строки, в некоторых - в самом конце, в некоторых
строках в верхнем регистре). По этим номерам сразу будет видно,
в каких строках было найдено совпадение. Например, содержание файла
tstline14_1 может быть таким:

#file tstline14_1 #1
#2 сумма - искомое слово
сумма - искомое слово #3
сумма - искомое слово #4 сумма
#5 искомое слово сумма
#6 Сумма - искомое слово
Сумма - искомое слово #7
сумма - искомое слово #8 СУММА
#9 искомое слово СУМма
#10 суммарный - не отдельное слово
суммарный - в начале строки не отдельное слово #11
сумма - искомое слово #12 псевдосумма
#13 в конце строки составное слово квази-сумма
#14 квазиСумма - не слово
Сумма-итоговая - сосавное слово в начале строки с заглавной С #15
суммарный - искомое слово #16 квази_СУММА
#17 в конце строки составное слово псевдо_СУМма

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

./11_4.pl tstline14_1

Для построчного чтения файла, заданного в командной строке запуска
программы используем пустой дескриптор файла в операторе угловых
скобок в условии цикла while (<>) { .... } :

use locale;
print qq{Поиск "сумма" в строках файла, заданного в строке запуска\n};

while (<>) {
# очередная строка читается в стандартную переменную $_
chomp;
# отрубается "\n" в строковом значении переменной $_
if (/сумма/)
# if (/сумма$/) # в конце строки
# if (/^сумма/) # в начале строки
# if (/^сумма.+сумма$/) # в начале и в конце строки
# if (/\bсумма\b/) # отдельным словом
# if (/\bсумма$/) # отдельным словом в конце строки
# if (/^сумма\b/) # отдельным словом в начале строки
# if (/^сумма\b.+\bсумма$/) # отд.словом в начале и в конце строки
# ниже те же варианты, но с игнорированием регистра при поиске /i,
# чтобы модификатор /i сработал для кириллицы, необходимо где-то
# ранее в программе задать прагму use locale;
# if (/сумма/i)
# if (/сумма$/i) # в конце строки
# if (/^сумма/i) # в начале строки
# if (/^сумма.+сумма$/i) # в начале и в конце строки
# if (/\bсумма\b/i) # отдельным словом
# if (/\bсумма$/i) # отдельным словом в конце строки
# if (/^сумма\b/i) # отдельным словом в начале строки
# if (/^сумма\b.+\bсумма$/i) # отд.словом в начале и в конце строки

{ print "$_\n"; }
# если для очередной строки проверка в условии
# if дала совпадение с RE - распечатать эту
# строку (она содержится в переменной $_ )
# { } else { print "nomatch:$_\n"; }
# Это распечатка строки в случае если совпадения
# c данным RE нет. Переставляя комментарий с этой
# команды на предыдущую строку можно получить
# список строк, в которых нет совпадений с RE
# Поскольку эта вторая строка содержит также и
# пустой True-блок составного оператора if
# одновременно снять комментарий и с этой и
# с предыдущей строки нельзя - одна из них
# должна быть закомментирована.

} # конец цикла while - по концу файла, заданного
# в командной строке запуска программы (tstline14_1).

Испытайте все 16 вариантов RE, оставляя без начального символа
комментария # каждую, но только одну из строк if и для каждого
конкретного варианта RE (конкретной раскомментированной строкой
с условием if ) произведите 2 запуска - для получения всех строк,
имеющих совпадение с RE (закомментирована строка с блоком else)
и получения всех строк, в которых нет совпадения (закомментирована
строка с True-блоком составного оператора if). Итого должно быть
не менее 32 запусков. Для каждой строки из полученных списков
(как совпавших, так и не совпавших) уясните почему строка не
совпала с данным RE, или почему совпала и что именно в этой
строке совпало. Если по какой-то строке не сможете понять,
почему и что именно совпало или почему что-то не совпало -
обязательно спросите у преподавателя. Только если причины
попадания в соответствующий список всех строк из всех 32 запусков
программы будут понятны, только тогда можете быть уверены, что
механизм работы якорей $ ^ \b и модификатора /i вами усвоен.
Если по какому-то варианту получатся пустые выдачи совпавших
(или несовпавших) строк, дополните файл tstline14_1 строкой, которая
пройдет в этот пустой список и повторите запуск. Если по какому-то
варианту получатся пустые выдачи совпавших (или несовпавших)
строк, дополните файл tstline14_1 строкой, которая пройдет в этот
пустой список и повторите запуск.


15. Метасимвол бэкслэшь.

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

m/\( \\\$var \)/ - совпадает с 9-ти символьной подстрокой:
. . . ( \$var ) . . .

Поскольку v не входит в состав метасимволов, и \v и не входит в
состав метапоследовательностей, то в RE бэкслешь перед v будет
просто игнорироваться и RE в примере совершенно эквивалентно
следующему:

m/\( \\\$\var \)

A вот перед `a' (латинской) и перед `r' бэкслешь сделает RE
совсем другим, поскольку есть метапоследовательности:

\a - совпадение с символом звонка (код 2) \r - совпадение с
символом возврата каретки (код 13)

Несложно догадаться, что это за метапоследовательности:

\n - совпадение с символом новой строки (код 10)
\t - совпадение с символом табуляции (код 9)

Всего в Perl в настоящий момент более 30 (35) метапоследовательностей.

Пример поиска с использованием символа табуляции:

m/\tx\t:\tcos\(x\)/ - совпадает с 11-символьной подстрокой:

TabxTab:Tabcos(x)

При просмотре исследуемого текста выглядит это так:

x : cos(x)



16. Метасимвол точка.

Точка в RE совпадает с одним любым символом, кроме символа новой
строки newline (обычно это "\n"). Если применятся RE c модификатором
/s , то точка совпадает с одним любым (без исключений) символом,
в том числе и с символом новой строки.

Пример.
m/t.nk/ - совпадает с 4-x символьными подстроками
tank, turk, tonk, trnk, ... - на месте точки любой символ,
кроме символа новой строки (код 10), поскольку модификатора
/s нет.

Упражнение 16_1.pl:

Cоставьте тестовый файл tstline16_1 и программу по аналогии с
14_1.pl для испытаний этого примера. Проведите испытания
и уясните роль метасимвола точка .



17. Символьные классы.

17.1. Традиционный класс.

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

[аеиоуэюя]

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

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

Некоторые метасимволы RE не являются метасимволами в классе, а
становятся просто литеральными символами, входящими в класс,
например символ точки в классе представляет литеральную точку.
Собственно говоря, кроме бэкслеши и циркумфлекса в случае, если
он стоит сразу за открывающей скобкой класса, все метасимволы RE
в классе не имеют специального значения и литерально представляют
самих себя. Однако для включения в класс символа $ , его нужно
экранировать \$, не потому, что он является метасимволом в классе,
но из-за интерполяции переменных, производимой во всей области,
занимаемой RE, включая традиционные классы, перед тем, как RE будет
передано для обработки механизму регулярных выражений. По той же
причине нуждается в экранировании для литерального представления
в области, занимаемой RE, также и символ @ ( \@ ), поскольку
интерполируются и скаляры и массивы.

Имеется и символы, которые являются только метасимволами в классе,
но не в RE. Например закрывающая квадратная скобка является
метасимволом класса, но не является метасимволом RE. В RE закрывающая
квадратная скобка и без экранирования представляет литерально саму
себя. Но в символьном классе закрывающая квадратная скобка имеет
особое значение - она закрывает символьный класс, т.е. является
метасимволом. Чтобы литерально представить закрывающую квадратную
скобку в перечне символов класса, ее необходимо экранировать
бэксешем ( \] ) или поставить на 1 место в классе, сразу же за
открывающей скобкой. Отметим отличие от диалекта egrep - поскольку
в egrep бэкслеш в классе не является метасимволом и следовательно
ничего не экранирует, то для представления литерально закрывающей
квадратной скобки в egrep имеется только одна возможность - поставить
символ закрывающей квадратной скобки на 1 место - сразу же за
открывающей квадратной скобкой. Например обозначить в RE подстроку
шириной в 1 символ, которым является любая скобка можно так:

[][(){}<>] # и в egrep и в perl
[()[\]{}<>] # в perl, но не в egrep

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

[][(){}<>-] # и в egrep и в perl
[]()[\-{}<>] # в perl, но не в egrep, в egrep все символы в
# диапазоне кодов от \ (92) до { (123) также
# будут включены в класс



17.2. Инвертированный класс.

Если класс открывается двусимвольным сочетанием [^ (циркумфлекс
сразу за открывающей квадратной скобкой), то это инвертированный
класс - он представляет атомарный элемент шириной 1 символ, которым
может быть любой символ, НЕ перечисленный в классе. Инвертированный
класс имеет все такие же метасимволы, как и обыкновенный класс,
включая и закрывающую квадратную скобку, которая закрывает его.
На любом другом месте в классе циркумфлекс не явяляется метасимволом
и литерально представляет себя. Заметим, что в RE циркумфлекс
является метасимволом - якорем начала строки.

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

#!/usr/bin/perl -w
while (<>) {
chomp; print "$_\n" if /^[^\t ]/; }

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

Но еще кое-что в этом примере требует пояснений. Вы уже знаете,
что если в условии цикла while ничего нет, кроме оператора угловых
скобок, то производится построчное чтение до конца файла в цикле
в скалярную переменную $_. Оператор `chomp;' без аргумента
применяется к этой же переменной и отрубает последний символ новой
строки. Затем эта переменная распечатывается, если выполнено
условие, заданное модификатором if. Остается объяснить куда же
делся оператор поиска m// и оператор "обработать с помощью" =~.
В качестве операнда модификатора if указано только /RE/.
Существуют следующие правила умолчания:

1. Если выражение совтоит только из /RE/, то подразумевается
оператор поиска совпадений m/RE/. Имя оператора может быть
опущено только если в качестве ограничителей использованы символы
/ . А при явном задании оператора в качестве ограничителя можно
использовать любой не алфавитно-цифровой символ или парные
скобки любого вида.

2. Если не задано (при помощи оператора =~ к какому строковому
выражению применятеся оператор поиска совпадений, он применяется
к скалярной переменной $_ .

Правило 2 применимо не только к неявному оператору поиска, но
и при явном задании оператора m//, а также и для всех других
операторов, для котрых объект их применения задаетсся оператором
привязки =~ ( s///, tr///, qr//): если объект применения не
задан оператором =~ , то им является стандартный скаляр Perl $_ .

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

./17_2.pl 17_2.in

Упражнение 17_2:

Создайте тестовый файл 17_2.in, в котором имеется:
- строка в которой текст начинается с самой первой позиции,
- строка, в которой сначала идет пробел, потом текст,
- строка, в которой сначала идет символ табуляции, потом текст,
- пустая строка (два раза подряд нажать Enter, без пробелов),
- еще раз строка, в которой текст начинается с самой первой позиции

Во все строки, кроме пустой, вставьте номер строки, для пустой
строки просто пропустите номер, т.е. номера будут #1 #2 #3
#5 (#4 для пустой строки будет просто пропущен).

Запустите программу и проанализируйте результат.

Вставтье в тестовый файл 17_2.in между 1 и 2 строкой строку в
которой только один пробел, а между 2 и 3 (по нумерации до правки
файла) строку, в которой содержится только один символ табуляции.
Запустите программу. Изменилось ли что-нибудь в выводе и почему?

Запустите программу, используя в качестве тестового файл данной
методички. Если он у вас в директории текущей даты и называется
pre3_Str, то командная строка запуска:

./17_2.pl pre3_Str | less -F

Много ли строк будет выведено и почему?


Эта программа из 3 строк покажется слишком длянной и не очень
изящной по-настоящему ленивому (в хорошем смысле, по Larry Wall)
программисту. В ней отрубается признак новой строки для анализа
с помощью RE, а затем тут же возвращается на место для распечатки
строки, это неизящно. К тому же при этом приходится явно показать
в операторе print потайную скалярную переменную $_ , что также
некрасиво. И три строки для человека, ленивого по-настоящему, это
слишком много. Приведем эквивалентный вариант программы, но по
Perl-овски изящный и лаконичный:

#!/usr/bin/perl -w
while (<>) { print if /^[^\t \n]/ }

Что же изменено. Теперь мы не отрубаем признак конца строки \n
после ее прочтения из файла и пользуемя тем, что оператор print
без списка вывода выводит значение переменной $_. Но запрещаем
прочитанной строке начинаться с символа конца строки, добавив его
в инвертированный класс. Да, если этого не сделать, то инвертированный
класс совпадет с символом конца строки и пустые строки пройдут на
печать. Инвертированный класс совпадает с любым символом, не
перечисленным в нем, в том числе и с символом конца строки.
Теперь вся программа занимает одну неполную строку, менее 30 символов.

Упражнение 17_2a:
Попробуйте с помощью "укороченной программы" обработать все,
что вы обрабатывали с помощью 17_2.pl (данную укороченную
программу можно назвать 17_2a.pl).

Всегда ли получился одинаковый результат с прежней "длинной"
программой?

Упражнение 17_2b:
Составьте измененную программу 17_2b.pl, которая будет нумеровать
все вводимые строки от начала обрабатываемого файла и выводить
соответствующий номер в начале выводимой строки (в которой
произошло совпадение с RE).

Можно организовать префикс с именем файла к каждой выводимой строке,
воспользовавшись тем, что имя текущего обрабатываемого файла из
списка (@ARGV) содержится в скаляре $ARGV.

Упражнение 17_2c:
Составьте измененную программу 17_2с.pl, которая будет печатать
имя файла и номер выводимой строки от начала данного файла.
Запустите программу для обработки всех файлов текущего директория.

./17_2c.pl ./* | less -F

Сохраните результат в текущем директории для контроля:

./17_2c.pl ./* > 7_2c.out


17.3. Стандартные классы Perl.

В PerlRE имеется 6 стандартных классов, обозначаемых
метапоследовательностями (3 пары: обычный и инвертированный):

Цифра \d эквивалентно [0-9]
Не-цифра \D эквивалентно [^0-9]
Пробельный символ \s эквивалентно [ \t\r\n\f]
Не пробельный символ \S эквивалентно [^ \t\r\n\f]
Символ слова \w эквивалентно [a-zA-Z_0-9]
Не символ слова \W эквивалентно [^a-zA-Z_0-9]

Если выполнялась прагма use locale; , то в число символов слова
также включены буквы национального алфавита, поэтому последние
два класса становятся эквивалентны следующим традиционным классам.

use locale;
\w эквивалентно [a-zA-Z_0-9ю-ЪёЁ]
\W эквивалентно [^a-zA-Z_0-9ю-ЪёЁ]


Класс пробельных символов включает в себя 5 символов, 4 из которых
обозначаются метапоследовательностями:
пробел,
табуляция \t (код 9),
возврат каретки \r (код 13),
символ новой строки \n (код 10),
символ новой страницы \f .

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

[\w()[\]{}]


В отличие от классов POSIX классы, обозначаемые метапоследовательностями,
могут использоваться и вне традиционного класса, т.е. без квадратных
скобок. Следующая программа найдет и выведет все непустые строки,
оканчивающиеся пробельными символами. Анализируются все строки
всех файлов, имена которых заданы в командной строке при запуске
скрипта(см. пример в п.7.2).

#!/usr/bin/perl -w
while (<>) { print if /\s+$]/ }


# в этом примере мною была допущена ошибка, которую я заметил и исправил
# только #20100425
# Ошибочная строка:
# while (<>) { print if /\s+\n$]/ }
# Ошибка состояла в следующем: если не ликвидировать разделитель
# записей вводных файлов ( $/=undef; ), то оператор <> читает из
# вводного файла последовательность символов до первого newline ("\n"),
# включая и его, либо до конца файла, если в файле это последняя
# строка, в конце которой нет символа newline "\n"
# поэтому в исследуемой строке $_ может быть не более одного newline
# в конце строки, в этом случае якорь $ совпадет перед этим
# завершающим строку newline, а не после него, следовательно
# это RE никогда совпадать не может.



Регулярное выражение оканчивается якорем конца строки $, что
заставляет его накладываться на самый конец текста. Символ
новой строки предшествует якорю конца строки, так как в программе
он не отрубается функцией chomp и имеется в любой прочитанной строке.
Перед ним идет класс пробельных символов с квантификатором `+',
задающим его повторение 1 или более раз, но не менее 1 раза.
Таким образом будут выявлены строки, оканчивающиеся не менее,
чем одним символом пробел или Tab. В принципе квантификатор
для данной задачи не является обязательным, достаточно
выявить один пробельный символ перед символом конца строки.

Упражнение 17_3:

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

Упражнение 17_3a:
Составьте измененную программу 17_3a.pl, которая будет печатать
в виде префикса имя файла и номер выводимой строки от начала
данного файла. Запустите программу для обработки всех файлов
текущего директория.

./17_3a.pl ./* | less -F

Сохраните результат в текущем директории для контроля:

./17_3a.pl ./* > 17_3.out


17.4. Классы POSIX.

В традиционные классы perlRE могут включаться классы POSIX. Их
перечень и правила использования ничем не отличаются от egrepRE,
см. соответствующий раздел в методичке по egrepRE (r4_Str). Также
как и в egrepRE классы POSIX в perlRE могут использоваться только
в составе традиционного класса, т.е. внутри квадратных скобок,
даже если кроме класса POSIX в традиционном классе ничего больше
не задается (тогда получится удвоение квадратных скобок). Например,
класс символов слова \w эквивалентен более громоздкой записи,
использующей класс POSIX [:alnum:] :

\w эквивалентно [[:alnum:]]



17.5. Класс кириллических букв.

Поясним почему класс кириллических букв задается не [а-яА-Я], а
[ю-ЪёЁ]. Дело в том, что любой оператор диапазона в Perl, будь
то оператор Perl (две точки .. ) или задание диапазона метасимволом
класса PerlRE (дефис -) работает по числовым кодам символов,
задающих границы диапазона. Поэтому невзирая на то, находится ли
регулярное выражение в зоне прагмы компилятора use locale или в
зоне no locale (что является значением по умолчанию) оператор
диапазона и диапазон в символьном классе работает по числовым
кодам. В кодовой таблице koi8-r 32 кириллические буквы верхнего
регистра (все, кроме Ё) занимают самые последние коды (224 -255),
каждая соответствующия буква нижнего регистра имеет код на 32
меньший, таким образом кириллические буквы нижнего регистра занимают
коды 192 - 223. Код 192 соответствует букве "ю", код 255 - букве
"Ъ", поэтому класс [ю-Ъ] представляет класс из 64-х символов: 32
кириллических буквы нижнего и 32 буквы верхнего регистра. По
возрастанию числовых значений кодов последовательность этих 64
символов следующая:

ю а б ц д е ф г х и й к л м н о п я р с т у ж в ь ы з ш э щ ч ъ
Ю А Б Ц Д Е Ф Г Х И Й К Л М Н О П Я Р С Т У Ж В Ь Ы З Ш Э Щ Ч Ъ

Буква "ё" нижнего регистра имеет код 163, соответствующая буква
верхнего регистра "Ё" - код 179, поэтому их нужно дополнительно
вне диапазона указывать в классе.

На первый взгляд такая последовательность кириллических букв в
таблице koi8-r кажется придуманной в наркотическом бреду и
бессмысленной, но на самом деле она продумана и используется еще
с момента зарождения компьютерной связи и сетей, когда программное
обеспечение узлов маршрута передачи сообщений было 7-битным (а 8-й
использовался как бит четности для контроля передачи). В этом
случае из сообщения, написанного на русском языке в коде каждой
кириллической буквы при передаче пропадал старший бит, что равносильно
уменьшению числового значенния кода на 128. Тогда кириллические буквы,
соответственно превращались в следующие символы ascii с кодами 64 - 127:

@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _
` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ DEL

Буква "ё" превращалась в символ # (числовой код 35), а "Ё" - в цифру 3
(числовой код 51).

Но вы видите, что 26 кириллических букв, часто употребляемых в
русских текстах транслитерируются в латинские a-z противоположного
регистра. Инверсия регистров позволяла сразу догадаться, что
произошла потеря старшего бита, и что текст был на национальном
языке (не английский). Поскольку большинство букв фонетически
правильно транслитерировалась латиницей, сообщение можно было
частично прочесть, частично догадаться о сути написанного. Это и
сейчас актуально, если вы пишете в другую страну на русском в
кодировке koi8-r. А вот если в сp-1251, то писать можно только
латинскими буквами, если знаете английски, то пишите по-английски,
а если нет то сами транслитеруруйте каждое слово фонетически
латинскими буквами, автоматически это невозможно и ваш русский
адресат в другой стране ничего не сможет прочитать и понять из
вашего послания - ведь тот компьютер локализован под местный
национальный язык.

Впрочем грядет эра UNICOD, мощная поддержка кодировки utf-8 которого
уже реализована в Perl. Как только соответствующее программное
обеспечение будет широко внедрено во всех странах мира, сможете
писать на любом языке в любую страну. В utf-8 ASCII-символы (коды
1-127) сохраняют свои стандартные значения и имеют длину 1 байт,
а все прочие символы имеют длину от 2-х байт и более; в настоящее
время до 6 байт, что позволяет иметь одновременно (не переключаясь)
все языки, включая иероглифические восточные японский, китайский,
корейский. Так что сможете без графических хлопот включать в русские
тексты цитаты из японских манускриптов на иероглифах (имея хороший
текстовый редактор).


Какова же тогда роль прагмы компилятора Perl "use locale;"? В
зоне своего действия она сообщает компилятору что все 66
букв кириллицы относятся к типу символов алфавитных (включены в
C_TYPE), для них начинают работать функции изменения регистра
lc EXPR, uc EXPR, выполняется интерполяция в контексте двойных
кавычек бекслеш - последовательностей \l \u \L ... \E \U ... \E.
При сравнении строк операторами cmp lt le gt ge старшинство
букв определяется кириллическим алфавитом, а не числовыми
кодами сравниваемых символов, поэтому все алфавитные сортировки
будут выполняться правильно, в соответствии с алфавитом:

А Б В Г Д Е Ё Ж З И Й К Л М Н О П Р С Т У Ф Х Ц Ч Ш Щ Ъ Ы Ь Э Ю Я
а б в г д е ё ж з и й к л м н о п р с т у ф х ц ч ш щ ъ ы ь э ю я

Обратите внимание, что инвертируется даже последовательность регистров -
в зоне use locale для кириллицы , как и для латиницы при строковом
сравнении теперь буквы верхнего регистра меньше букв нихнего регистра.


Упражнение 17_5:

Следующия програма читает в массив файл с перечнем кириллических
букв и выводит их порядок по числовым кодам и строковому сравнению
в зоне "no locale;" и в зоне "use locale;"


if ($f=$ARGV[0]){print "Тестовый файл с кириллическими буквами $f\n"}
else {print "Нужно задать в командной строке аргумент - файл с ".
"кириллическим алфавитом\n".
"каждая буква задается в отдельной строке файла\n"; exit}

@ar = <> ; # Читаем тестовый файл с кирилличекскими буквами (в каждой
# строке тестового файла должно быть по одной букве)
# каждая прочитанная строка присваивается очередному
# элементу массива

foreach $v (@ar) { $v =~ s/\s//g ; push (@arc, $v) }
# Убираем в каждом элементе массива все пробельные
# символы, включая "\n", каждый элемент массива @arc
# содержит только один символ - строка длиной 1 символ

print "Исходная последовательность букв в тестовом файле @ARGV\n";
print "@arc\n\n";

print "no locale; сортировка по числовым кодам символов\n";
@ordnoloc = sort { ord($a) <=> ord($b) } @arc ;
print "@ordnoloc\n\n";
# функция ord EXPR возвращает числовой код первого символа
# результата вычисления выражения EXPR в контексте строки.

print "no locale; сортировка по возрастанию строковых ".
"значений символов\n";
@noloc = sort { $a cmp $b } @arc ;
print "@noloc\n\n";


use locale;

print "use locale; сортировка по числовым кодам символов\n";
@ordloc = sort { ord($a) <=> ord($b) } @arc ;
print "@ordloc\n\n";

print "use locale; сортировка по возрастанию строковых ".
"значений символов\n";
@loc = sort { $a cmp $b } @arc ;
print "@loc\n\n";

print " Коды ё Ё \n";
print "ё - ", ord("ё"), "; Ё - ", ord("Ё"), ";\n";

Создайте тестовый файл (например 17_5.alf) с перечнем кириллических
букв, порядок их произвольно перепутайте. Каждая строка должна
содержать одну и только одну кириллическую букву. Пробельные
символы не помешают, все равно в алгоритме предусмотрено их
удаление при помощи оператора поиска с заменой s///g.

Запустите программу, задав в командной строке созданный вами тестовый
файл, например:

./17_5.pl 17_5.alf



17.6. Программа-тренажер для освоения механизма RE
и его использования для поиска.

Тренажер (программа, описание работы с ней и ее рабочие файлы с
demo-вариантами) находятся в архиве поддиректория ftstre.tgz в
/cell/public/8sem/2008/lab/. Развернуть этот директорий и выполнить
демо-варианты (5 RE x 3 испытуемых строки = 15 вариантов) нужно
не в директории текущей даты занятий, а в поддиректории ~/ftstre,
который автоматически будет создан при распаковке архива. Итак для
разворачивания работы копируем архив в свой home и распаковываем,
после чего делаем текущим возникший при раскпаковке директорий ftstre
и разбираемся с тренажером, выполняя demo-варианты:

cp -v /cell/public/8sem/2008/lab/ftstre.tgz ~/
cd ~
tar -xvzf ftstre.tgz
cd ftstre

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


********* начало файла ~/ftstre/README **************

Работа с тренажером по исследованию регулярных выражений ./ftstre.pl

Исследуемое регулярное выражение записывается в первой записи (строке)
файла ./re между стандартными ограничителями:

/исследуемое_RE/ 14. комментарий

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

Испытав и разобравшись с совпадениями регулярного выражения во
всех исследуемых строках, для исследования следующего RE не
изменяйте первую строку, а спустите ее вниз, например образовав
в начале файла новую строку для запиcи нового RE; а если проще
новое RE cоздавать путем внесения изменений в какое-то из уже
имеющихся в файле ./re, продублируйте эту строку-прототип и только
после этого изменяйте 1-ю строку в файле ./re, создавая новое RE.
При проверке вашей работы будут учитываться только те варианты
RE и в том виде, как вы их оставите в файле ./re.

Исследуемая строка, в которой производится поиск совпадений с RE
записывается в 1-й записи файла ./tstline, в левой части от начала
записи до ограничителя, составленного из двух символов |# (вертикальная
черта и знак комментария). Все содержание строки правее этого
ограничителя явялется комментарием, таи пишется номер исследуемой
строки и возможно, другие особенности данной строки.

По умолчанию исследуемое RE прииеняется только к первой строке
файла ./tstline, для применения к другой строке, нужно произвести
ротацию в файле ./tstline, выставив другую строку на 1-е место в
файле ./tstline, подобно тому как другое RE выставляется на 1-е
место в файле ./re, и повторно запустить прогроамму ./ftstre.pl.

Имеется возможность применить исследуемое RE циклически ко всем
строкам файла ./tstline, задав в командной строке запуска программы
опцию -a , поскольку результат при этом не уместится на одном экране,
нужно будет запускать программу в конвеере с less:

./ftstre.pl -a | less

Если какие-то из строк файла ./tstline при этом не нужно обрабатывать,
нужно их закомментировать, вписав в самом начале строки |# .

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

То состояние файлов, которое вы скопировали в свой home, является
у вас исходным, файл re содержит пять регулярных выражений, а
файл tstline - две исследуемы строки. Начать нужно с того, что
перепробывать эти пятнадцать вариантов по очереди подставляя
каждую из строк в файлах re и tstline в начало файла и
запуская программы. Каа только освоите анализ результатов с одной
испытуемой строкой, можно попробовать использовать опцию -a чтобы
обойтись меньшим числом запусков программы, чем 15, но это только
когда вы будете уверены, что не запутаетесь в анализе. Лучше
всего предварительно понять, что делает каждое из регулярных
выражений, но хотя бы сделать это, получив результат, т.е. понять
как он получился и как он связан с каждым из элементиов RE .
Второй вариант строки появился, потому что второе регулярное
выражение не совпадало, если в начале не было хотя бы одного
пробела. (Почему?).

Затем придумывайте свои регулярные выражения и свои исследуемые
строки и вставляйте новые строки в начало файлов tstline и
re, увеличивая число строк в этих файлах - это ваш отчет о работе.

В стартовом варианте программы комментарии расставлены так, что
поиск совпадений производится в списковом контексте оператора m//.
Можете копировать файл программы ftestre.pl в тот же директорий с
изменением имени программы или в директорий текущей даты занятий
без изменения имени прораммы и переставить комментарии в копии так,
чтобы исследовать применение регулярных выражений в скалярном контексте.

Можете скопировать еще раз с новым именем ваш вариант программы
скалярного контекста оператора m// и добавить в копии модификаторы
m//g или m//gc и исследовать последовательный поиск.

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

********* конец файла ~/ftstre/README **************


Если у Вас до настоящего момента не сформировалось неясных моментов
в RE и замыслов интересных опытов опробывания определенных RE
на определенных строках, не мучайтесь и не высасывайте из пальца
идиотских примеров или микроскопических вариаций тех же
демоо-вариантов. Ограничьтесь 15 demo-вариантами и идите дальше.
На этих 15 вариантах вы с тренажером ознакомились и поняли как
его использавать, дальше будут разные примеры и, возможно для
уяснения некоторых вы захотите использовать тренажер. Тогда
скопируете программу ~/ftstre.pl в поддиректорий текущей даты
занятий, когда захотите его использовать, там же создадите и
рабочие файлы ./re и ./tstline, разместите в них непонятные или
заинтересовавшие вас варианты и проанализируете что и как там
совпадает. Тренажер можно использовать для уясненя работы оператора
m// в списковом и скалярном контекстах и для последовательного
поиска. Если это будет сделано с пониманием и к месту, это очень
весомый вклад в оценку вашей работы.



18. Квантификаторы. Механизм RE.

18.1. Традиционные (максимальные) квантификаторы.

Квантификатор помещается только после атомарного элемента RE и
задает число повторений этого атомарного элемента. В perlRE всего
11 квантификаторов. Следующие 6 квантификатров perlRE эквивалентны
квантификаторам egrepRE, их описание и примеры см. в методичке
r4_Str, здесь мы только перечислим их:

* - повторение предшествующего атомарного элемента 0 или более раз.
+ - повторение предшествующего атомарного элемента 1 или более раз.
? - повторение предшествующего атомарного элемента 0 или 1 раз.

Интервальные квантификаторы задаются при помощи фигурных скобок.
MIN, MAX и COUNT обозначают целые положительные числа. Если в
качестве ограничителя RE используется не апостроф, то это могут
быть скалярные переменные, значения которых - положительные числа.
Поскольку литерал RE интерполируется по правилам двойных кавычек
перед тем как будет задействована машина поиска совпадений, скалярные
переменные будут заменены своими значениями. Но произвольные
выражения использовать в литералах RE нельзя.

{MIN,MAX} - повторение предшествующего атомарного элемента не менее MIN раз
и не более MAX раз.
{MIN,} - повторение предшествующего атомарного элемента не менее MIN раз.
{COUNT} - повторение предшествующего атомарного элемента COUNT раз.

Отметим, что число повторений, задаваемое квантификаторами, относится
к повторению атомарных элементов в RE, а не к числу повторений
подстрок в исследуемом тексте. Например, пусть:

$tx="аббббббв";

В строке символ "б" повторен 6 раз, но совпадет любое из следующих RE:

/б{4}/
/б{3,}/
/б{2,5}/
/б*/
/б?/
/б+/
/б/

Упражнение 18_1_1:

Проверьте на тренажере что и как совпадает с этими RE в этой
строке. Добавьте еще строку "аббв", какие из этих RE совпадут
в этой строке и с чем именно совпадут?


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

$tx2 = "aaa 156 bbb 2893 ccc 7704 ddd";
$tx2 =~ m/(\d\d).*(\d\d)/;
print "\$1= $1\n"; # 15
print "\$2= $2\n"; # 04
print "\$&= $&\n" # 156 bbb 2893 ccc 7704

Упражнение 18_1_2:

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

Чтобы понять полученный результат рассмотрим как работает механизм RE.

Упражнение 18_1_3:

Составить программу-фильтр, читающую из STDIN строки и выводящую
на STDOUT те строки, в которых встречаются все 5 гласных английского
языка a,e,i,o,u,y .
Испытать программу на вебстерском словаре слов американского языка:

cat /usr/share/dict/web2 | 18_1_3.pl | less

и на вебстерском словаре словосочетаний:

cat /usr/share/dict/web2a | 18_1_3.pl | less


Одно из возможных решений:

#!/usr/bin/perl -w
while (<>) {
print if /a/i && /e/i && /i/i && /o/i && /u/i && /y/i;
}

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

Если в командной строке запуска заменить утилиту просмотра less на
утилиту подсчета строк, то получим число слов и словосочетаний. Сделайте
это.


Упражнение 18_1_4:

Скопируйте и переделайте программу 18_1_4.pl, так чтобы пропускались
на выход только такие строки где не просто все гласные встречаются,
а расположены в алфавитном порядке, т.е первой встречается a потом e,
и т.д., и последней встречается y .


18.2. Механизм RE.


Различается два типа механизмов (алгоритмов поиска совпадений RE
в исследуемой строке): Детерминированный конечный автомат (ДКА) и
недетерминированный конечный автомат (НДКА). ДКА проще, быстрее,
но принципиально не обеспечивает многих возможностей расширенных
RE и ряд задач текстовой обработки при его использовании не может
быть решен. ДКА не может поддерживать сохраняющие круглые скобки
и обратные ссылки, минимальные квантификаторы, не говоря уже об
опережающих и ретроспективных проверках. Механизм ДКА используется
в grep, awk, lex. В версии GNU egrep сделана попытка комбинированного
решения, там используются оба механизма - сначала основной ДКА, а
если требуется реализовать сохраняющие скобки и обратные ссылки,
то подключается НДКА. Но в силу того, что с кириллицей обратные
ссылки не работают, если захватывается более одного символа, и
поиск при комбинировании обоих механизмов оказывается очень
медленным, эта попытка комбинирования вряд ли может считаться
удачной. ДКА называют также механизмом, управляемым строкой
(исследуемым текстом), а НДКА называют механизмом, управляемым RE.

Функциональные возможности НДКА существенно шире, этот механизм
сложнее, требует большего времени, но он значительно интереснее.
Развитые средства RE используют механизм НДКА, разумеется этот
механизм используется и в perlRE. В курсе изучается только механизм
НДКА и его возможности, переходим к нему.

Определим понятия символа и позиции в исследуемой строке.

Символом будем называть любой символ в исследуемой строке
(алфавитный, цифровой, пробел, табулирование, признак новой строки,
любой управляющий символ и т.д.)

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

Например, в строке

$line = "ababacaca";

9 cимволов и 10 позиций:

0 1 2 3 4 5 6 7 8 9
|a|b|a|b|a|c|a|c|a|


Самым внешним циклом НДКА является перемещение исходной позиции
наложения RE по исследуемому тексту. Во внутреннем цикле (от
исходной точки) RE анализируется слева направо, а во всех точках,
где возможна неоднозначность наложения (например квантифицированный
звездочкой атомарный элемент может повторяться 0 или более раз),
производится сохранение состояний, которые могут совпадать,
обеспечивающее возможность возврата к этой точке и пересмотра
произведенного наложения. Как только обнаруживатся несовпадение
RE, производится возврат к последней точке сохранения состояний
(налево в RE), выбирается следующий вариант и от этой точки опять
накладывется RE.

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

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

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

Если совпадения не произойдет и будут исчерпаны все состояния в
данной точке возврата, будет возврат на предшествующую точку
возврата и продолжится перебор всех состояний в этой предшествующей
точке. Если совпадения нет и нет предшествующих точек возврата,
состояний, это означает, что совпадение данного RE с данной позиции
исследуемого текста невозможно, произойдет выход во внешний цикл
и смещение на 1 вправо исходной точки наложения RE (это смещение
безвозвратно и гарантирует конечность алгритма). Т.е. исходной
позицией наложения RE станет граница нулевой ширины между следующими
двумя символами правее в исследуемом тексте и внутренний цикл будет
повторен. Если и с этой позиции совпадение RE не будет получено
при переборе всех комбинаций всех вариантов во всех точках возврата
в RE, произойдет возврат во внешний цикл НДКА и перемещение
исходной позиции еще на 1 вправо. Возврат назад налево исходной
точки наложения RE в исследуемом тексте никогда не производится,
поэтому алгоритм конечен, зацикливания быть не может, исходная
точка неуклонно продвигается слева направо по тексту, однако объем
вычислений при определенных условиях для продвижения исходной
позиции наложения RE на 1 направо может быть столь велик, что
требует астрономического времени и потому становится нереальным.

Когда исходная точка наложения достигнет конца исследуемого текста,
механизм НДКА RE завершится с результатом unmathced. Останов с
результатом matched будет получен при первом же совпадении RE в
этом процессе.

Точки возврата образуются только двумя элементами RE:
- квантифицированый атомарный элемент (кроме {COUNT}),
- группа альтернатив `|'.

Если таких элементов нет, то нет никаких возвратов и внутренний
цикл завершается за один проход - производится простое наложение
RE от исходной точки слева направо.

Единственный квантификатор, не создающий точку возврата - это
{COUNT}, поскольку он не имеет неоднозначности числа повторений,
квантифицированный с его помощью элемент RE не создает разветвления
способа дальнейшего наложения RE и если бы и был возврат в эту
точку, все равно ничего нельзя изменить. Например (_{32}) -
32-кратное повторение симсола подчеркивания, никакой точки возврата
для такого элемента RE не будет создано. Любой квантификатор,
допускающий варианты числа повторений, создаст точку возврата в
RE. Например, (_?) - повторение символа подчеркивания 0 или 1 раз.
Или (_{3,7}) - повторение символа подчеркивания от 3 до 7 раз.
Такие элементы создадут точки возврата в RE.

Рассмотрим как работает алгоритм НДКА на следующем примере:


"Bad Bilbo Baggins" =~ /(b[^a]+)(b.*)\s/i ;
print "\$1=|$1| \$2=|$2|\n"; # $1=|Bil| $2=|bo|


В исследуемой строке 17 символов, 18 позиций, регистр букв при
проверке совпадений игнорируется (оператор поиска имеет модификатор
/i), буква b встречается 4 раза; буква a, пробел, i, g - по 2 раза;
буквы d, l, o, n, s - однократно.

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
B a d B i l b o B a g g i n s

Наложение RE начинается с 0-й позиции, литеральный символ b
совпадает с B в исследуемой строке (несмотря на различие в
регистре поскольку применяется модификатор /i). Однако следующий
элемент RE - инвертированный класс [^a]+ (любой символ, кроме
a, квантифицированный метасимволом +, требующим хотя бы однократное
повторение), не совпадает. Поскольку левее в RE нет ни одной точки
возврата, совпадение RE с 0-й позиции невозможно и следует выход
во внешний цикл алгоритма - смещение исходной позиции наложения
RE на 1 вправо. Исходной становится позиция 1.

С позиции 1 не совпадает уже 1-й
Соседние файлы в предмете Операционные системы