| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |

Глава 7

    В этой главе:


Регулярные выражения

Основные понятия

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

Регулярные выражения используются многими программами, в частности, UNIX-командами, программами grep, sed, awk, ed, vi, emacs и даже различными shell. В каждой программе используется свой набор метасимволов (большей частью они совпадают). Perl ≈ семантическое надмножество всех этих средств: любое регулярное выражение, которое можно записать в одной из подобных программ, может быть записано и на языке Perl, но не обязательно теми же символами.

Основные направления использования регулярных выражений

Если бы нам нужно было найти в каком-то файле все строки, содержащие строку abc, мы могли бы использовать команду grep:

grep abc somefile >results

В этом случае abc ≈ регулярное выражение, которое команда grep сверяет с каждой входной строкой. Строки, соответствующие этому регулярному выражению, посылаются на стандартный вывод и попадают в файл results (так как в командной строке стоит оператор переадресации).

В Perl мы можем превратить строку abc в регулярное выражение, заключив ее между косыми:

if (/abc/) ( print $_;

}

Но что же сверяется с регулярным выражением abc в данном случае? Да наша старая подруга, переменная $_! Если регулярное выражение заключено между косыми (как в этом примере), то переменная $_ сверяется с регулярным выражением. Если значение переменной совпадает с регулярным выражением, операция сопоставления возвращает значение "истина". В противном случае она возвращает "ложь".

В данном примере предполагается, что переменная $_ содержит какую-то строку текста и выводится, если в любом месте этой строки обнаруживается последовательность символов abc (аналогичные действия производит приведенная выше команда grep. Однако в отличие от grep, которая оперирует всеми строками файла, данный фрагмент Perl-программы просматривает только одну строку). Чтобы обрабатывались все строки, добавьте операцию цикла:

while (о) (

if (/abc/) { print $_;

> )

А что, если мы не знаем, сколько символов b стоит между а и с? То есть что нужно делать, если мы хотим вывести на экран строку только в том случае, если она содержит символ а, за которым следует ни одного или более символов b и символ с? Работая с grep, мы написали бы так:

grep "ab*c" somefile >results

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

while (о) {

if (/ab*c/) ( print $_;

) >

Как и в grep, такая запись обозначает последовательность, содержащую символ а, ни одного или более символов b и символ с.

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

Еще одна простая операция, в которой используются регулярные выражения, операция замены, посредством которой часть строки, соответствующая регулярному выражению, заменяется другой строкой. Операция замены похожа на команду s UNIX-утилиты sed: она состоит из буквы s, косой черты, регулярного выражения, еще одной косой, заменяющей строки и третьей косой черты:

s/ab*c/def/;

Переменная (в данном случае $_) сопоставляется с регулярным выражением (ab*c). Если сопоставление оказалось успешным, то соответствующая часть строки отбрасывается и заменяется строкой (def). Если сопоставление неудачно, ничего не происходит.

Позже, в разделе "Операция замены", мы рассмотрим множество опций операции замены.

Образцы

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

Образцы, обозначающие один символ

Самый простой и самый распространенный символ, встречающийся в регулярных выражениях, это одиночный символ, соответствующий самому себе. Другими словами, наличие буквы а в регулярном выражении требует наличия соответствующей буквы а в строке.

Следующий из самых известных символов сопоставления точка ("."). Точка обозначает любой одиночный символ, кроме символа новой строки (\п). Например, образцу /а. / соответствует любая двухбуквенная последовательность, которая начинается с буквы а и не является последовательностью "а\п".

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

/[abode]

соответствует строка, содержащая любую из первых пяти строчных букв алфавита, тогда как образцу

/[aeiouAEIQU]

соответствует любая из первых пяти гласных, причем как строчных, так и прописных. Если вы хотите вставить в список правую квадратную скобку (]), поставьте перед ней обратную косую или же поставьте эту скобку на первое место в списке. Диапазоны символов (например, от а до z) можно приводить в сокращенной записи, указав конечные точки диапазона через дефис (-). Чтобы включить в список дефис как таковой, поставьте перед ним обратную косую или поместите его в конец. Вот еще несколько примеров:

[0123456789] # обозначает любую цифру

[0-9] # то же самое

[0-9\-] # обозначает цифры 0-9 или знак минус

[a-z0-9] # обозначает любую строчную букву или цифру

[a-zA-ZO-9_] # обозначает любую букву, цифру или знак подчеркивания

Существует также такое понятие, как отрицание класса символов: оно обозначается знаком л, который ставится сразу же за левой скобкой. Такому классу символов соответствует любой символ, отсутствующий в этом списке. Например:

["0-9] # обозначает любой нецифровой символ

["aeiouAElOU] # обозначает любую негласную букву

["\"] # обозначает любой символ, кроме символа "

Для удобства пользователя некоторые распространенные классы символов определены заранее. Они представлены в таблице 7.1.

Таблица 7.1. Предопределенные классы символов

Конструкция

Эквивалентный класс

Конструкция с отрицанием

Эквивалентный класс с отрицанием

\d (цифра)

\w (обычный символ)

\s (пробельный символ)

[0-9] [a-zA-ZO-9] [ \r\t\n\f]

\d (нецифровые символы)

\w (специальные символы)

\s (непробельный символ)

^0-9] [^a-zA-ZO-9] [" \r\t\n\f]

 

Образцу \d соответствует одна цифра. Образцу \w формально соответствует один обычный символ, но на самом деле ему соответствует любой символ, который допустим в именах переменных Perl. Образцу \s соответствует один пробельный символ. К пробельным символам относятся пробел, возврат каретки (редко используемый в UNIX), символ табуляции, символы перехода на новую строку и на новую страницу. Варианты конструкций с 'использованием прописных букв соответствуют дополнениям (отрицаниям) этих классов. Так, \w обозначает один специальный символ, \s ≈ один символ, который не является пробельным (т.е. является буквой, знаком препинания, управляющим символом и т.д.), a \D ≈ один нецифровой символ.

Приведенные выше конструкции можно использовать при задании других классов символов:

[\da-fA-F] # соответствует одной шестнадцатеричной цифре

Образцы, обозначающие группу символов

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

Последовательность

Первый (и, вероятно, самый неочевидный) образец данного видапоследовательность. Например, образец abc соответствует букве а, за которой следует буква Ь, за которой идет буква с. Вроде бы просто, но название этому виду образца все равно нужно дать, чтобы в дальнейшем знать, о чем идет речь.

Множители

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

Есть еще два образца, работающих подобным образом: знак "плюс" (+), который обозначает один или более экземпляров стоящего непосредственно перед ним символа, и вопросительный знак (?), который обозначает ни одного или один экземпляр стоящего непосредственно перед ним символа. Например, регулярное выражение /fo+ba?r/ обозначает символ f, за которым следует один или более символов о, затем символ Ь, затем ни одного или один символ а и, наконец, символ г.

Однако все описанные выше образцы (множители) характеризуются "прожорливостью". Например, если множителю может соответствовать 5-10 символов, то каждый раз он будет выбирать десятисимвольную строку. Например,

$_ = "fred xxxxxxxxxx barney";

s/x+/boom/;

всегда заменяет словом boom все символы х (что в результате дает fred boom barney), а не только один или два, несмотря на то, что более короткий набор иксов соответствовал бы этому же регулярному выражению.

Если нужно сказать "от пяти до десяти" символов х, можно поставить пять иксов, а затем еще пять, дав после каждого из последних пяти вопросительный знак. Это, однако, выглядит уродливо. Есть более простой способ применение общего множителя. Общий множитель состоит из пары фигурных скобок, между которыми заключены одно-два числа, например /х{5,10}. Необходимо найти символ, стоящий непосредственно перед скобками (в данном случае это буква х), повторяющийся указанное число раз (в рассматриваемом случае от пяти до десяти)*.

Если второе число не указано (например, /х {5, } /), это означает "столько или больше" (в данном случае пять и более), а если выпущена и запятая (например, /х{5}/), это означает "ровно столько" (в данном случае пять символов х). Чтобы получить пять или менее символов х, нужно перед запятой поставить нуль: /х {0, 5} /.

Так, регулярное выражение /а. {5} b/ соответствует букве а, отделенной от буквы b любыми пятью символами, кроме символов новой строки, и все это может быть в любом месте строки. (Вспомните, что точка соответствует любому символу, кроме символа новой строки, а нам здесь нужно пять таких символов.) Эти пять символов не обязательно должны быть одинаковыми. (В следующем разделе мы увидим, как заставить их быть одинаковыми.)

Можно было бы вполне обойтись без *, + и ?, потому что эти образцы полностью эквивалентны образцам {0,},(!,} и {0,1}, но проще ввести один эквивалентный знак препинания, к тому же это более привычно.

Если в одном выражении используются два множителя, то "правило прожорливости" дополняется правилом "чем левее, тем прожорливее". Например:

$_ = "а ххх с хххххххх с ххх d";

/a.*c.*d/;

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

Можно заставить любой множитель перестать быть "прожорливым" (т.е. сделать его ленивым), поставив после него вопросительный знак:

$_ = "а ххх с хххххххх с ххх d";

/a.*?c.*d/;

Здесь а. * ? с теперь соответствует минимальному числу символов между а и с, а не максимальному. Это значит, что с образцом совпадает часть

* Конечно, /\d(3}/ соответствует не только трехзначным числам, но и любому числу с количеством знаков больше трех. Чтобы задать именно трехзначное число, нужно использовать фиксирующие точки, которые рассматриваются ниже в разделе "Фиксирующие образцы".

строки до первой буквы с, а не до второй. Такой модификатор можно ставить после любого множителя (?,+,* и {m,n}).

Что, если строка и регулярное выражение несколько изменятся, скажем, так:

$_ ╟ "а ххх се хххххххх ci xxx d";

/a.*ce.*d/;

Символы .* в этом случае соответствуют максимально возможному числу символов, стоящих до следующей буквы с, но очередной символ регулярного выражения (е) не совпадает с очередным символом строки (i). В этом случае мы получаем автоматический поиск с возвратом: поиск начинается сначала и завершается остановкой в некоторой позиции до выбранной на первом этапе (в нашем случае в позиции предыдущей с, рядом с е)*. Сложное регулярное выражение может включать множество уровней поиска с возвратом, в результате чего время выполнения значительно увеличивается. В данном случае превращение множителя в "ленивый" (с помощью вопросительного знака) упрощает задачу, которую должен выполнить Perl, поэтому рекомендуем хорошо изучить этот метод.

Круглые скобки как способ запоминания

Следующая групповая операция пара круглых скобок, в которую заключается часть образца. При совпадении с образцом никаких изменений не происходит, просто совпавшая часть строки запоминается, и к ней можно впоследствии обращаться. Например, (а) продолжает соответствовать букве а, а ([a-z] ) ≈ любой строчной букве.

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

/fred(.)barney\l/;

соответствует строке, состоящей из слова fred, любого символа, кроме символа новой строки, слова barney и еще одного такого же символа. Таким образом, данному образцу соответствует последовательность символов fredxbarneyx, a не fredxbarneyy. Сравните это с

/fred.barney./;

где два обозначенных точками символа могут быть одинаковыми или разными; роли это не играет.

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

* На самом деле для поиска буквы с в первой позиции понадобится больший объем поиска с возвратом в операции *, но описание этого процесса не представляет интереса, а работает он по такому же принципу.

то вторая часть (считая левые круглые скобки слева направо) обозначается как \2, третья как \3 и т. д. Например,

/a(.)b(.)c\2d\l/;

обозначает а, какой-то символ (назовем его #1), b, еще один символ (назовем его #2), с, символ #2, d и символ #1. Таким образом, этот образец соответствует, в частности, строке axbycydx.

Запоминаемая часть может состоять не только из одного символа. Например,

/а(.*)Ь\1с/;

обозначает а, любое количество символов (даже нуль), b, ту же последовательность символов и, наконец, с. Следовательно, этот образец совпадет со строкой aFREDbFREDc и даже со строкой abc, но не со строкой аХХЬХХХс.

Дизъюнкция

Следующая групповая конструкциядизъюнкция, т.е. а | b | с. Это значит, что данный образец соответствует только одному из указанных вариантов (в данном случае а, b или с). Такая конструкция работает даже в том случае, если варианты содержат несколько символов, как в образце /song | blue/, что соответствует либо song, либо blue. (Для односимвольных альтернатив определенно лучше будет использовать класс символов, например, / [ abc ] /.)

Что, если бы мы хотели найти songbird или bluebird? Мы могли бы написать /songbird | bluebird/, но часть bird не хотелось бы указывать дважды. Из такой ситуации есть выход, однако вначале нам следует поговорить о приоритете группирующих образцов, который рассматривается ниже, в разделе "Приоритет".

Фиксирование образцов

Некоторые особые виды записи позволяют фиксировать образец относительно позиции в строке, в которой ищется соответствие. Обычно при сопоставлении образец "перемещается" по строке слева направо; сообщение о совпадении дается при первой же возможности. Фиксирующие точки позволяют гарантировать, что с образцом совпадают определенные части сравниваемой строки.

Первая пара фиксирующих директив требует, чтобы определенная часть символов, соответствующих образцу, была расположена либо на границе слова, либо не на границе слова. Фиксирующая директива \Ь требует, чтобы совпадение с образцом b происходило только на границе слова. Граница слова это место между символами, которые соответствуют предопределенным классам \w или \w, либо между символами, которые соответствуют классу \w, а также начало или окончание строки. Отметим, что все это больше предназначено для работы с С, а не с английскими словами, но вполне применимо и к словам. Например:

/fred\b/; # соответствует слову fred, но не Frederick /\bmo/; # соответствует словам тое и mole, но не Eimo /\bFred\b/; # соответствует слову Fred, но не Frederick или alFred /\b\+\b/; # соответствует "х+у", но не "++" или " + " /abc/bdef/; # никогда не дает совпадения(границы там быть не может)

Аналогичным образом \в требует, чтобы в указанной точке границы слова не было. Например:

/\bFred\B/; # соответствует "Frederick", но не "Fred Flintstone"

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

Символ $, как и л, фиксирует образец, но не по началу, а по концу строки. Другими словами, с$ соответствует символу с только в том случае, если он стоит в конце строки*. Знак доллара в любом другом месте образца, вероятно, будет интерпретироваться как представление скалярного значения, поэтому для того, чтобы использовать его в строке буквально, перед ним следует поставить обратную косую.

Поддерживаются и другие фиксирующие точки, включая \А, \2 и упреждающие фиксирующие точки, создаваемые с помощью комбинаций (?=...) и (?!...). Они подробно описаны в главе 2 книги Programming Perl и на man-странице perlre(Y).

Приоритет

Что произойдет, если объединить а | Ь*? Что будет отыскиваться любое количество символов а или Ь или один символ а и любое количество Ь?

Групповые и фиксированные образцы, как и операции, имеют приоритет. Приоритет образцов (от высшего к низшему) приведен в таблице 7.2.

Таблица 7.2. Приоритет групповых регулярных выражений**

Наименование

Обозначение

Круглые скобки Множители Последовательность и фиксация Дизъюнкция

( ) (?: ) ? + * {m,n} ?? +? *? (m,n}? abc л $ \Z (?= ) (?! )

 

* Или прямо перед символом новой строки в конце строки.

** Некоторые из этих символов в нашей книге не описываются. См. книгу Programming Perl или man-страницу perlreii(l).

Согласно этой таблице, специальный символ * имеет более высокий приоритет, чем |. В силу этого /а |Ь*/ интерпретируется как один символ а или любое число символов ь.

Что, если нам понадобится другое например, "любое число символов а или Ь"? В этом случае нужно просто использовать пару круглых скобок. В нашем примере в скобки нужно заключить ту часть выражения, к которой должна относиться *, т.е. (а|Ь)*. Если вы хотите подчеркнуть, какое выражение вычисляется первым, можно дать избыточные круглые скобки:

а (Ь*).

Изменение приоритета с помощью круглых скобок одновременно активизирует режим запоминания для данного образца, как мы рассказывали выше. То есть эти круглые скобки учитываются, когда вы определяете, соответствует ли какой-то элемент \2, \3 и т.д. Если вы хотите использовать круглые скобки без включения режима запоминания, применяйте форму (?:...), а не (...). Она тоже позволяет указывать множители, но не изменяет значение счетчика подлежащих запоминанию лексем, используя, например, переменную $4 и т.п. Например,/(?: Fred |Wilma) Flintstone/ ничего не записывает в переменную $ 1; здесь просто предполагается группирование.

Вот еще несколько примеров регулярных выражений и действия круглых скобок:

abc* # соответствует ab, abc, abcc, abccc, abcccc, и т.д. (abc)* # соответствует "", ab, abc, abcabc, abcabcabc, и т.д. ^х |у # соответствует х в начале строки или у в любом месте л^x.^y) # соответствует х или у в начале строки а|be Id # либо а, либо be, либо d (alb) Id) # ас, ad, be или bd (song|blue)bird # songbird или bluebird

Еще об операции сопоставления

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

Выбор другого объекта для сопоставления (операция :='∙)

Обычно строка, которую нужно сопоставить с образцом, не находится в переменной $_, и помещать ее туда довольно утомительно. (Может быть, в переменной $__ уже хранится значение, которое вам не хочется терять.) Ничего страшного здесь нам поможет операция =~. С ее помощью вы можете назначить для проведения операции сопоставления строку, хранящуюся в переменной, отличной от $_.

Эта переменная указывается справа от знака операции. Выглядит это так:

= "hello world";

$а =~ /^he/; # истина

$а =~ /(.)\1/; # тоже истина (соответствует двум 1)

if ($а =~ /(.)\1/) ( t истина, поэтому проводятся дальнейшие операции

1

Справа от знака операции =~ может стоять любое выражение, которое дает в результате некоторое скалярное строковое значение. Например, <stdin> при использовании в скалярном контексте дает скалярное строковое значение, поэтому, объединив эту операцию с операцией =~ и операцией сопоставления с регулярным выражением, мы получим компактную программу проверки входных данных:

print "any last request? ";

if (<STDIN> ==~ /л[y1}/) { # начинаются ли входные данные с буквы у? print "And just what might that request be? ";

# и чтобы это мог быть за запрос? <STDIN>; # получить строку со стандартного ввода print "Sorry, I'm unable to do that.\n";

# прошу прощения, но я не могу этого сделать )

В данном случае при помощи <stdin> берется очередная строка со стандартного ввода, которая затем сразу же используется как строка, сопоставляемая с образцом л [ yY ]. Отметим, что мы не сохраняли входные данные в переменной, поэтому если мы захотим сопоставить эти данные с другим образцом или же вывести их в сообщении об ошибке, то у нас ничего не выйдет. Тем не менее эта форма часто оказывается удобной.

Игнорирование регистра

В предыдущем примере мы указывали образец [yY] для обозначения строчной и прописной буквы у. Если речь идет об очень коротких строках, например, у или fred, то данный способ обозначения достаточно удобен, скажем, [fF] [rR] [eE] [dD]. А что делать, если сопоставляемая строкаэто слово procedure в нижнем или верхнем регистре?

В некоторых версиях grep флаг -i означает "игнорировать регистр". В Perl тоже есть такая опция. Чтобы ею воспользоваться, нужно добавить строчную i к закрывающей косой черте, т.е. написать / образец/i. Такая запись говорит о том, что буквы образца будут соответствовать буквам строки в любом регистре. Например, чтобы найти слово procedure в любом регистре, стоящее в начале строки, запишите /^procedure/i.

Теперь наш предыдущий пример будет выглядеть так:

print "any last request? ";

if (<STDIN> =~ /"y/i) { # начинаются ли входные данные с буквы у? # да! выполнить какие-то операции

}

Использование другого разделителя

Чтобы найти строку, которая содержит несколько косых (/), в соответствующем регулярном выражении нужно перед каждой из них поставить обратную косую черту (\). Например, чтобы найти строку, которая начинается с названия директории /usr/etc, нужно записать:

$path = <STDIN>; # прочитать путевое имя (вероятно, из find?) if ($path =~ /"VusrVetc/) {

# начинается с /usr/etc... }

Как видите, комбинация "обратная косая косая" создает между элементами текста своеобразные "проходы". Если косых очень много, это занятие может стать весьма утомительным, поэтому в Perl предусмотрена возможность использования другого разделителя (delimiter). Поставьте перед любым специальным символом* (выбранным вами в качестве разделителя) букву т, укажите свой образец и дайте еще один такой же разделитель:

/''∙VusrVetc/ # использование стандартного разделителя косой черты m@^/usr/etc@ # использование в качестве разделителя символа @ m#^/usr/etc# # использование в качестве разделителя символа # # (это мой любимый символ)

Если хотите, можете опять использовать косые, например, m/fred/. Таким образом, m ≈ общепринятое обозначение операции сопоставления с регулярным выражением, но если в качестве разделителя выбрана косая черта, то m не обязательна.

Использование интерполяции переменных

Перед тем как регулярное выражение рассматривается на предмет наличия специальных символов, в нем производится интерполяция переменных. Следовательно, регулярное выражение можно строить не только из литералов, но и из вычисляемых строк. Например:

$what = "bird";

$sentence = "Every good bird does fly.";

if ($sentence =~ /\b$what\b/) {

print "The sentence contains the word $what!\n";

>

Здесь мы использовали ссылку на переменную для построения операции сопоставления с регулярным выражением \bbird\b/.

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

Вот несколько более сложный пример:

$sentence = "Every good bird does fly.";

print "What should I look for? ";

$what = <STDIN>;

chomp($what) ;

if ($sentence =~ /$what/) ( # нашли! print "I saw $what in $sentence.\n";

} else (

print "nope... didn't find it.\n";

)

Если вы введете слово bird, оно будет найдено, а если слово scream ≈ не будет. Если ввести [bw] ird, результаты поиска тоже будут успешными. Это говорит о том, что квадратные скобки в данном случае воспринимаются как символы сопоставления с образцом.

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

$what = "[box]";

foreach (qw(in([box] out [box] white [sox] ) ) { if (/\Q$what\E/) {

print "$_ matched!\n";

1 }

Здесь конструкция \Q$what\E превращается в \[box\], в результате чего операция сопоставления ищет пару квадратных скобок, а не рассматривает всю конструкцию как класс символов.

Специальные переменные, защищенные от записи

После успешного сопоставления с образцом переменным $1, $2, $3 и т.д. присваиваются те же значения, что и \1, \2,\3 и т.д. Это можно использовать для поиска соответствия в последующем коде. Например:

$_ = "this is a test";

/(\w+)\W+(\w+)/; # сопоставление первых двух слов

# $1 теперь содержит this, а $2 ≈ is

Доступ к тем же значениям ($1, $2, $3 и т.д.) можно также получить, использовав операцию сопоставления для соответствующих списков. Если результаты сопоставления окажутся положительными, будет получен список значений от $1 до $п (где n ≈ количество занесенных в память элементов). В противном случае значения не определены. Запишем последний пример по-другому:

$_ = "this is a test";

($first, $second) = /(\w+)\W+(\w+)/; # сопоставление первых двух слов # $first теперь содержит this, a $second - is

К другим предопределенным защищенным от записи переменным относятся: $& (часть строки, совпавшая с регулярным выражением); $' (часть строки, стоящая перед совпавшей частью); $ ' (часть строки, стоящая после совпавшей части). Например:

$_ = "this is a sample string";

/sa.*le/; # соответствует слову sample внутри строки

# $' теперь содержит "this is a "

# $& теперь содержит "sample"

# $' теперь содержит "string"

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

Операция замены

Мы уже говорили о простейшей форме операции замены: s/ регуляр-ное_выражение/новая_строка/. Пора рассмотреть несколько разновидностей этой операции.

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

$_ = "foot fool buffoon";

s/foo/bar/g; # $_ теперь содержит "bart barl bufbarn"

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

$_ = "hello, world";

$new = "goodbye";

s/hello/$new/; # заменяет hello на goodbye

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

$_ = "this is a test";

s/(\w+()/<$l>/g; # $_ теперь содержит "<this> <is> <a> <test>"

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

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

* О влиянии этих переменных на производительность рассказывается в книге Mastering Regular Expressions (издательство O'Reilly).

г ; Как и в операции сопоставления, можно выбрать другой разделитель, если косая черта неудобна. Для этого просто нужно использовать один символ три раза*:

s#fred#barney#; # заменить fred на barney, как в s/fred/barney/

Как и при сопоставлении, можно с помощью операции =~ указать другой объект для проведения замены. В этом случае объект должен быть таким, которому можно присвоить скалярное значение, например, скалярной переменной или элементом массива:

$which = "this is a test";

$which =~ s/test/quiz/; # $which теперь содержит "this is a quiz"

$someplace[$here] =~ s/left/right/; # заменить элемент массива

$d{"t") =~ s/^/x /; # поставить "х " перед элементом массива

Функции split и join

Регулярные выражения можно использовать для разбивки строки на поля. Это делает функция split. Функция join выполняет противоположное действие вновь "склеивает" эти кусочки.

Функция split

Функция split получает регулярное выражение и строку и ищет в этой строке все экземпляры указанного регулярного выражения. Те части строки, которые не совпадают с регулярным выражением, возвращаются по порядку как список значений. Вот, например, код синтаксического анализа разделенных двоеточиями полей, аналогичных тем, которые используются в UNIX-файлах /etc/passwd:

$line = "merlyn::118:10:Randal:/home/merlyn:/usr/bin/peri";

@fields = split (/:/,$line); # разбить $line, используя в качестве t разделителя двоеточие

# теперь @fields содержит ("merlyn","","118","10",

# "Randal","/home/merlyn","/usr/bin/peri")

Обратите внимание на то, что второе пустое поле стало пустой строкой. Если вы этого не хотите, задайте сопоставление следующим образом:

Sfields = split(/:+/, $line);

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

Очень часто приходится разбивать на поля значение переменной $_, поэтому этот случай предлагается по умолчанию:

$ = "some string";

Swords = split (/ /); # то же самое, что и Swords = split(/ /, $_); *

# Или две пары, если используется символ из пары "левая-правая".

При такой разбивке соседние пробелы в разбиваемой строке вызовут появление пустых полей (пустых строк). Лучше использовать образец / +/, а лучше /\s+/, который соответствует одному и более пробельным символам. Этот образец, по сути дела, используется по умолчанию*, поэтому, если вы разбиваете переменную $_ по пробельным символам, вы можете использовать все стандартные значения и просто написать :

Swords = split; # то же самое, что и (Swords = split(/\s+/, $_) ;

Завершающие строки пустые поля в список, как правило, не включаются. Особой роли это обычно не играет. Решение вроде

$line = "merlyn::118:10:Randal:/home/merlyn:";

($name,$password,$uid,$gid,$gcos,$home,$shell) = split(/:/,$line);

# разбить $line, используя в качестве разделителя двоеточие

просто присваивает переменной $shell нулевое значение (undef), если эта строка недостаточно длинна или содержит в последнем поле пустые значения. (Разбиение выполняется так, что лишние поля просто игнорируются.)

Функция join

Функция join берет список значений и "склеивает" их, ставя между элементами списка строку-связку. Выглядит это так:

$bigstring = join($glue,@list);

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

$outline = join(":", @fields) ;

Отметим, что строка-связка это не регулярное выражение, а обычная строка, состоящая из символов общим числом нуль или более.

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

$result = (join "+", "", @fields);

Здесь пустая строка "" рассматривается как пустой элемент, который должен быть связан с первым элементом данных массива @fields.B результате связка помещается перед каждым элементом. Аналогичным образом можно поставить пустой элемент-связку в конец списка:

$output = join ("\n", @data, "");

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

Упражнения

'Ответы к упражнениям даны в приложении А.

1. Постройте регулярное выражение, которое соответствует:

а) минимум одному символу а, за которым следует любое число символов Ь;

б) любому числу обратных косых, за которым следует любое число звездочек (любое число может быть и нулем);

в) трем стоящим подряд копиям того, что содержится в переменной

$whatever;

г) любым пяти символам, включая символ новой строки;

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

2. а) Напишите программу, которая принимает список слов из stdin и ищет строку, содержащую все пять гласных (a,e,i,o ии). Запустите эту программу с /usr/dict/words* и посмотрите, что получится. Другими словами, введите

$ программа </usr/dict/words

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

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

3. Напишите программу, которая просматривает файл /etc/passwcf* (из stdin), выводя на экран регистрационное имя и реальное имя каждого пользователя. (Совет: с помощью функции split разбейте строку на поля, а затем с помощью sill избавьтесь от тех частей поля comment, которые стоят после первой запятой.)

4. Напишите программу, которая просматривает файл /etc/passwd (из stdin) на предмет наличия двух пользователей с одинаковыми именами и выводит эти имена. (Совет: после извлечения первого имени создайте хеш с этим именем в качестве ключа и числом его экземпляров в качестве значения. Прочитав последнюю строку stdin, ищите в этом хеше счетчики с показанием больше единицы.)

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

* Словарь вашей системы может находиться не в каталоге /usr/dict/words; обратитесь к man-странице spell(l).

** Если используется NIS, то файл /etc/passwd в вашей системе будет содержать мало данных. Посмотрите, может быть, ypcat passwd даст больше информации.




|     Назад     |     Вперед     |


| Содержание | Предисловие | Введение | Ссылки
| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10
| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19
| Приложение А | Приложение Б | Приложение В | Приложение Г |