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

Урок 5. Повторение совпадений

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

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

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

text@text.text

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

\w@\w\.\w

Метасимвол \w соответствует всем алфавитно-цифровым символам (плюс символ подчеркивания, который является допустимым в адресе электронной почты). В отличие от точки . символ @ защищать не нужно.

Это — совершенно правильное регулярное выражение, хотя и довольно бесполезное. Оно соответствует адресу электронной почты вроде a@b.c (который, хотя и является синтаксически правильным, очевидно, не может быть правильным (допустимым) адресом на практике). Проблема в том, что \w соответствует одному отдельному символу, а вы не можете заранее знать, сколько символов предшествует @. Приведенные ниже адреса электронной почты являются правильными (допустимыми), но они все имеют разное число символов перед @:

b@forta.com 
ben@forta.com 
bforta@forta.com

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

Соответствие с одним или несколькими символами

Чтобы установить соответствие символа (или набора) шаблона с одним или несколькими символами, просто добавьте в конец шаблона символ +. Символ + устанавливает соответствие с одним или несколькими символами (по крайней мере с одним символом; с нулем символов он соответствие не устанавливает). Принимая во внимание, что a соответствует a, выражение a+ соответствует одному или нескольким вхождениям a. Точно так же, учитывая, что диапазон [0-9] соответствует любой цифре, [0-9]+ соответствует последовательности, состоящей из одной или нескольких цифр.

Замечание

Когда + означает повторение набора, + должен быть помещен вне набора. Поэтому выражение [0-9]+ является правильным и ему может соответствовать непустая последовательность цифр, тогда как выражение [0-9+], хотя и является синтаксически правильным, имеет совсем другое назначение. На самом деле [0-9+] — правильное (допустимое) регулярное выражение, но ему не будет соответствовать последовательность длиной более одной цифры. Это выражение определяет набор от 0 до 9 и символ +; поэтому ему будут соответствовать любая единственная цифра или знак "плюс". Хотя это выражение синтаксически правильно, означает оно совсем не то, что [0-9]+.

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

Текст

Send personal email to ben@forta.com. For questions about 
a book use support@forta.com. Feel free to send unsolicited 
email to spam@forta.com (wouldn't it be nice if it were 
that simple, huh?).

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

\w+@\w+\.\w+

Результат

Send personal email to ben@forta.com. For questions about 
a book use support@forta.com. Feel free to send unsolicited 
email to spam@forta.com (wouldn't it be nice if it were 
that simple, huh?).

Шаблон в точности соответствует всем трем адресам. Регулярное выражение с помощью \w+ сначала находит один или несколько алфавитно-цифровых символов. Затем устанавливается соответствие с @, после чего снова используется \w+, чтобы установить соответствие с одним или несколькими символами, следующими за @. Затем устанавливается соответствие с точкой . (используется защищенная точка \.) и еще один шаблон \w+, чтобы установить соответствие с концом адреса.

Замечание

+ — метасимвол. Чтобы найти +, его надо защитить, т.е. задать как \+.

Плюс + может также использоваться для того, чтобы установить соответствие с одним или несколькими наборами символов. Чтобы продемонстрировать это, в следующем примере применяется то же самое регулярное выражение, но текст немного отличается:

Текст

Send personal email to ben@forta.com or ben.forta@forta.com.   
For questions about a book use support@forta.com.  If your 
message is urgent try ben@urgent.forta.com. Feel free to   
send unsolicited email to spam@forta.com (wouldn't it be nice  
if it were that simple, huh?).

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

\w+@\w+\.\w+

Результат

Send personal email to ben@forta.com or ben.forta@forta.com.   
For questions about a book use support@forta.com.  If your 
message is urgent try ben@urgent.forta.com. Feel free to   
send unsolicited email to spam@forta.com (wouldn't it be nice  
if it were that simple, huh?).

Регулярное выражение соответствовало пяти адресам, но два из них охвачены не полностью. Почему так получилось? Потому что в \w+@\w+\.\w+ ничего не предусмотрено для символов . перед @, и потому это выражение допускает только одну точку ., отделяющую две строки после @. Хотя ben.forta@forta.com — совершенно законный адрес электронной почты, регулярное выражение найдет только forta (вместо ben.forta), потому что \w соответствует алфавитно-цифровым символам, но не точке . в середине строки текста.

Здесь нужно установить соответствие с \w или точкой ., т.е. с набором [\w\.]. Ниже приведен тот же пример с пересмотренным шаблоном:

Текст

Send personal email to ben@forta.com or ben.forta@forta.com.   
For questions about a book use support@forta.com.  If your 
message is urgent try ben@urgent.forta.com. Feel free to   
send unsolicited email to spam@forta.com (wouldn't it be nice  
if it were that simple, huh?).

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

[\w.]+@[\w.]+\.\w+

Результат

Send personal email to ben@forta.com or ben.forta@forta.com.   
For questions about a book use support@forta.com.  If your 
message is urgent try ben@urgent.forta.com. Feel free to   
send unsolicited email to spam@forta.com (wouldn't it be nice  
if it were that simple, huh?).

Этим, казалось бы, мы достигли цели. Выражение [\w.]+ соответствует одному или нескольким вхождениям любого алфавитно-цифрового символа, символа подчеркивания и точки ., и потому вхождение ben.forta будет найдено. Выражение [\w.]+ также используется для строки после @, так что будут установлены соответствия с именами (названиями) более глубоких доменов (или с именем (названием) главного компьютера).

Обратите внимание, что для заключительного соответствия использовалось выражение \w+, а не [\w.]+. Попытайтесь объяснить, почему? Используйте [\w.] в качестве заключительного шаблона и выясните, что случится со вторым, третьим и четвертым совпадением.

Замечание

Обратите внимание, что точка . в наборе не защищена, и все равно она каким-то образом соответствует . (она была обработана как буквальный символ, а не как метасимвол). Вообще, метасимволы типа . и + рассматриваются как буквальный текст, когда используются в наборах, и поэтому защищать их не нужно. Однако если защитить их, это не причинит вреда. Выражение [\w.] функционально эквивалентно [\w\.].

Поиск нуля или большего количества символов

Плюс + соответствует вхождению одного или нескольких символов. С отсутствующими символами (т.е. с нулевым количеством символов) соответствие установлено не будет, потому что должен быть по крайней мере один символ. Иными словами, в случае использования метасимвола + для установления соответствия необходимо не менее одного символа. Но что же делать, если нужно установить соответствие с необязательными символами, т.е. с такими символами, которые могут отсутствовать вообще (иными словами, с символами, количество которых может равняться нулю)?

Чтобы сделать это, используйте метасимвол *. Метасимвол * используется в точности так, как +; он записывается сразу после символа или набора и будет соответствовать нулю или большему количеству вхождений символа или шаблона. Поэтому шаблон В.* Forta соответствует B Forta, B. Forta, Ben Forta и, конечно же, и другим комбинациям.

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

Текст

Hello .ben@forta.com is my email address.

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

[\w.]+@[\w.]+\.\w+

Результат

Hello .ben@forta.com is my email address.

Вспомните, что [\w.]+ соответствует одному или нескольким вхождениям алфавитно-цифровых символов и точки ., и потому вхождение .ben было найдено. Однако в предыдущем тексте, очевидно, есть опечатка (лишняя точка в середине текста), но сейчас это к делу не относится. Гораздо большая проблема состоит в том, что хотя точка . является допустимым символом в адресе электронной почты, с нее не может начинаться адрес электронной почты, т.е. она не является допустимым символом в начале адреса электронной почты.

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

Текст

Hello .ben@forta.com is my email address.

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

\w+[\w.]*@[\w.]+\.\w+

Результат

Hello .ben@forta.com is my email address.

Этот шаблон выглядит довольно сложным (но фактически он таким не является). Давайте рассмотрим его вместе. Выражение \w+ соответствует любому алфавитно-цифровому символу, но не точке . (иными словами, он соответствует всем допустимым символам, с которых может начинаться адрес электронной почты). После начальных допустимых символов действительно может следовать точка . и дополнительные символы, хотя фактически они могут и не присутствовать. Выражение [\w.]* соответствует нулю или большему количеству вхождений точки . или алфавитно-цифровых символов, а именно это нам и было необходимо.

Замечание

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

Замечание

* является метасимволом. Чтобы установить соответствие со звездочкой * как с обычным символом, ее нужно защитить, т.е. задать как \*.

Соответствие с нулем вхождений или с вхождением одного символа

Есть еще один очень полезный метасимвол — ? (знак вопроса). Подобно +, знак вопроса ? отмечает необязательный текст (так что ему будет соответствовать и нулевое количество вхождений шаблона). Но в отличие от знака + знак вопроса ? соответствует только отсутствию вхождений или одному вхождению символа (или набора), но не более чем одному вхождению. Поэтому знак ? очень полезен тогда, когда нужно установить соответствие с одним конкретным необязательным символом в блоке текста.

Рассмотрим следующий пример:

Текст

The URL is http://www.forta.com/, to connect  
securely use https://www.forta.com/ instead.

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

http://[\w./]+

Результат

The URL is http://www.forta.com/, to connect  
securely use https://www.forta.com/ instead.

Чтобы найти URL, используется шаблон http:// (который является буквальным текстом и поэтому соответствует только себе); за ним следует шаблон [\w./]+, соответствующий одному или нескольким вхождениям набора, в котором допускаются алфавитно-цифровые символы, точка . и косая черта /. Этот шаблон может найти только первый URL (тот, который начинается с http://), но не второй, который начинается с https://. Причем s* (нуль или больше вхождений s) не является правильным шаблоном, потому что ему соответствовало бы и httpsssss:// (что явно не допустимо).

Решение? Используйте s? как показано в следующем примере:

Текст

The URL is http://www.forta.com/, to connect  
securely use https://www.forta.com/ instead.

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

https?://[\w./]+

Результат

The URL is http://www.forta.com/, to connect  
securely use https://www.forta.com/ instead.

Шаблон здесь начинается с https?://. Метасимвол ? указывает, что с предшествующим ему в шаблоне символом (s) должно быть установлено соответствие, если в тексте этого символа нет или если есть одно вхождение этого символа в текст. Другими словами, https?:// соответствует и http://, и https:// (но ничему иному).

Кстати, использование метасимвола ? решает также проблему, рассмотренную в предыдущем уроке. Вспомните пример, в котором использовался шаблон \r\n, чтобы установить соответствие с концом строки, и я упоминал, что в Unix и Linux нужно использовать \n (без \r) и что идеальное решение состояло бы в том, чтобы установить соответствие с необязательным \r, за которым следует \n. Рассмотрим этот пример снова, но на сей раз немного изменим регулярное выражение:

Текст

"101","Ben","Forta" 
"102","Jim","James"

"103","Roberta","Robertson"
"104","Bob","Bobson"

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

[\r]?\n[\r]?\n

Результат

"101","Ben","Forta" 
"102","Jim","James" 
                    
"103","Roberta","Robertson"
"104","Bob","Bobson"

Выражение [\r]?\n соответствует одному необязательному вхождению \r, за которым обязательно следует \n.

Замечание

Обратите внимание, что здесь использовано регулярное выражение [\r]?, а не \r?. Выражение [\r] определяет набор, содержащий единственный метасимвол, т.е. одноэлементный набор, поэтому [\r]? фактически функционально идентично \r?. Квадратные скобки [ ] обычно используются, чтобы определить набор символов, но некоторые разработчики любят заключать в них даже единственный символ, чтобы предотвратить всякую двусмысленность (выделив шаблон, к которому применяется следующий метасимвол). Если вы используете и [ ], и ?, удостоверьтесь, что ? поместили вне набора. В соответствии с этими правилами правильно писать http[s]?://, a не http[s?]://.

Замечание

Знак вопроса ? является метасимволом. Чтобы найти ?, нужно защитить его, т.е. задать как \?.

Использование интервалов

Метасимволы +, * и ? помогают решить много проблем с регулярными выражениями, но иногда этих знаков недостаточно. Учтите следующее:

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

Чтобы решить эти проблемы и обеспечивать большую свободу управления повторением совпадений, в регулярных выражениях допускается использование интервалов. Интервалы определяются между символами { и }.

Замечание

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

Указание точного количества совпадений

Чтобы определить точное количество совпадений, число совпадений указывают между фигурными скобками { и }. Поэтому {3} означает поиск соответствий с тремя экземплярами предыдущего символа или набора. Если есть только 2 вхождения шаблона, соответствие установлено не будет.

Чтобы продемонстрировать это, давайте повторно рассмотрим пример с RGB-значениями (использованный в уроках 3, "Соответствие набору символов", и 4, "Использование метасимволов"). Как вы помните, RGB-значения определяются как три набора шестнадцатеричных чисел (причем каждый набор состоит из 2 символов). Первый шаблон для поиска RGB-значения имел следующий вид:

#[0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f]

В уроке 4, "Использование метасимволов", мы имели дело с классом POSIX и шаблон выглядел так:

#[[:xdigit:]][[:xdigit:]][[:xdigit:]][[:xdigit:] ][[ixdigit:]][[:xdigit:]]

Проблема с обоими шаблонами состоит в том, что приходится повторять один и тот же набор символов (или класс) шесть раз. Теперь в том же самом примере используем интервал для установления соответствия:

Текст

<BODY BGCOLOR="#336633" TEXT="#FFFFFF" 
    MARGINWIDTH="0" MARGINHEIGHT="0"
    TOPMARGIN="0" LEFTMARGIN="0">

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

#[[:xdigit:]]{6}

Результат

<BODY BGCOLOR="#336633" TEXT="#FFFFFF" 
    MARGINWIDTH="0" MARGINHEIGHT="0"
    TOPMARGIN="0" LEFTMARGIN="0">

[:xdigit:] соответствует шестнадцатеричному символу, а {6} повторяет класс POSIX шесть раз. Повторение работает точно так же в выражении

#[0-9A-Fa-f]{6}

Установление соответствия в случае интервала-диапазона

Чтобы определить диапазон количества вхождений (от минимального до максимального значения количества вхождений шаблона), могут использоваться также интервалы. Диапазоны определяются, например, так: {2,4}. (Этот диапазон задает 2 в качестве минимального значения для количества вхождений шаблона и 4 — в качестве максимального значения для количества вхождений шаблона). Пример применения диапазона — регулярное выражение, используемое для проверки правильности формата дат:

Текст

4/8/03 
10-6-2004 
2/2/2 
01-01-01

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

\d{l,2}[-\/]\d{l,2}[-\/]\d{2,4}

Результат

4/8/03 
10-6-2004 
2/2/2 
01-01-01

Перечисленные здесь даты — значения, которые пользователи могут вводить в качестве значений полей в формах; эти значения должны представлять собой правильно отформатированные даты. Шаблону \d{l,2} соответствует одна или две цифры (такая проверка используется для дня и месяца); \d{2,4} соответствует году; [-\/] соответствует - или / в качестве разделителя даты. Использованный шаблон нашел три даты, но не нашел 2/2/2 (потому что для года последовательность цифр слишком короткая).

Замечание

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

Обратите внимание, что предыдущий шаблон не проверяет правильность дат (недопустимые даты типа 54/67/9999 выдержали бы испытание) — он только проверяет правильность формата (шаг, обычно предпринимаемый непосредственно перед проверкой правильности дат).

Замечание

Интервалы могут начинаться с 0. Интервал {0,3} соответствует нулю, одному, двум или трем вхождениям шаблона.
Как отмечалось ранее, знак вопроса ? находит нуль совпадений или одно совпадение для шаблона, который предшествует этому знаку. Поэтому знак вопроса ? функционально эквивалентен интервалу {0,1}.

Соответствие в случае интервала типа "не менее"

Интервалы используются также для того, чтобы определить минимальное количество совпадений с шаблоном, не указывая при этом максимального. Синтаксис для этого типа интервала подобен синтаксису для диапазона, но в нем опущен максимум. Например, {3,} означает совпадение не менее чем с 3 вхождениями. Иными словами, здесь требуется 3 или больше совпадений.

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

Текст

1001: $496.80
1002: $1290.69
1003: $26.43
1004: $613.42
1005: $7.61
1006: $414.90
1007: $25.00

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

\d+: \$\d{3,}\.\d{2}

Результат

1001: $496.80
1002: $1290.69
1003: $26.43
1004: $613.42
1005:	$7.61
1006: $414.90
1007: $25.00

Предыдущий текст — сообщение, где указаны номера заказов, за которыми следует стоимость соответствующего заказа. Регулярное выражение сначала использует шаблон \d+:, чтобы найти порядковый номер заказа (если этот шаблон опустить, то, когда будет найдена стоимость, соответствие будет установлено только с ней, а не со всей строкой, включающей также и порядковый номер заказа). Чтобы установить соответствие со стоимостью, используется шаблон \$\d{3,}\.\d{2}. Выражение \$ соответствует $, \d{3,} соответствует числам, содержащим не менее трех цифр (и таким образом стоимости не менее 100$), \. соответствует ., и, наконец, \d{2} соответствует тем двум цифрам, которые следуют после десятичной точки. Весь шаблон правильно устанавливает соответствие с четырьмя из семи заказов.

Замечание

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

Замечание

Знак + функционально эквивалентен {1,}.

Предотвращение лишних соответствий

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

Все примеры к настоящему моменту были тщательно отобраны так, чтобы не столкнуться со слишком большим количеством соответствий. Но теперь пришло время рассмотреть следующий пример. Выбранный для примера текст является частью Web-страницы и содержит текст со встроенными HTML-тегами <B>. Регулярное выражение должно найти любой текст внутри тегов <B> (это может потребоваться при замене форматирования). Вот пример:

Текст

This offer is not available to customers living in <B>AK</B> and <B>HI</B>.

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

<[Bb]>.*</[Bb]>

Результат

This offer is not available to customers living in <B>AK</B> and <B>HI</B>.

Выражение <[Bb]> соответствует открывающему тегу <B> (на верхнем или нижнем регистре), а соответствует закрывающему тегу </B> (также на верхнем или нижнем регистре). Однако вместо двух совпадений было найдено только одно, причем .* соответствовало всему, что было расположено после первого <B> до последнего тега </B>, так что фактически был найден текст AK</B> and <B>HI. Найденный текст содержит текст, который нужно было найти, но помимо него он включает также и другие вхождения тегов.

Причина этого состоит в том, что метасимволы типа * и + являются жадными; т.е. они ищут самое длинное возможное соответствие, а не наименьшее. Из-за этого кажется, что соответствие начинается не с начала, а с конца текста и продолжается назад (т.е. в направлении к началу), пока не будет найдено следующее соответствие. Это делается преднамеренно — кванторы по природе своей жадны.

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

Таблица 5.1. Жадные и ленивые кванторы

Жадный Ленивый
* *?
+ +?
{n,} {n,}?

Квантор *? — ленивая версия *, так что давайте повторно рассмотрим наш пример, на сей раз используя *?:

Текст

This offer is not available to customers living in <B>AK</B> and <B>HI</B>.

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

<[Bb]>.*?</[Bb]>

Результат

This offer is not available to customers living in <B>AK</B> and <B>HI</B>.

Используя ленивый квантор *?, мы добились правильного результата. Первый раз с ним было сопоставлено только вхождение <B>AK</B>, и совершенно независимо от него было найдено вхождение <B>HI</B>.

Замечание

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

Резюме

Реальная мощь шаблонов из регулярных выражений становится очевидной именно при использовании повторения совпадений. В этом уроке введены кванторы + (соответствует одному или большему количеству совпадений), * (нуль или больше совпадений), ? (нуль или одно совпадение). Эти кванторы можно рассматривать как способы найти повторяющиеся совпадения. Для более тонкого управления могут использоваться интервалы, так как они позволяют определить точное количество повторений или же минимальное и максимальное их количество. Жадные кванторы могут найти слишком много соответствий; чтобы избежать этого, используйте ленивые кванторы.