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

Урок 7. Использование подвыражений

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

Понятие подвыражения

Соответствие нескольким вхождениям символа было введено в уроке 5, "Повторение совпадений", где указывалось, что \d+ соответствует одной или нескольким цифрам, a http?:// соответствует http:// или https://.

В обоих этих примерах (а на самом деле во всех примерах к настоящему времени) метасимволы повторения (? или * или {2}, например) относятся к предыдущему символу или метасимволу.

Например, разработчики HTML-документов часто размещают неразрывные пробелы между словами, чтобы гарантировать, что текст не будет разорван на строки между такими словами. Предположим, что в HTML-документе нужно найти местонахождение всех повторений неразрывных пробелов (чтобы заменить их кое-чем другим). Boт пример:

Текст

Hello, my name is Ben Forta, and I am the author of 
books on SQL, ColdFusion, WAP, Windows  2000, and 
other  subjects.

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

 {2,}

Результат

Hello, my name is Ben Forta, and I am the author of 
books on SQL, ColdFusion, WAP, Windows  2000, and 
other  subjects.

  в HTML обозначает неразрывный пробел. Шаблон  {2,} должен был найти 2 или больше вхождений  . Но он этого не сделал. Почему? Потому что {2,} определяет количество повторений того символа, который непосредственно предшествует ему, в данном случае ему предшествует точка с запятой.  ;;;; было бы найдено, в отличие от   .

Группировка подвыражений

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

Замечание

Скобки ( и ) — метасимволы. Чтобы найти символы ( и ), их нужно защитить, т.е. задать как \( и \).

Чтобы продемонстрировать использование подвыражений, давайте повторно рассмотрим предыдущий пример:

Текст

Hello, my name is Ben Forta, and I am the author of 
books on SQL, ColdFusion, WAP, Windows  2000, and 
other  subjects.

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

( ){2,}

Результат

Hello, my name is Ben Forta, and I am the author of 
books on SQL, ColdFusion, WAP, Windows  2000, and 
other  subjects.

Подвыражение ( ) обрабатывается как единый объект. И потому диапазон {2,}, который следует после него, относится ко всему подвыражению (а не только к точке с запятой). Этим мы достигли цели.

Вот еще один пример. На сей раз регулярное выражение используется, чтобы определить местонахождение IP-адресов. IP-адрес — это четыре числа, разделенных точками. Вот пример IP-адреса: 12.159.46.200. Поскольку каждое из чисел может быть одно-, двух- или трехцифровым, шаблон, соответствующий каждому числу, может быть записан как \d{1,3}. Это показано в следующем примере:

Текст

Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:

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

\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}

Результат

Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:

Каждому шаблону \d{1,3} соответствует одно из чисел в IP-адресе. Эти четыре числа разделены точками ., которые защищены, и потому заданы как \..

Шаблон \d{1,3}\. (до 3 цифр, после которых следует точка .) повторен три раза и потому также может быть записан с помощью повторения. Ниже приведена альтернативная версия того же примера:

Текст

Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:

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

(\d{1,3}\.){3}\d{1,3}

Результат

Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:

Этот шаблон работал точно так же, как и предыдущий, но его синтаксис немного отличается. Выражение \d{1,3}\. заключено в скобки ( и ), поэтому оно рассматривается как подвыражение. Выражение (\d{1,3}\.){3} повторяет подвыражение 3 раза (для первых трех чисел в IP-адресе), а затем в нем следует \d{1,3}, которому соответствует последнее, заключительное число.

Замечание

Шаблон (\d{1,3}\.){4} для IP-адресов не годится. Вы можете догадаться, почему?

Замечание

Некоторые пользователи любят заключать части выражений в скобки, точно так же, как и подвыражения, потому что это повышает удобочитаемость; предыдущий шаблон мог бы быть записан как {\d{1,3}\.){3}(\d{1,3}). Это совершенно законный прием, и дополнительные скобки не оказывают никакого влияния на фактическое поведение выражения (хотя в зависимости от используемой реализации регулярных выражений скобки могут оказывать влияние на быстродействие).

Использование подвыражений для группировки крайне важно. Рассмотрим еще один пример, в котором повторений нет вообще. В этом примере мы попытаемся найти год в записи о пользователе:

Текст

ID: 042
SEX: M
DOB: 1967-08-17
Status: Active

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

19|20\d{2}

Результат

ID: 042
SEX: M
DOB: 1967-08-17
Status: Active

В этом примере шаблон должен был определить местонахождение года (четырехразрядное число), но для большей точности первые две цифры были указаны явно как 9 и 20. Как объяснено в уроке 3, "Соответствие набору символов", | — оператор OR (ИЛИ), и так как 19|20 соответствует или 19 или 20, то шаблон 19|20\d{2} должен соответствовать любому четырехразрядному числу, начинающемуся с 19 или 20 (т.е. числу, в котором число сотен равно 19 или 20, а за ними следует две цифры). Очевидно, это не сработало. Почему? Оператор | смотрит на то, что находится у него слева, и на то, что находится у него справа, и читает шаблон 19|20\d{2} как "19 или 20\d{2}" (причем думает, что \d{2} является частью выражения, которое начинается с 20). Другими словами, он соответствует числу 19 или любому четырехразрядному году, начинающемуся с 20. Поэтому число 19 было найдено.

Решение состоит в том, чтобы сгруппировать 19|20 и рассматривать его как подвыражение, тогда шаблон будет выглядеть следующим образом:

Текст

ID: 042
SEX: M
DOB: 1967-08-17
Status: Active

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

(19|20)\d{2}

Результат

ID: 042
SEX: M
DOB: 1967-08-17
Status: Active

Если оператор | поместить в подвыражение, то он будет знать, что требуется найти один из вариантов в группе. Таким образом, шаблон (19|20)\d{2} найдет 1967 и вообще он соответствует любым четырем цифрам, начинающимся с 19 или 20. Для более поздних дат (для настоящего столетия) код нужно изменить, чтобы он соответствовал также годам, начинающимся с 21. Для этого шаблон нужно заменить на (19|20|21)\d{2}.

Замечание

В этом уроке описано использование подвыражений для группировки, однако есть еще одно чрезвычайно важное применение подвыражений. Оно описано в уроке 8, "Использование ссылок назад".

Вложение подвыражений

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

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

Чтобы продемонстрировать использование вложенных подвыражений, мы снова рассмотрим пример с IP-адресами. Вот использованный ранее шаблон (подвыражение повторено три раза, а за ним в заключение следует число):

(\d{1,3}\.){3}\d{1,3}

Так что же неправильно в этом шаблоне? Синтаксически все правильно. IP-адрес действительно составлен из четырех чисел; каждое из них содержит от одной до трех цифр, причем числа отделены друг от друга точками. Данный шаблон будет соответствовать любому правильному (допустимому) IP-адресу. Но он будет соответствовать не только правильным IP-адресам — недопустимые IP-адреса также будут соответствовать ему.

IP-адрес составлен из 4 байт, и потому IP-адрес, например 12.159.46.200, является представлением 4 байт. Поэтому значения этих четырех чисел из IP-адреса могут быть записаны в одном байте, а потому они находятся в диапазоне от 0 до 255. Это означает, что ни одно из чисел в IP-адресе не может быть больше, чем 255. А наш шаблон допускает также и другие числа (например, 345, 700 и 999), которые недопустимы в IP-адресах.

Замечание

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

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

Можно ли записать нужное регулярное выражение? В некоторых случаях можно. Чтобы создать нужное регулярное выражение, необходимо точно определить все те цепочки, которым оно должно соответствовать, и все те цепочки, которым оно не должно соответствовать. Ниже приведены правила, определяющие правильные (допустимые) комбинации в каждом числе IP-адреса:

  • Любое одноцифровое или двухцифровое число.
  • Любое трехцифровое число, начинающееся с 1.
  • Любое трехцифровое число, начинающееся с 2, если вторая цифра находится в диапазоне от 0 до 4.
  • Любое трехцифровое число, начинающееся с 25, если третья цифра находится в диапазоне от 0 до 5.

Если последовательно придерживаться этих правил, становится ясно, что действительно есть шаблон, который может найти все IP-адреса и только их. Вот пример:

Текст

Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:

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

(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}
((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))

Результат

Pinging hog.forta.com [12.159.46.200] with 32 bytes of data:

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

(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.)

Подвыражение (\d{1,2}) находит любое число в диапазоне от 0 до 99. Подвыражение (1\d{2}) соответствует любому трехзначному числу, начинающемуся с 1 (после 1 следуют любые две цифры), т.е. любому числу в диапазоне от 100 до 199. Подвыражение (2[0-4]\d) находит числа от 200 до 249, а (25[0-5]) находит числа от 250 до 255. Каждое из этих подвыражений является операндом другого подвыражения, составленного из этих подвыражений-операндов с помощью знака операции |. Поэтому будет выполняться поиск числа, соответствующего одному из этих подвыражений, а не всем. После диапазона чисел следует шаблон \., поэтому будет выполняться поиск точки. Так что полученное подвыражение соответствует всей последовательности цифр и точке (всему числу и точке после него). Это подвыражение заключено в скобки, т.е. само рассматривается как часть подвыражения, которое повторяется три раза с помощью {3}. В конце диапазон чисел повторяется снова, но на этот раз после него нет шаблона \.; этот диапазон как раз и соответствует последнему числу в IP-адресе. Именно благодаря тому, что каждое из четырех чисел находится в диапазоне от 0 до 255, этот шаблон действительно находит все правильные IP-адреса и отвергает все ошибочные.

Замечание

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

Резюме

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