Перейти к основному содержимому

Самоучитель по регулярным выражениям

Назад: Регулярные выражения (справка)

Здесь когда-нибудь будет самоучитель по регулярным выражениям, а пока копия текста из старой справки "как есть".

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

Введение

QSP отличается наличием очень мощных и гибких механизмов для работы с двумя самыми часто используемыми типами данных: строками и массивами. Однако в QSP есть ещё один механизм обработки строк, который, ввиду своей сложности, был пропущен мною. Я говорю о механизме регулярных выражений (regular expressions). Настало время поговорить о них, потому что в дальнейшем нам, возможно, очень часто потребуется использовать их. Кроме того, использование регулярных выражений во многих ситуациях поможет вам заменить кучу кода всего одной строчкой. Единственная проблема, которая обычно возникает при работе с регулярными выражениями - их очень необычный, и, поначалу, совершенно непонятный синтаксис. Поэтому я постараюсь рассказать о синтаксисе регулярных выражений по возможности более просто и подробно.

Общая информация

Регулярное выражение (regular expression, regexp, регэксп) - механизм, позволяющий задать шаблон для строки и осуществить поиск данных, соответствующих этому шаблону в заданном тексте.

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

Очень часто регулярные выражения используются для того, чтобы проверить, является ли данная строка строкой в необходимом формате. Например, следующий regexp предназначен для проверки того, что строка содержит корректный e-mail адрес:

————————————————————- ^\w+([\.\w]+)*\w@\w((\.\w)*\w+)*\.\w3$ ————————————————————-

Выглядит, на первый взгляд, довольно страшно :-) Но, тем не менее, это работает, и работает очень хорошо. А когда вы научитесь писать и использовать regexp'ы в своем коде - это ещё будет и сильно облегчать вам жизнь.

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

Сутью механизма регулярных выражений является то, что они позволяют задать шаблон для нечеткого поиска по тексту. Например, если перед вами стоит задача найти в тексте определённое слово, то с этой задачей хорошо справляются и обычные функции работы со строками. Однако если вам нужно найти "то, не знаю что", о чем вы можете сказать только то, как приблизительно это должно выглядеть - то здесь без регулярных выражений просто не обойтись. Например, вам необходимо найти в тексте информацию, про которую вам известно только то, что это "3 или 4 цифры, после которых через пробел идёт 5 заглавных латинских букв", то вы сможете сделать это очень просто, воспользовавшись следующим регулярным выражением:

———————-- \d4\s[A-Z]5 ———————--

Синтаксис регулярных выражений

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

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

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

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

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

Метасимволы для задания символов, не имеющих изображения

——— ————————- \n Символ перевода строки \r Символ возврата каретки \t Символ табуляции ——— ————————-

Метасимволы для задания групп символов

——— ————————————————————————————————————————— \d Цифра (0-9) \D Не цифра (любой символ кроме символов 0-9) \s Пустой символ (обычно пробел и символ табуляции) \S Непустой символ (всё, кроме символов, определяемых метасимволом \s) \w "Словесный" символ (символ, который используется в словах. Обычно все буквы, все цифры и символ подчёркивания ("_")) \W Всё, кроме символов, определяемых метасимволом \w ——— —————————————————————————————————————————

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

—————— ——————————————————————————— \d\d\d Любое трехзначное число ("123", "719", "001") \w\s\d\d Буква, пробел (или табуляция) и двузначное число ("A 01", "z 45", "S 18") \d and \d Любая из следующих строк: "1 and 2", "9 and 5", "3 and 4" —————— ———————————————————————————

Синтаксис регулярных выражений имеет средства для определения собственных подмножеств символов. Например, вам может понадобиться задать условие, что в этом месте строки должна находиться шестнадцатеричная цифра или ещё что-то подобное. Для описания таких подмножеств применяются символы квадратных скобок "[" и "]". Квадратные скобки, встреченные внутри регулярного выражения считаются одним символом, который может принимать значения, перечисленные внутри этих скобок.

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

—————————- —————————————————————————————————————————————————————————————————————— Обратный слэш ("\") Т.е. все метасимволы из приведённой ранее таблицы будут работать. Минус ("-") Используется для задания набора символов из одного промежутка (например, все цифры могут быть заданы как "0-9"). Символ "^" Если этот символ стоит первым в секции задания подмножества символов (и только в этом случае!) он будет рассматриваться как символ отрицания. Т.о. можно задать все символы, которые не описаны в данной секции. —————————- ——————————————————————————————————————————————————————————————————————

Несколько примеров, чтобы было понятно, как это работает:

——————- ————————————————————————————————————————————————————————————-- [0-9A-Fa-f] Цифра в шестнадцатеричной системе счисления [\dA-Fa-f] То же самое, но с использованием метасимвола [02468] Четная цифра [^\d] Всё, кроме цифр (аналог метасимвола \D) [a^b] Любой из символов "a", "b", "^". Заметьте, что здесь символ "^" не имеет какого-либо специального значения, потому что стоит не на первой позиции внутри квадратных скобок ——————- ————————————————————————————————————————————————————————————--

Другие метасимволы

Теперь необходимо рассмотреть ещё несколько метасимволов. Как уже было сказано ранее, все они работают только вне секций описаний подмножеств символов (вне квадратных скобок).

Символы "^" и "$". Они используются для того, чтобы указать парсеру регулярных выражений на положение искомого текста в строке. Символ "^" указывает, что искомый текст должен находиться в начале строки, символ "$" наоборот, указывает, что искомый текст должен находиться в конце строки. Посмотрим, как это работает на примере:

Допустим, у нас есть текст:
12 aaa bbb
aaa 27 ccc
aaa aaa 45\

И регулярное выражение для поиска чисел в этом тексте: "(?m)\d\d" (не обращайте пока внимание на "(?m)"). Поиск по этому регулярному выражению вернёт нам 3 значения: "12", "27", "45". Теперь ограничим поиск, указав, где именно внутри строки должен располагаться текст: "(?m)^\d\d". Здесь результат будет только один - "12", потому что только это число располагается в начале строки. Аналогично, регулярное выражение "(?m)\d\d$" вернёт результат "45".

Символ точки ("."). Этот метасимвол указывает, что на данном месте в строке может находиться любой символ (за исключением символа перевода строки). Очень удобно использовать его, если вам нужно "пропустить" какую-нибудь букву в слове при проверке. Например, регулярное выражение ".bc" найдёт в тексте и "abc" и "Abc" и "Zbc" и "5bc".

Символ вертикальной черты ("|"). Используется для задания списка альтернатив. Например, регулярное выражение "(красное|зелёное) яблоко" найдёт в тексте все словосочетания "красное яблоко" и "зелёное яблоко". О значении круглых скобок в этом выражении см. далее.

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

Чтобы было понятнее, о чем я только что рассказал - рассмотрим в качестве примера то, как работает парсер регулярных выражений в случае приведённого выше регулярного выражения о яблоках: "(красное|зелёное) яблоко".

  • Парсер начинает разбор регулярного выражения и встречает выражение в скобках: "(красное|зелёное)".
  • Парсер вызывает себя для поиска по найденному регулярному выражению.
  • Получив результаты поиска, парсер подставляет по очереди каждый из полученных результатов на место выражения в скобках и смотрит, удовлетворяет ли найденный результат всем условиям основного регулярного выражения (в данном случае смотрит, есть ли после найденного слова слово "яблоко").
  • Если всё в порядке - результаты поиска по каждому из имеющихся регулярных выражений для этого случая возвращаются, если нет - парсер просто переходит к следующему найденному фрагменту. Результат поиска внутреннего регулярного выражения для этого фрагмента при этом теряется.

В качестве примера возьмем строку:

————————————————————————————-- яблоко красное и зелёное яблоко и ещё одно красное яблоко и ещё одно яблоко, зелёное ————————————————————————————--

Поиск по внутреннему регулярному выражению даст 4 результата (выделены жирным шрифтом):

—————————————————————————————————— яблоко красное и зелёное яблоко и ещё одно красное яблоко и ещё одно яблоко, зелёное ——————————————————————————————————

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

———————————————————————————————- яблоко красное и зелёное яблоко и ещё одно красное яблоко и ещё одно яблоко, зелёное ———————————————————————————————-

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

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

——————————————-- \((\d5)\)\s+(\d3-\d2-\d2) ——————————————--

Некоторые из примененных здесь метасимволов вам ещё неизвестны и будут рассмотрены чуть позднее. Давайте рассмотрим этот regexp подробнее.

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

———————————————— \((\d5)\)\s+(\d3-\d2-\d2) ————————————————

Далее идёт регулярное выражение в скобках (проверка кода города):

———————————————— \((\d5)\)\s+(\d3-\d2-\d2) ————————————————

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

———————————————— \((\d5)\)\s+(\d3-\d2-\d2) ————————————————

Затем идёт пропуск пустого места:

———————————————— \((\d5)\)\s+(\d3-\d2-\d2) ————————————————

И ещё одно регулярное выражение в скобках, которое проверяет номер телефона:

———————————————— \((\d5)\)\s+(\d3-\d2-\d2) ————————————————

Как видите, здесь есть 3 регулярных выражения - основное и два внутренних. При этом основное выражение позволяет нам проверить, имеет ли строка необходимый нам формат, а два внутренних - получить соответственно код города и номер телефона. Т.е. одним регулярным выражением мы можем решить сразу несколько задач!

Посмотрим, как работает это регулярное выражение. Пусть у нас есть строка: "My phone is (095) 123-45-67".
Результатами поиска будут 3 строки:
"(095) 123-45-67"
"095"
"123-45-67"

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

Звездочка ("*"). Указывает, что символ должен быть повторен 0 или более раз (т.е. символ может отсутствовать или присутствовать в любых количествах). Пример: выражение "ab*c" найдёт строки "ac", "abc", "abbc" и т.д.

Плюс ("+"). Указывает, что символ должен быть повторен 1 или более раз (т.е. символ обязан присутствовать, и может присутствовать в любых количествах). Пример: выражение "ab+c" найдёт строки "abc", "abbc", "abbbc" и т.д., но не найдёт строку "ac".

Символ вопроса ("?"). Указывает, что символ может как присутствовать, так и нет, но при этом не может повторяться более одного раза. Пример: выражение "ab?c" найдёт строки "ac" и "abc", но не найдёт строку "abbc".

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

Примеры:

  • {2,4} - символ должен повториться минимум 2 раза, но не более 4.
  • {,5} - символ может отсутствовать (т.к. не задано минимальное количество повторений), но если присутствует, то не должен повторяться более 5 раз.
  • {3,} - символ должен повторяться минимум 3 раза, но может быть и больше.
  • {4} - символ должен повторяться ровно 4 раза.

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

Модификаторы регулярных выражений

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

Запись модификаторов производится посредством указания последовательности "(?модификатор)". Для отмены действия модификатора используется запись "(?-модификатор)".

——- ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————- i Включение режима case-insensitive, т.е. большие и маленькие буквы в выражении не различаются. m Указывает на то, что текст, по которому ведётся поиск, должен рассматриваться как состоящий из нескольких строк. По умолчанию механизм регулярных выражений рассматривает текст как одну строку вне зависимости от того, чем она является на самом деле. Соответственно метасимволы "^" и "$" указывают на начало и конец всего текста. Если же этот модификатор указан, то они будут указывать соответственно на начало и конец каждой строки текста. s По-умолчанию, метасимвол "." не включает в свое определение символ перевода строки. Т.е. для многострочного текста выражение ".+" вернёт только первую строку, а не весь текст, как ожидается. Указание этого модификатора снимает это ограничение. ——- ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-

Назад: Регулярные выражения (справка)