Знакомство с регулярными выражениями

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


Что такое регулярные выражения?

Регулярные выражения придуманы не вчера и даже не позавчера. Это средство облегчения жизни, одинаково хорошо подходящее и программистам, и пользователям, придумано уже довольно-таки давно и с тех пор исправно трудится на нелёгкой компьютерной ниве. В общем-то, это довольно странно, но факт: львиная доля пользователей и даже некоторые разработчики не умеют пользоваться регулярными выражениями. Ну, с пользователями всё, в общем-то, ясно - они народ ленивый, и изучать что-то новое для многих из них, что называется, смерти подобно. Но вот чем оправдать лень разработчиков, которые выписывают огромные куски кода вместо того, чтобы написать одно регулярное выражение?.. Наверное, дело во всё той же инерционности, которой программисты зачастую страдают не меньше, чем те, кто потом пользуется продуктами их труда. К счастью, во многих современных языках программирования обойтись без регулярных выражений попросту невозможно. Однако что-то я уже стал забегать вперёд. Я же ведь ещё даже не рассказал, что эти самые регулярные выражения, собственно, собой представляют, верно? Точно. Исправляюсь.

Регулярные выражения - средство поиска по тексту на основе шаблонов. Шаблон описывает закономерность, которой должны подчиняться искомые последовательности символов в тексте. Самый простой пример - поиск файлов, который достался Windows в наследство от DOS'а. Строка поиска - это ведь, фактически, шаблон, который задаёт специальным образом имя файла. Например, если мы будем искать встроенными в Windows средствами файлы по маске "*.doc?", то система найдёт все файлы, имя которых содержит сколько угодно символов, а расширение начинается на .doc и содержит не более четырёх символов. Маска поиска и будет в нашем случае шаблоном, задающим закономерность, которой и должны удовлетворять результаты нашего с вами поиска. Однако, конечно же, регулярные выражения имеют гораздо больше возможностей, нежели простой поиск по маске.

Тем не менее, как и при поиске по маске в Windows, при работе с регулярными выражениями приходится иметь дело с двумя существенно различающимися по своей природе вещами - литералами и метасимволами. Литералы - это обычные символы, то есть те, которые при записи в строке регулярного выражения интерпретируются именно так, как они записаны - они не имеют никого "подвоха", не имеют никаких специальных значений в данном выражении. В том примере, который я привёл выше, литералом были символы ".doc". Метасимволы, соответственно, интерпретируются при поиске каким-то особым образом - как, например, "*" задаёт последовательность любого количества любых литералов.


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

Говорить о каком-то едином стандарте для записи регулярных выражений пока не приходится, поскольку, теоретически, любой интерпретатор регулярных выражений (их, кстати, часто называют регэкспами - от английского regular expression) может использовать свой синтаксис. Однако компьютерный мир тяготеет к наличию стандартов - если не формальных, то хотя бы негласных. Поэтому для записи регулярных выражений чаще всего применяется синтаксис, разработанный для использования регулярных выражений в POSIX-системах. Там они применяются для работы с интерпретатором команд, несравненно более мощным, чем тот, который корпорация Microsoft встроила в Windows. Именно с синтаксисом регулярных выражений для POSIX-интерпретаторов я сейчас вас и познакомлю.

Символ "." соответствует любому единичному символу - фактически, именно точке аналогичен вопросительный знак в Windows-поиске.

Для задания символов из определённого диапазона используются квадратные скобки ([]). Символы, заключённые в такие скобки, задают символьный класс. Например, если мы напишем [a-c], то это будет обозначать любой единичный символ из набора букв a, b, c. То же самое можно было записать как [abc]. Обратите внимание на то, что символ "-" интерпретируется буквально только в том случае, если он расположен непосредственно после открывающей или перед закрывающей скобкой (т.е. записывается как [abc-] или [-abc]. В противном случае, он обозначает интервал символов. Таким образом, [a-z] соответствует буквам нижнего регистра латинского алфавита. Для простоты записи стандарт POSIX задаёт некоторые специальные обозначения, однако многими интерпретаторами регулярных выражений в скриптовых языках программирования они могут не поддерживаться. К таким обозначениям относятся: [:upper:] - символы верхнего регистра, [:lower:] - символы нижнего регистра, [:alpha:] - весь алфавит, т.е. символы и верхнего, и нижнего регистров, [:alnum:] - то же самое плюс цифры, [:digit:] - только цифры, [:xdigit:] - шестнадцатеричные цифры, [[:punct:]] - знаки пунктуации, [[:blank:]] - пробел и табуляция. Сами скобки тоже можно вводить в перечисление элементов, для этого нужно, чтобы закрывающая скобка была первым символом после открывающей - например, так: [][a-z].

Чтобы не писать длинные перечисления, можно вводить списки того, чего нет в скобках - для этого после открывающей скобки нужно поставить "домик" (^). Если он стоит вне квадратных скобок, то соответствует началу текста или началу любой строки (последнее в случае, если работа с текстом ведётся в мультистроковом режиме). Концу текста (или концу строки) соответствует символ доллара ($). Звёздочка после выражения, соответствующего единичному символу, соответствует нулю или более копий этого выражения. Например, "[xyz]*" соответствует пустой строке, "x", "y", "zx", "zyx", "zzzxxxyyy" и т.п. Для задания определённого количества букв используется следующая конструкция: \{x,y\}. Она соответствует последнему встречающемуся перед ней блоку, встречающемуся не менее x и не более y раз. Например, "x\{3,5\}" соответствует "xxx", "xxxx" или "xxxxx".

К более сложным операторам, применяемым в конструировании регулярных выражений, относятся операторы для работы с "отмеченными подвыражениями". Конструкция \(\) объявляет "отмеченное подвыражение", которое может быть использовано позже с помощью оператора \n, где n - это цифра от 1 до 9; соответствует n-му отмеченному подвыражению. \n* соответствует нулю или более вхождений для соответствия n-го отмеченного подвыражения. Например, "\(a.\)c\1*" соответствует "abcab" и "abcaba", но не "abcac".

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

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


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

Наиболее часто регулярные выражения используются в web-программировании. Большая часть популярных скриптовых языков web-программирования используют синтаксис, характерный для POSIX-платформ и описанный выше. В "настольном" программировании регулярные выражения тоже время от времени приходится использовать - для этого в таких популярных языках, как С# (хотя вернее было бы, конечно, сказать в платформе .NET) предусмотрены специальные классы, умеющие разбирать и интерпретировать регулярные выражения. Например, на C# для работы с регулярными выражениями используется следующий код:

Regex re = new Regex(pattern, options);
MatchCollection mc = re.Matches(text);

Здесь Regex - класс, отвечающий за работу с регулярными выражениями, pattern - собственно, шаблон, options - опции интерпретатора, а mc - результаты поиска с помощью регулярных выражений в тексте text. Опции для интерпретатора могут быть такие: I - поиск без учета регистра; m - многострочный режим, когда можно попытаться искать совпадения в начале или конце одной какой-то строки, а не всего текста; c - компиляция алгоритма поиска в промежуточный MSIL-код с его последующим выполнением, s - интерпретация конца строки как символа-разделителя, x - исключение из образца неприкрытые незначащие символы (пробелы, табуляция и т.д.) и включение комментариев в стиле Perl (т.е. комментарии будут начинаться с символа "решёточки" - #); r - поиск справа налево.

Стоит отдельно упомянуть регулярные выражения в языке Perl, который, собственно, именно ими больше всего и знаменит. Perl - пожалуй, единственный в мире язык программирования, где регулярные выражения реализованы не в виде классов, а могут применяться прямо в тексте программы. Регулярные выражения в стие Perl обычно называют PCRE - Perl-Compatible Regular Expressions. Многие языки программирования имеют специальные библиотеки для работы с регулярными выражениями формата PCRE. Например, тот же самый PHP, который сейчас весьма популярен в сфере web-программирования, поддерживает регулярные выражения как стандарта POSIX, так и выражения стандарта PCRE. Надо сказать, что эти два стандарта имеют ряд глубоких и весьма существенных различий, поэтому рассказывать сейчас о том, чем они отличаются, я не стану - для этого потребуется отдельная статья, и, вполне возможно, если эта тема окажется интересной для наших читателей, такая статья и появится.

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

Вадим СТАНКЕВИЧ,
dreamdrusch@tut.by

Версия для печатиВерсия для печати

Номер: 

14 за 2008 год

Рубрика: 

Software
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!