Приведём несколько примеров регулярных выражений.
карова
— очевидно, шаблон, под который подходит слово
карова
;
\b(shift|unshift|pop|push|splice)\b
— любое из перечисленных
слов;
^\s+
— один или несколько пробелов или знаков табуляции,
стоящих в начале строки.
В регулярных выражениях алфавитно-цифровые символы обычно обозначают сами себя.
Например, шаблон Hello
указывает искать символ
H
, за которым следует e
, затем
l
и т. д.
Если символ трудно или неудобно задать буквально, можно использовать уже
известные нам литералы: \n
, \t
и другие.
Это означает, что знак \
, входящий в регулярное выражение,
уже не может обозначать сам себя, поскольку он меняет смысл следующего за ним
символа: в частности, буква n
, вместе с предшествующим
знаком бэкслэш обозначает символ конца строки. Если требуется включить в шаблон
знак \
как таковой, следует использовать литерал
\\
.
Имеются и другие символы, которые в шаблонах получают особый смысл вместо того,
чтобы обозначать самих себя. Такие символы называются
метасимволами. Приведём несколько
примеров метасимволов, не указывая пока их особый смысл (список не является
исчерпывающим): \.-[](){}?*+^$|
.
Некоторые символы оказываются метасимволами не всегда, а только тогда, когда попадают в определённый контекст. У некоторых метасимволов в зависимости от контекста оказывается разный смысл.
Если нужно вставить в регулярное выражение метасимвол, лишив его особого
смысла, его следует защитить (экранировать), поставив перед ним
бэкслэш. Например, знак плюса в регулярное выражение вставляется как
\+
.
Шаблон [abcdef]
обозначает один из символов, перечисленных
в квадратных скобках. Если, к примеру, нас интересует слово
Hello
, неважно, с большой или маленькой буквы, шаблон будет
таким: [Hh]ello
. Вот шаблон, обозначающий маленькую гласную
букву английского алфавита: [aeiouy]
. Ещё один пример —
символьный класс, состоящий из обеих квадратных скобок:
[\[\]]
.
Если символьный класс включает символы, идущие подряд в кодовой таблице,
достаточно указать первый и последний символы, вставив между ними дефис.
К примеру, класс, обозначающий любую десятичную цифру, можно задать как
[0-9]
. Буква английского алфавита обозначается как
[A-Za-z]
(здесь мы полагаемся на тот факт, что в любой
кодовой таблице заглавные и маленькие английские буквы идут непрерывными
блоками в алфавитном порядке; однако блок маленьких букв не следует сразу за
блоком заглавных).
Можно определить символьный класс, состоящий из всех символов за исключением
перечисленных — так называемое отрицание символьного
класса. Для этого сразу за открывающей квадратной
скобкой перед перечислением вставляется знак циркумфлекса ^
.
Любой символ, не являющийся цифрой, можно обозначить как
[^0-9]
.
Для некоторых популярных классов символов есть специальные обозначения:
символьный класс | назначение |
---|---|
. | любой символ (кроме символа конца строки) |
\s | пробельный символ, т. е. символ пробела или табуляции или конца строки или конца страницы |
\S | любой символ, кроме пробельного |
\d | десятичная цифра, то же, что
и [0-9] |
\D | нецифра, то же, что
и [^0-9] или [^\d] |
\w | символ, встречающийся в словах (алфавитно-цифровой или знак подчёркивания) |
\W | любой символ, не входящий в класс \w , т. е. не
алфавитно-цифровой и не знак подчёркивания. |
При успешном сопоставлении строки с регулярным выражением каждому символу или символьному классу в шаблоне соответствует какой-то символ в строке. Но имеются конструкции, которые обозначают не наличие определённого символа, а определённое (пустое) место в строке. Такие конструкции называются привязками, или анкерами.
Наиболее часто используемые анкеры — привязка к началу (^
)
и концу ($
) строки. Привязка к началу строки должна помещаться
в начале шаблона, а привязка к концу — в конце.
Например, к шаблону ^анти
подходят такие слова русского
языка, как антидот
, антисемитизм
или
античастица
. Без привязки также подошли бы строки, не
начанающиеся с «анти-», но содержащие это буквосочетание внутри, например,
меркантилизм
. Для поиска слов, заканчивающихся на «-ться»
нужен шаблон ться$
(мы уверены почти на 100 %, что все такие
слова — это возвратные глаголы в инфинитиве). Ничто не мешает применять
в шаблоне обе эти привязки.
Другой полезный анкер — привязка к границе между словами \b
.
Он соответствует месту в строке, находящемуся между символами, один из которых
принадлежит классу \w
, а другой — \W
(в любом порядке). Эта привязка может соответствовать также началу или концу
строки (в этом случае считается, что срока как бы окружена воображаемыми
символами из класса \W
).
Если мы ищем фрагмент, который должен подойти под один из нескольких шаблонов,
нужно перечислить эти шаблоны, разделив вертикальной чертой
|
. Например,
понедельник|вторник|среда|четверг|пятница|суббота|воскресенье
.
Для того, чтобы сделать список альтернатив самостоятельной единицей и отделить
от соседних, его нужно заключить в скобки. Например, шаблон
Уважаем(ый|ая)
означает строку Уважаем
,
за которой следует одна из строк ый
или
ая
. Без скобок шаблон Уважаемый|ая
обозначал бы одну из строк Уважаемый
или
ая
. У скобок имеется важное побочное действие, о котором
будет сказано в разделе «Группировка и захват».
Для того, чтобы указать, сколько раз может повторяться шаблон, после него ставят так называемые квантификаторы (от латинского слова quantum — сколько):
квантификатор | назначение |
---|---|
* | |
+ | |
? | или |
{} | ровно |
{,} | |
{,} | и |
Квантификаторы *
, +
и ?
являются избыточными, поскольку они могут быть выражены
иначе с помощью фигурных скобок. А именно, *
равносилен
{0,}
, +
равносилен
{1,}
, а ?
— то же самое, что
и {0,1}
. Но данные квантификаторы очень часто используются,
и этим заслужили отдельных обозначений.
Если шаблон, к которому применяется квантификатор, представляет из себя нечто более сложное, чем просто единичный символ или класс символов, его нужно заключить в круглые скобки.
Вот несколько примеров:
^\d+$
— последовательность из одной или нескольких десятичных
цифр (шаблон для целых неотрицательных чисел в десятичной записи);
^\-?\d+$
— то же самое, но для всех (возможно,
отрицательных) целых чисел;
^\-?(\d+(\.\d*)?|\.\d+)$
— шаблон для вещественных чисел;
Разберём последний пример подробнее. Помимо необязательного минуса в начале,
в шаблоне присутствует группа с двумя альтернативами:
\d+(\.\d*)?
и \.\d+
. Первая альтернатива
включает обязательную целую часть \d+
(как минимум одна
цифра), и следующую за ней необязательную дробную (\.\d*)?
.
В дробной части, если она есть, имеется десятичная точка, и, возможно,
несколько цифр. Таким образом, этой альтернативе соответствуют строки
15
, 15.
, 15.487
.
Другая альтернатива нужна для строк вида .618
с отсутствующей целой частью — во многих компьютерных языках эта запись имеет
право на существование.
Примечание | |
---|---|
Было бы ошибкой попытаться объединить эти альтернативы:
|
Если простейшие элементы регулярного выражения — символы, символьные классы
и анкеры, записываются подряд, это означает, что при поиске в строке по шаблону
эти элементы будут сопоставляться с частями строки последовательно, в той же
последовательности. Этот порядок нарушается, если применяются альтернативы.
Можно представлять себе, что составное регулярное выражение составляется из
простейших при помощи двух операций: последовательного соединения
(композиции) и альтернативы. Композиция — аналог операции
умножения в арифметике. Альтернатива — аналог сложения. Первое сходство
с арифметикой состоит в том, что операция альтернативы имеет более низкий
приоритет, чем композиция, поэтому могут потребоваться скобки для группировки,
как в примере Уважаем(ый|ая)
.
Примечание | |
---|---|
Многие, хотя и не все, законы арифметики действуют и для регулярных выражений:
В этой странной арифметике регулярных выражений не имеет место закон
коммутативности для композиции. Кроме того, отсутствует аналог нуля из-за
очевидного соотношения
Роль единицы (правой и левой) для композиции выполняет пустой шаблон (обозначим
его ):
Квантификаторы вида |
Помимо группировочной функции скобки выполняют функцию захвата. Главным результатом сопоставления строки с шаблоном является ответ на вопрос: подходит ли строка под шаблон? Но, кроме того, часто бывает нужно определить, какой фрагмент или фрагменты строки подошли к тем или иным фрагментам в регулярном выражении.
Рассмотрим пример, в котором в тексте отыскиваются упоминания различных кислот.
Наши школьные воспоминания из химии навели нас на мысль, что названия кислот
оканчиваются или на вая
, или на ная
, или
на тая
, а затем, после пробела, следует слово
кислота
. Составляем шаблон: \S+[внт]ая
кислота
. Сопоставляем с шаблоном текст. Удача! Но, спрашивается,
упоминание какой именно кислоты нашлось в тексте? Соляной? Серной? Азотной?
Плавиковой? Хлорной? Хлорноватой? Хлорноватистой? Лимонной? Синильной?
Дезоксирибонуклеиновой?
Вот здесь пригодится захват. Ту часть шаблона, который, по нашему замыслу,
должен соответствовать названию, заключим в скобки: (\S+[внт]ая)
кислота
. Тогда машина, найдя в тексте упоминание кислоты, сохранит её
название (то, что соответствует заключённому в скобки фрагменту шаблона)
в специальной переменной — буфере захвата.
Регулярное выражение может содержать несколько групп захвата. Такие группы могут не только следовать друг за другом, но и вкладываться одна в другую. Иными словами, регулярное выражение должно быть сбалансировано по отношению к круглым скобкам в том же смысле, какой обсуждался в главе 23. «Проверка баланса скобок» (конечно, это относится только к круглым скобкам, служащим цели группировки и захвата; скобки, которым предшествует бэкслэш, не сказываются на балансе групп). При успешном поиске каждая группа захватит какую-то часть текста: первая — в первый буфер, вторая — во второй, и так далее. Как же нумеруются группы в случае, когда они вложены друг в друга? Нумерация идёт в том порядке, в каком появляются открывающие скобки:
2 4 5 ┝┑ ┝┑┝┑ (()(()())) │ ┝━━━━┙│ │ 3 │ ┝━━━━━━━━┙ 1
При желании группу можно исключить из нумерации, то есть лишить её
«захватнической» функции, оставив только группирующую. Для этого вместо
ограничителей группы (⋯)
используем
(?:⋯)
. Здесь вопросительный знак
не обозначает квантификатор, поскольку квантификатору
должны предшествовать либо символ, либо символьный класс, либо группа.
Использование нумерованных групп захвата не всегда удобно, особенно в больших
регулярных выражениях. Стоит только вставить в шаблон новую группу захвата, как
нумерация сбивается. Тогда придётся во всех местах в программе, где происходит
обращение к буферам захвата по номерам, вносить исправления. Но можно связать
с группой имя, которое позволит обратиться к соответствующему буферу по этому
имени. Для создания именованной группы используются ограничители
(?<name>⋯)
, где вместо
name
подставляется нужное имя.
Захваченные в буферы части строки могут использоваться двумя путями. Во-первых, программа, использующая регулярное выражение для поиска или замены, может обратиться к буферам как к специальным переменным. О таком использовании речь пойдёт в разделе «Операторы поиска и замены». Вторая возможность предусматривает использование ссылок на группы прямо в регулярном выражении, см. раздел «Обратные ссылки».
Рассмотрим задачу поиска слов, содержащих три одинаковые гласных буквы подряд.
Наивное решение [аеёиоуэюя]{3}
, использующее квантификаторы,
не будет работать, поскольку такому шаблону соответствуют строки с тремя подряд
идущими гласными, но необязательно одинаковыми. Чудовищное решение с полным
перечислением альтернатив,
ааа|еее|ёёё|иии|ооо|ууу|эээ|ююю|яяя
, мы с негодованием
отвергаем: ведь стоит взять другой, более обширный символьный класс, или
заменить тройку в квантификаторе на большее значение, как размер шаблона
катастрофически вырастет.
Тем не менее возможно элегантное решение, использующее группы захвата. Захватим
гласную в группу, а затем сошлёмся на содержимое буфера захвата. Ссылки на
первый, второй, третий буферы записываются в регулярном выражении как
\g1
, \g2
, \g3
. Итак,
решением будет шаблон ([аеёиоуэюя])\g1{2}
. Обратите
внимание, что ссылка на буфер захвата должна следовать в регулярном выражении
строго после соответствующей группы.
Обратные ссылки могут ссылаться не только на нумерованные буферы, но и на
именованные. Такие ссылки имеют вид \k<name>
, где,
опять же таки, вместо name
стоит конкретное имя. Наш пример
можно переписать, применяя именованные группы:
(?<vowel>[аеёиоуэюя])\k<vowel>{2}
(vowel — гласная).
Иногда возникает необходимость в поиске, при котором не делается отличий между
строчными и прописными буквами. Такой поиск называется
нечуствительным к регистру (case-insensitive). Вместо того, чтобы всюду в шаблоне
заменять буквы на двухбуквенные классы
(a
→[Aa]
,
b
→[Bb]
, …), Просто заключим шаблон
в специальную группу, включающую режим case-insensitive поиска:
(?i:⋯)
. Такая группа не является
группой захвата. Если case-insensitive поиск должен быть реализован только
в части регулярного выражения, в группу следует поместить только нужную часть.
Наоборот, если какая-то часть регулярного выражения, в которой осуществляется
case-insensitive поиск, нуждается в отключении этого режина, то вернуться
к обычному, case-sensitive поиску можно, используя группу
(?-i:⋯)
.
Режимы чувствительности/нечуствительности к регистру влияют лишь на буквы. Что
считается буквой, а что нет, зависит от языка, как и правила соответсвия между
прописными и строчными буквами. С точки зрения английского языка, например,
буквой не является символ Щ
. В немецком языке имеется буква
ß
(между прочим, заглавный вариант этой буквы состоит из
двух букв SS
: Carl Friedrich
Gauß
→CARL FRIEDRICH GAUSS
).