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

Содержание

1. Общее описание. Вариант 1
2. Общее описание. Вариант 2
3. Общее описание. Вариант 3
4. Общее описание. Вариант 4
5. Регулярные выражения в .NET
6. Регулярные выражения в perl
7. Регулярные выражения (шаблоны)
8. Регулярные выражения в PHP
9. Регулярные выражения в Visual Studio
10. Краткий справочник по синтаксису


Регулярные выражения, Часть I

Sergey Kolesnichenko

2004-03-23

Глава 1. Шерлок Холмс спешит на помощь вебпрограммисту или регулярные выражения на пальцах.

Введение.

Каждый веб-программист сталкивался с задачей, когда в произвольном тексте нужно найти какие-то данные по какому-то закону, проверить данные, которые поступили от пользователя, подвергнуть найденные данные сложной модификации. Можно изобретать велосипед, а можно использовать средства, которые используют программисты всего мира. Иной раз кажется, что профи пользуются какими-то инструментами, приемами, которые доступны только им. Разочарую читателя, что профи используют те же средства и инструменты, что и вы, только разница состоит в том, что они ими умеют пользоваться и умеют выбирать, какой инструмент стоит использовать в конкретном случае.

Данный материал призван помочь программистам решать насущные задачи при помощи регулярных выражений. Я постараюсь описать самые основы использования этого инструмента, чтобы вы не смотрели на комбинацию подобную этой: /^(?:http:\/\/)?[-0-9a-z._]*.\w{2,4}[:0-9]*$/ как баран на новые ворота.

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

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

Часть 1.

Где мы видели шаблон? Пойдем к знакомой секретарше и спросим у нее. Правильно, ответ - шаблоны Microsoft Word! Чем отличается шаблон "Календарь" от шаблона "Изысканное резюме"? Данными и способом их подачи. Человек, который хотя бы раз видел и то и другое, без труда будет отличать календарь от резюме. Так почему тогда регулярные выражения пугают программиста? Ведь это почти то же самое! О чем думает человек, который видит календарь и знает что это такое, как он его узнает? Календарь - это документ, который разделен на блоки, каждый блок состоит из цифр, которые соответствуют дням месяца. Каждому месяцу соответствует только один блок, в месяце бывает не больше 31 дня, в феврале не бывает больше 28 (за исключением високосного года), дни, которым соответствует день недели воскресенье или государственный праздник выделены красным, можно продолжить систематизировать данные дальше, указав месяцы в которых ровно 30 и 31 день. Что мы сделали? Мы создали описание календаря, по-другому, мы описали данные, найдя которые в произвольном тексте, можно с определенной уверенностью сказать, что перед нами календарь. Подобное описание я называю шаблоном в контексте разговора о регулярных выражениях.

Грядет день, когда программист будет говорить компьютеру, какую он хочет написать программу, а компьютер будет ее писать, но пока что программисту приходится трудиться самому. Т.е. если я скажу, что ищу отрывок текста, который удовлетворяет описанию календаря, то в наше время компьютер меня не поймет. Садитесь поудобней, ибо если гора не идет к Магомету, то Магомет идет к горе. Как все программисты к горе пойдем сидя перед монитором. Наша задача - научиться описывать данные, которые мы хотим найти, в понятной компьютеру форме. А что вы еще не умеете? Стыдно! Это тоже умеет каждая секретарша. Надеюсь, что такое командная строка программисту объяснять не надо. Start=>Run=>cmd. Наберем в командной строке слово dir.

Вот, что я получил:

C:\Documents and Settings\Administrator>dir
Volume in drive C has no label.
Volume Serial Number is 3CC6-6445

Directory of C:\Documents and Settings\Administrator

13.10.2003 18:03 <DIR> .
13.10.2003 18:03 <DIR> ..
18.07.2003 21:55 <DIR> .java
18.07.2003 21:54 <DIR> .javaws
18.07.2003 21:55 <DIR> .jpi_cache
15.10.2003 16:33 694 .plugin141.trace
05.10.2003 11:40 <DIR> Desktop
16.10.2003 13:08 <DIR> Favorites
08.10.2003 16:42 <DIR> My Documents
18.08.2003 20:51 <DIR> Start Menu
04.07.2003 21:24 <DIR> WINDOWS
1 File(s) 694 bytes
10 Dir(s) 2 162 040 832 bytes free

Знакомо? Естественно! Считайте, что вы уже умеете пользоваться регулярными выражениями, осталось только совершенствовать свои навыки. Что вы сделаете, если файлов и директорий много, а вам нужно проверить только те, которые вас интересуют непосредственно? Вы попробуете уменьшить количество выводимых данных, указав условие поиска, описав данные, которые хотите получить. Обратите внимание, что нужно описывать данные, а значит, вы стоите на пороге создания шаблона. Допустим, нас интересуют все файлы и директории, которые имеют название, которое имеет в себе слово java. Уверен, что вы думаете так же, как и я и получаете вот такой результат:

C:\Documents and Settings\Administrator>dir *java*
Volume in drive C has no label.
Volume Serial Number is 3CC6-6445

Directory of C:\Documents and Settings\Administrator

18.07.2003 21:55 <DIR> .java
18.07.2003 21:54 <DIR> .javaws
0 File(s) 0 bytes
2 Dir(s) 2 161 618 944 bytes free

Попробуем перевести строку dir *java* на русский язык: Найти и показать все файлы и директории в текущей директории, в названии которых присутствует слово java.

Вроде правильно, а вот и нет! Понимание регулярных выражений заключается в первую очередь в правильном описании закона совпадения (либо несовпадения), а также знания средств, которыми это описание можно "рассказать" компьютеру. Большинство статей в Сети занимаются решением второго пункта, и полностью опускают первый, они рассказывают программисту при помощи каких средств можно "рассказать" компьютеру свое описание интересующих данных. Почему я сказал, что описание неправильное? Потому что dir java* отличается одним символом от dir *java*, но под описание русским языком тоже попадает, ведь слово "java" в названии присутствует?

Делаем вторую попытку описания строки dir *java*

Найти и показать все файлы и директории в текущей директории (и до этого момента все идет правильно), название которых начинается на какой угодно символ, таких символов может быть сколь угодно (в том числе их может и не быть), но после них обязательно идут подряд символы "j", "a", "v", "a", после которых может идти какой угодно символ, этих символов может быть сколько угодно (а может вообще не быть).

Отличается? Еще как! Продолжаем учиться описывать данные, которые мы хотим найти. Ищем командой dir, работаем с командной строки. Но задание усложним. Теперь я дам описание данных, которые хочу найти, а вы попробуете это сделать, все инструменты вам знакомы.

Найти и показать все файлы и директории в текущей директории, название которых начинается на какой угодно символ, таких символов может быть сколько угодно (а может не быть вообще), но после них обязательно идет символ "j", после которого снова идет любое количество каких угодно символов, но в конце названия стоят подряд символы "w" и "s".

Если вы читали внимательно, то без труда введете: dir *j*wsи получите ответ:

C:\Documents and Settings\Administrator>dir *j*ws
Volume in drive C has no label.
Volume Serial Number is 3CC6-6445

Directory of C:\Documents and Settings\Administrator

18.07.2003 21:54 <DIR> .javaws
0 File(s) 0 bytes
1 Dir(s) 2 161 504 256 bytes free

Просто? Еще бы! А теперь представьте, что я хочу найти не сколько угодно, а определенное количество каких угодно символов. Для этого используется символ "?" (знак вопроса). Вот и задача:

Найти и показать все файлы и директории в текущей директории, название которых начинается на какой угодно символ, таких символов может быть сколько угодно, но после них обязательно идет символ "j", после которого идет ровно четыре любых символа, а после них стоят подряд символы "w" и "s". Ее решение: dir *j??ws

C:\Documents and Settings\Administrator>dir *j?ws
Volume in drive C has no label.
Volume Serial Number is 3CC6-6445

Directory of C:\Documents and Settings\Administrator

18.07.2003 21:54 <DIR> .javaws
0 File(s) 0 bytes
1 Dir(s) 2 161 504 256 bytes free

Первая часть закончена. Краткий итог:

  • данные для поиска поступают на вход в нашем случае команды dir в каком-то унифицированном виде, который я назвал описанием данных или шаблоном;
  • механизмом вывода команды dir можно управлять, меняя шаблон;
  • команда dir выводит данные, только если они удовлетворяют заданному шаблону;
  • в шаблон входят как обычные буквы, так и специальные символы.

Часть 2.

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

Посмотрите, что обозначает символ "*" и символ "?". Первый означает любое количество любых символов, второй означает только один любой символ. Ну, если с количеством пока все понятно, то вот что означает "любой символ"? Спрашиваете - отвечаем! "Любой символ" означает в нашем контексте работы с командой dir из командной строки любой из литералов. А значит звездочка "*" означает "сколько угодно литералов", "?" означает один литерал. Понятно? Вроде. Понятно, но не совсем определени дано не совсем точно. А почему? Потому что когда я описывал данные для поиска командой dir, я писал примерно вот так: на какой угодно символ, таких символов может быть сколь угодно (в том числе их может и не быть)

Звездочка "*" означает сколько угодно литералов, а равно и их отсутствие! Что это значит? Это значит, что литералов может быть сколько угодно, но для совпадения при поиске не требуется ни один! И это нужно понимать и запомнить.

Знак вопроса "?" означает, что литералов может быть только один! Т.е. на данной позиции может стоять только один литерал, не больше и не меньше!

Рассмотрим подробнее понятие литерал, немного с другой стороны. Что из этих двух символов литерал, а что метасимвол? "j" "*"

Элементарно, Ватсон, "j" - литерал, "*" - метасимвол.

Зреет вполне логичный вопрос, а как отличил?

На логичный ответ даем логичный ответ. Мы знаем, что символы "*" и "?" имеют какое-то магическое действие, по крайней мере команда dir их воспринимает по-другому, не так как симоволы "j", "a", "v", "a". Вот мы и разделили все символы на два класса, один - литералы, второй - метасимволы. Мы точно знаем, что в класс литералов входят все символы латиницы a, b, c, d и так далее до z, а так же входят цифры 0, 1, 2 и так до 9. Т.е класс литералов мы разбили еще на подклассы. Вам не кажется, что мы уже слишком много знаем, чтобы до сих пор возиться с командной строкой? Пора уже попробовать свои знания.

Кратко итог второй части:

  • обычные символы называются "литералами";
  • специальные символы называются "метасимволами";
  • литералы означают сами себя;
  • метасимволы предназначены для описания диапазнона литералов, каких-то применимых к литералам условий, свойств литералов, их количества;
  • все литералы можно классифицировать, собрав их вместе по какому-то признаку.

Часть 3.

Ну наконец-то этот автор перестал рассказывать байки про командную строку, а начал что-то говорить про то, ради чего собственно писалась статья, использование регулярных выражений в веб-программировании. Остановитесь и подумайте, все ли понятно в предыдущих двух частях? Если нет, прочтите их еще раз, проделайте все примеры, эксперементируйте! Читать часть 3 без понимания предыдущих частей не советую, потратите время зря.

Из великого и могучего языка PHP нам понадобится только одна функция: preg_match()

Ее общий формат таков:

preg_match("шаблон_поиска", "строка_в_которой_проводится_поиск", массив_с_результами_поиска)

Эта функция реализует обращение к механизму обработки регулярных выражений, поиск совпадения в строке и возврат совпадений в массив. Так. Стоять! Что значит обращение к механизму, поиск и возврат? Мы привыкли оперировать немного другими понятиями. Вернемся снова к команде dir. В ее состав входит какой-то механизм, который читает Ваш шаблон и ищет по нему файлы и директории в текущей директории, ведь так? Для простоты назовем этот механизм тоже механизмом обработки регулярных выражений. Представим себе, что названия файлов и директорий записаны в строку, шаблон поиска записан в другую строку, а результаты выводятся не на черный экран, а в массив, и для поиска информации о файлах и директориях мы используем не команду dir, а функцию PHP preg_match(). Все стало на свои места. Функция preg_match() передает шаблон для поиска и строку, в которой будем искать, механизму поиска совпадений (механизму обработки регулярных выражений), и обеспечивает вывод результатов поиска в массив. Вылитая команда dir! Теперь понятно, почему я в начале сказал, что вас не нужно учить пользоваться регулярными выражениями, вы уже знаете, как это делается, просто не осознаете сам факт. Ну вот, поздравляю, вы теперь осознаете и факт. На этом можно было бы и закончить, если бы возможности поиска в PHP ничем не отличались от возможностей команды dir. Но это не так, поэтому будем учиться дальше и будем читать Главу 2.

Глава 2. Основы.

Еще одно введение

Регулярные выражения - язык шаблонов (на самом деле это математический термин, кому интересно, читайте про детерминированные и недетерминированные конечные автоматы)

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

В РНР это будет, например, вот так:

  • preg_replace - заменить
  • preg_match - найти соответствие
  • preg_split - разбить

тоже самое с функциями стандарта Posix типа ereg, только они отличаются тем, что в отличие от Preg у них другой механизм обработки поиска совпадений.

Обработка шаблона происходит посимвольно, как вы будете искать букву d в слове stadium? правильно, первое, что приходит на ум, это перебрать все буквы слова stadium и сравнить их с буквой, которую ищем, итак получаем простой перебор. Считайте, что вы уже научились использовать РВ, только осталось выучить как задавать шаблон того, что вы хотите найти в строке.

Шаблон - это своеобразный указатель, что искать в строке.

Искать можно цифры, буквы, невидимые символы (пробел, таб)

Как вы в слове stadium будете искать букву d либо букву m пока не произойдет первое совпадение одного из символов? правильно, тоже перебором, возьмете каждую букву слова и будете сравнивать с тем, что вам надо найти поочередно с d и c m. Но вам каждый раз теперь придется сравнивать букву слова (строки) с двумя буквами условия поиска. Таким образом, вы создали свой первый символьный класс, который на языке регулярных выражений записывается вот так: [dm] - это означает, что вы ищете либо d либо m

Что нужно, чтобы указать, что вы ищете в строке то, что может быть любой буквой алфавита? надо либо перечислить их все [abc....xyz] либо просто написав интервал [a-z]

Внимание смертельный номер, такое возможно с русскими буквами [а-я] а так же с цифрами [0-9] но есть еще заглавные [A-Z] т.е. чтобы получить символьный класс со всеми буквами латинского алфавита надо поставить в шаблоне [a-zA-Z]

Но вот такой символьный класс описывает только один символ, а у вас в строке их вон сколько, это решается при помощи квантификаторов.

Квантификаторы

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

Один символьный класс может совпасть только с одним символом! - это надо понимать!

Как в условии задать поиск двух символов?

Приведу простой пример без символьных классов, который уже был затронут выше:

Ищем последовательность iu в слове stadium. Как вы это сделаете? Ну естественно перебором:

  1. берем первый символ слова и сравниваем его с первым символом условия 's' неравно 'u'
  2. берем второй символ слова и сравниваем его с первым символа условия 't' неравно 'i'
  3. делаем так до тех пор, пока j-й символ в слове не совпадет с первым символом условия поиска. В этот момент 'i' равно 'i' (мы находимся в выделенной позиции в слове stadium, и выделенной позиции условия поиска iu ), как только выполнилось это условие, надо взять следующий символ из слова и из условия (предварительно запомнив, где произошло первое совпадение, очень важный шаг!)
  4. сравниваем следующий символ слова со вторым символом условия 'u' равно 'u' Поздравляю, исходная комбинация найдена!
  5. Что делать если условие 4 не выполнилось, если вместо слова stadium нам подсунули слово с ошибкой stadiem?

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

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

Допустим, у нас есть строки:

abcd12345efg
fghi56789qwe

Условие: Найти в строках участки, которые состоят из четырех любых букв латинского алфавита, после которых следуют пять любых цифр.

Выше я рассказывал, как описать символ, который будет совпадать с любой латинской буквой, напомню, для этого мы используем символьный класс: [a-z] (пока не обращаем внимания на то, что есть еще заглавные буквы) символ условия, который совпадет с любой цифрой, описывается вот таким символьным классом: [0-9]

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

Из того, что мы уже знаем, можно написать: [a-z][a-z][a-z][a-z][0-9][0-9][0-9][0-9][0-9]

Посмотрите сами, такая форма записи условия поиска будет работать! так как мы описали каждым символьным классом один символ в условии поиска. Вам не кажется, что слишком громоздкое условие поиска получается? И тут на помощь приходят квантификаторы. Вспоминаем английский: quantity - количество. Т.е. квантификатор - то, что выражает количество чего-то, в нашем случае количество символов в условии поиска. Упрощаем условие поиска при помощи квантификаторов: [a-z]{4}[0-9]{5}

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

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

Каждый символьный класс описывает только один символ, количество схожих символов идущих подряд описывается квантификаторами.

Естественно подобное задание количества символов в условии поиска не является единственным. Квантификаторы бывают разные! [a-z]{1,3} означает, что подряд может идти от одного до трех букв латинского алфавита. [a-z]{2,} означает, что может идти минимум 2 буквы латинского алфавита подряд.

Но квантификатор в фигурных скобках не является единственным способом задать количество символов идущих подряд, которые описаны символьным классом. [a-z]* означает, что подряд может идти сколь угодно букв латинского алфавита, а может быть, что и ни одной, идентично [a-z]{0,} . [a-z]+ означает, что обязательно подряд должна идти минимум одна буква латинского алфавита, но максимальное количество не указано, идентично [a-z]{1,} [a-z]? означает, что количество латинских букв не должно превышать 1, буква также может вообще отсутствовать, идентично [a-z]{0,1}.

Применение квантификаторов к литералам

Вернемся в самое начало статьи, где мы искали перебором букву d в слове stadium. Условие поиска совпадения, которое описывается одним неспециальным символом, называется литералом.

Есть строки:

abcdefg
abcddefg
abcdddefg
abcddddefg

Требование: Написать условие поиска совпадения для всех строк.

Решение: abcd{1,4}efg

Только что вы видели, как я применил квантификатор к литералу. К литералам можно применять любой из вышеприведенных квантификаторов.

Ясно, что применив условие поиска avcd{1,4}efg для строки abcefg, совпадение найдено не будет, так как квантификатор {1,4} подразумевает, что после abc перед efg идет миним одна, максимум четыре буквы d.

Символьные классы "для продвинутых"

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

Что может входить в символьный класс? В символьный класс может входить любой литерал, а так же интервалы литералов. Для описания интервалов литералов используется символ '-', который ставится между первым символом интервала и последним. Приведу примеры задания различных интервалов в одном символьном классе: [1-5] - числа в диапазоне от 1 до 5 [a-f] - буквы латинского алфавита от a до f [a-fq-x] - буквы латинского алфавита от a до f и от q до x как вы заметили, в последнем символьном классе я использую два диапазона.

Представим, что вам нужно описать условие, что в определенном месте строки могут стоять символы: либо a, либо g, либо 7, либо 4. Что делать? Писать символьный класс: [ag47]

Объяснение простое, в символьном классе можно перечислять допустимые в условии поиска литералы. Перечисление литералов можно совмещать с указанием интервалов: [14a-kz] - это означает, что символ в строке может совпадать с 1, 4, буквами латинского алфавита c a по k, а так же с буквой z. Естественно литералами могут быть не только буквы и цифры, а так же знаки препинания, математические знаки, например ',' (запятая), '!' (восклицательный знак), '+' (плюс), '-' (минус). Стоп, скажете вы, какой минус, если он же используется для описания интервалов. Правильно, если вы поставите минус между a и z, то это будет интервал, но если вы поставите минус сразу же после открытой квадратной скобки, то это будет минус! И вот пример: [-,a-z] - означает, что в символьный класс входят минус, запятая, а так же буквы латинского алфавита от a до z.

А как написать символьный класс, в который входят все символы кроме заданных? Например, все кроме a, b, c? Для этого существует специальный спецсимвол отрицания: ^ (крышка) Пишем: [^abc] - все символы (не буквы, а именно символы) кроме букв латинского алфавита a, b, c.

Запомнить все!

Наверное, у многих возникает вопрос, что если надо проверить всю строку на соответствие условию, а вернуть при помощи функций языка только часть строки?

Как при помощи функций языка выполнить операцию не над всей строкой, которая удовлетворяет условию, а только над ее частью?

Специально для таких целей существуют группирующие и одновременно сохраняющие круглые скобки.

Пример:

Строка может состоять из 5-ти букв латинского алфавита, после которых следует знак минуса, после которого следуют четыре цифры от 1 до 8.

Задание:

  1. проверить строки на соответствие условию
  2. вернуть в программу четыре цифры, что на данном этапе будет означать только то, что эти четыре цифры надо как-то выделить в условии поиска

Если мы просто хотим проверить строку на соответствие условию совпадения, то условие поиска будет вот таким: [a-z]{5}[1-8]{4} но нам надо запомнить цифры, поэтому добавляем инструкцию запоминания: [a-z]{5}([1-8]{4})

Как видите, то, что надо запомнить, выделено круглыми скобками. Внутри функции, которая будет выполнять операцию со строкой при помощи вышеприведенного условия, совпадение будет запоминаться в специальных переменных, в PHP к ней можно обращаться через \1 в Perl - $1. В одном условии поиска может быть несколько инструкций запоминания: ([a-z]{5})([1-8]{4}) - проверит строку на совпадение с условием, в случае удачного совпадения, запомнит пять букв в \1 ($1), четыре цифры в \2 ($2). Если обратиться к переменной \0, то окажется, что в ней хранится вся совпавшая строка, которая была описана условием.

Ближе к реалиям:

Тот, у кого хватило желания дочитать до этого места, уже много понимает, пытается писать свои собственные условия поиска по строке, но у него не получается. Скорее всего это происходит из-за того, что вы не знаете многих мелочей работы с регулярными выражениями. Про некоторые я расскажу прямо сейчас. Строка имеет начало и конец. Вы скажете, что это понятно каждому, но скорее всего вам придется немного изменить свое понятие о строках. Считайте, что вначале каждой строки и в конце каждой строки стоит невидимый символ. При поиске в реальной программе это надо учитывать, поставив в начале условия поиска символ '^' (начало строки), а в конце условия поиска '$' (конец строки). Поэтому пример с пятью буквами и четырьмя цифрами (как и все остальные после них вышеприведенные) надо переписать: ^[a-z]{5}[1-8]{4}$ теперь при помощи этого условия на самом деле можно проверить строки

asbvc1234
sdwtsv1234

на соответствие условию.

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

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

Считайте, что \s соответствует литералу 'пробел', либо литералу 'перевод строки', либо литералу 'табулятор'.

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

Пример:

preg_match("/^[a-z0-9]/", $string,$mathces);

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

Примеры к главе

Все примеры взяты из вопросов, которые люди задают на различных форумах. Примеры буду разбирать на том языке, на котором сам пишу: PHP.

  1. Очень часто встречается ситуация, когда надо написать регистрацию на ресурсе, который вы поддерживаете. В силу каких-либо причин выдвигаются требования к введенной информации. Например, часто спрашивают, как проверить, что пользователь ввел в качестве ника (nickname, login) слово, состоящее только из букв латинского алфавита и цифр.

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

    <?php
    $user     = $_POST['username'];
    if(!    preg_match("/^[a-zA-Z0-9]+$/", $user)) {
        echo     "Имя пользователя задано в неправильном формате";
    } else {
        echo     "Имя пользователя задано в правильном формате";
    }
    ?>

    А теперь разбираем само регулярное выражение.

    Так как регистрационное имя пользователя должно состоять из латинских букв, а также цифр, то надо написать символьный класс, который будет удовлетворять этому условию: [a-zA-Z0-9] в этот символьный класс входит три интервала, первый интервал a-z (все символы от маленькой буквы a до маленькой буквы z), второй интервал A-Z (аналогично, но с большими буквами), третий интервал 0-9 (цифры от 0 до 9). Мы описали только одну букву, из которой может состоять регистрационное имя, но таких букв может быть... а теперь как раз надо ответить на вопрос, сколько таких букв может быть? Да сколько угодно, скажете вы, а я скажу, что вы неправы.

    Регистрационное имя должно состоять минимум из одной буквы! и я считаю, чти это обязательное условие при прохождении регистрации, поэтому надо данный факт описать. Вспоминаем про квантификаторы: [a-zA-Z0-9]+

    Плюс '+', как раз тот квантификатор, который говорит, что в строковой переменной $user должен быть минимум один символ, который соответствует условию.

    А теперь надо сказать регулярному выражению, что условию должна соответствовать вся строка, от начала до конца, поэтому добавляем в регулярное выражение символ начала строки '^' вначале регулярного выражения и символ конца строки '$' в конец: ^[a-zA-Z0-9]+$

    Теперь надо объяснить функции preg_match, что строка ^[a-zA-Z0-9]+$ является регулярным выражением, надо поставить ограничители, я ставлю слеш '/':

    preg_match("/^[a-zA-Z0-9]+$/",$user)

  2. Есть люди, которые сами себе усложняют жизнь, эти люди называются программистами и системными администраторами, причем часто они усложняют жизнь не только себе, а еще и друг другу, а тут еще и босс приходит и видит, что на сайте компании можно регистрироваться, а имя пользователя еще как-то хитро проверяется, в 80% случаев вышеприведенная задача усложняется боссом, надо либо расширить набор символов, что делается простым добавлением символов в символьный класс, либо ужесточаются условия. Например, ставится условие, что имя пользователя может состоять как из букв латинского алфавита, так и из цифр, но первый символ имени пользователя должен быть обязательно буквой латинского алфавита! Дописываем регулярное выражение: ^[a-zA-Z][a-zA-Z0-9]*$

    Как уже давно известно, символьный класс описывает только один символ. Наша задача как раз в том и состоит, чтобы описать один символ (первый): [a-zA-Z] тут два интервала, описывают первый символ и дают понять, что никаких цифр в первом символе проверяемой строки быть не может.

    Из размышлений в предыдущем примере явно следовало, что регистрационное имя пользователя должно быть длинной минимум в один символ. Мы уже описали первый символ строки, но он может быть и единственным, после него может быть сколько угодно символов, либо может их вообще не быть, но регулярное выражение совпадет только тогда, когда все последующие символы будут удовлетворять условию, что они являются либо буквами латинского алфавита, либо цифрами. [a-zA-Z0-9] Первый обязательный символ описан, остальные символы не обязательны, поэтому меняем квантификатор с '+' на '*', Ставим символы начала и конца строки: ^[a-zA-Z][a-zA-Z0-9]*$

Глава 3.

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

Спецсимволы.

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

  • \s - если вы в условии поиска поставите друг за другом символ обратного слеша, а после него сразу букву s, то таким образом вы опишите либо пробел, либо символ табуляции. Конечно в условии поиска можно поставить пробел так, как вы его обычно ставите на письме, но запись [a-z\s] будет намного понятнее и читабельней чем [a-z ], с первого взгляда видно, что в первый символьный класс входит пробел, а вот со вторым символьным классом надо присмотреться, а так как регулярные выражения итак представляют для многих набор значков, то пропустить пробел, поставленный таким образом будет очень просто. Внимательно используйте этот спецсимвол, так в дополнении к тому, что он совпадает с пробелом и табулятором, он совпадет также с символом новой строки.
  • \S - скажу просто, что в своем большинстве это видимые символы, т.е. все, что не совпадает с \s
  • \w - спецсимвол, который призван заменить целый символьный класс, в него входят все символы, которые могут входить в слово, обычно это [a-zA-Z_], хотя много может зависеть от установленной локали, поддержки юникода и т.д.
  • \W - все что не входит в определение \w. либо [^a-zA-Z_]
  • \d - все цифры т.е. уже известный вам символьный класс [0-9]
  • \D - все, что не является цифрой

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

А вы спросите тогда, а что делать, если надо описать именно точку, а не все символы? Поставьте перед точкой обратный слеш: \.

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

Аналогично, чтобы поставить два обратных слеша их надо тоже удвоить вот так: \\\\

Альтернативы

Быть или не быть... Извечная альтернатива! После прочтения этой части, вы сможете самостоятельно записать гамлетовскую альтернативу при помощи регулярных выражений. Сначала как всегда разберемся с данными, которые надо обработать. У Гамлета был выбор между быть (be) и не быть (not to be). В любом случае он получал на выходе какое-то решение, которое равнялось true :) Чтобы описать условие выбора при помощи регулярных выражений надо сначала определиться между чем и чем выбирать будем. В случае Гамлета мы выбираем между группами литералов, одна группа состоит из двух литералов идущих друг за другом b и e, вторая группа состоит из девяти литералов идущих друг за другом: n, o, t, \s, t, o, \s, b, e

Понятно, что \s представляет собой один пробел. Я, кажется, употребил новое слово, на которое вы не обратили свое внимание: группа литералов. Группа литералов - последовательность символов, которые описаны либо символьными классами, либо собственно литералами. Группу литералов описывают в круглых скобках, они же сохраняют совпавшую группу литералов в специальных переменных, о которых я писал в предыдущей статье. Вот примеры групп литералов:

(be)
(not\sto\sbe)

Теперь вернемся к выбору из двух групп литералов: (be)|(not\sto\sbe) вот эта палочка | между группами литералов и есть условие выбора, читается как 'или'. Теперь представьте, что ответ Гамлета обрабатывает компьютер, которому известно, что может быть только два ответа либо "быть" либо "не быть". Все остальные ответы просто не принимаются за ответы как таковые. Пишем регулярное выражение проверки ответов Гамлета:

preg_match("/^(be)|(not\sto\sbe)$/", $alternate, $answer);

Регулярное выражение совпадет в случае

  • если $alternate равно "be"
  • если $alternate равно "not to be"

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

Пример выбора из двух литералов: s(o|u)n совпадет как с son, так и с sun.

Пример выбора из двух групп литералов: (son)|(sun) аналогично совпадет с son и с sun

В случае с выбором между группами литералов, либо между одиночными литералами, литералы объединяются при помощи круглых скобок, но в первой статье в части Запомнить все, я сказал, что все, что заключается в круглые скобки, запоминается в специальных переменных. Запоминается, значит использует ресурс "память". Что делать, если нам не нужно запоминать выделенные группы литералов, на надо их сгруппировать? Надо заставить скобки не запоминать! Делается это при помощи последовательности ?: вот так: (?:be)|(?:not\sto\sbe) теперь ни группа литералов "be" ни группа литералов "not to be" не будут заноситься в переменные \1, \2 (Perl $1, $2), но зато будут отлично группироваться.

Понятно, что, если надо поставить литерал "|", то надо перед ним поставить спецсимвол, который будет обозначать, что в данном случае вертикальная черта является литералом. Этим спецсимволом служит обратный слеш: \|

Пример к главе

У администратора есть список пользователей, которые генерирует программа, либо утилита, генерирует она этот список в формате:

фамилия имя отчество
фамилия и о
фамилия и.о.

Как всегда боссу требуется какая-то статистика, но его абсолютно не интересуют ни имена ни отчества, ему нужна фамилия и инициалы в виде: фамилия ио.

Надо что-то делать... я сделал это вот так (цикл перебора по всем строкам, которые генерирует утилита, напишете сами):

preg_match("/([^\s]+)\s+([^\s.])[^\s.]*(?:\s|\.)([^\s.])[^\s.]*/",$income_str,$out_arr);
print_r($out_arr);

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

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

  • [^\s] - любой символ, который не является пробелом! включая символ новой строки, можно было написать \S без крышки вначале символьного класса, но это на любителя.
  • [^\s]+ - минимум один символ, который не является пробелом, т.е. фамилию мы уже описали.
  • \s+ - минимум один пробел между фамилией и именем

Для обозначения имени исползуется несколько способов:

  • полное имя и после него пробел
  • первая буква имени и после него пробел
  • первая буква имени и после него точка

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

  • [^\s] - все что не пробел, первый обязательный символ имени (сокращения)
  • [^\s.] - все что не пробел и не точка, в символьном классе точка - литерал
  • [^\s.]* - в случае полного имени, это будет означать, что надо найти все, что не точка и не пробел и идет после обязательного первого символа имени, который мы описали выше

Получается, что имя мы описали вот такой конструкцией: [^\s.][^\s.]* но первую букву имени (даже если там сокращение) по условию задачи надо запомнить: ([^\s.])[^\s.]*

Что идет после имени? в любом случае там идет либо пробел, либо точка. Значит нужно пройти этот участок так, чтобы выполнилось условие выбора из двух символов: (\s|\.) - выбор между двумя литералами осуществляется путем группировки их в круглые скобки и написания между литералами вертикальной черты. Если в строке гарантировано будет только один пробел, то можно так и оставить, если вы не уверены, поставьте символ '+' после \s: (\s+|\.)

Вам нужно запоминать что стояло между фамилией и отчеством, пробел или точка? Мне нет! Поэтому из сохраняющих и группирующих скобок делаем только группирующие: (?:\s|\.)

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

Наверх

Регулярные выражения, Часть II

Использование позиционных проверок

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

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

Пример: Мой директор спрашивает, как найти такой-то банк в Киеве. Я знаю, что объяснить местоположение банка голландсокму директору, используя транслитерированные русские названия, невозможно! Но я программист и объясняю, что нужный банк находится на центральной площади. В тоже время площадь описываю словами, что с одной стороны стоит женщина на огромной колонне, а с другой стороны Мак Дональдс. Даже некиевляне догадались, что я имею в виду Площадь Независимости.

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

Каждый раз при помощи регулярных выражений происходит поиск совпадений. Поиск проводится по строке, в строке ищется подстрока. Теперь научимся задавать условие, что должно совпасть перед подстрокой и после нее. Условие совпадения, которое идет перед подстрокой, называют ретроспективной проверкой, условие совпадения, которое идет после подстроки, называется опережающей проверкой. Можно объяснить также, что ретроспективная проверка совпадает слева, опережающая - справа.

Позитивные и негативные проверки

Хорошо, если можно описать, что следует перед искомой подстрокой, а что после. А что если нужно описать, что НЕ идет перед либо после искомой подстроки? Для этого опережающую и ретроспективную проверку делят еще каждую на две:

  • Опрежающая позитивная, опережающая негативная проверки.
  • Ретроспективная позитивная, ретроспективная негативная проверка.

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

Ретроспективная проверка

Позитивная ретроспективная проверка либо проверка слева описывается специальной последовательностью символов: (?<=) скобки группируют условие, вопросительный знак, показывает, что содержимое скобок надо рассматривать не как группировку символов, чтобы их запомнить, а группировку символов для выполнения над выражением внутри скобок других действий. Вспомним, что просто скобки описывают группу символов и запоминают в переменные все, что в них находится, а скобки с вопросом и двоеточием указывают на специальное действие: группировка без запоминания. Символ(ы) после вопрсительного знака воспринимаются как указание, какое именно действие надо выполнить. Итого: знак больше-равно идущий после вопросительного знака, который стоит внутри круглых скобок сразу после открывающейся скобки показывает, что последующие символы являются позитивной ретроспективной проверкой, т.е. условием, что должно обязательно идти перед подстрокой, которую мы ищем.

Ретроспективная негативная проверка описывается подобной последовательностью символов: (?<!) Посмотрите, что знак равно сменился на восклицательный знак и проверка сразу поменяла "полярность" т.е. из позитивной превратилась в негативную. Логика простая если вспомнить, что для проверки условия "неравно", отрицания равно, во многих языках программирования используется последовательность символов: !=

Опережающая проверка

Если вы поняли описание ретроспективной проверки, то понять суть опережающей проверки вам будет несложно.

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

Для описания негативной опережающей проверки используется последовательность символов: (?!) тоже самое, только знак равно заменили отрицанием - восклицательным знаком.

Пример №1

Часто возникает проблема по парсингу интересующих программиста данных из HTML, который не всегда хорошего качества, все было бы терпимо, если бы еще не вставки на javascript'е, вот пример такого текста:

<TD>20.02<BR>05:30
<TD class=l>Товар 1<BR>Товар 2
<TD><B>35</B>
<TD><A href="http://ссылка/" id=sfsd32dfs
onclick="return m(this)">26.92</A><BR><A href="http://ссылка/"
id=r3_3143svsfd onclick="return m(this)">27.05</A>
<TD><B>270.5</B>
</TR>

Те цифры, которые написаны через точку, являются ценами. Задача состоит в том, чтобы собрать все цены, которые находятся между тегами <a>... </a>

Видим, что помимо цен между заданными тегами, есть такие, которые идут сразу после тега <TD>, а также стоят между тегами <B>...</B>. Ясно, что описать достаточно точно содержимое атрибутов тега <A> представляется задачей не самой легкой, поэтому надо ее упростить! Любой тег имеет закрывающий знак '>', наша задача описать, что этот знак идет перед ценой, но так как перед ценой может стоять тег <B> и тег <TD>, но эти цены нам не нужны. Каким образом мы узнаем, что цена стоит между тегами <A>...</A>? По тегу, который идет после цены, если это не тег </B>, то это будет либо тег </A> либо <BR>, а так же по тегу перед ценой если этот тег <TD>.

Путем таких размышлений мы пришли к выводу, что должно стоять справа, а что должно стоять слева искомой строки, которая описывается как цифры, разделенные точкой: \d*\.\d*

То, что должно совпасть слева, мы описали как символ '>', записываем: (?<=>) - выглядит немного странно, но совпадение слева записывается вот так (?<=), а внтури него после ?<= идет символ '>'

То, что должно совпасть справа описывается (?=) внутри мы пишем </A>.

Теперь опишем, что не должно стоять перед ценой: (?<!<TD>) перед ценой не должен стоять тег <TD>, это и есть негативная ретроспективная проверка.

При помощи негативной опережающей проверки опишем, что не должно стоять справа цены: (?!<\/B>) справа от цены не должен стоять тег </B>.

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

preg_match_all("/(?<!<TD>)(?<=>)\d*\.\d*(?!<\/B>)(?=<\/A>)/", $string, $matches);
print_r($matches);

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

  1. Написанные друг за другом проверки применяются независимо друг от друга в одной точке, не меняя ее. Естественно, что совпадение будет найдено, если все проверки совпадут. В нашем примере это были точки перед и после цены. С точки зрения логики применения проверок нет никакой разницы, будет ли стоять проверка на тег <TD> перед проверкой на знак '>'. Правда, с точки зрения оптимизации первой позиционной проверкой должна идти та, которая имеет наибольшую вероятность несовпадения.
  2. Совпавшие значения ретроспективных проверок не сохраняются. Т.е. если в нашем примере совпадает опережающая проверка, которая указывает, что после цены идет тег </A>, то сам тег </A>, который заключен в конструкцию (?=) не будет запоминаться в специальных перменных /1,/2 и т.д. Сделано это из-за того, что позиционная проверка совпадает не со строкой, а с местом в строке (она описывает место, где произошло совпадение, а не символы, которые совпали).
  3. Нужно указать что PCRE не позволяет делать проверки на совпадение текста произвольной длинны. То есть нельзя делать, например, такую проверку: /(?<=\d+)
Механизм поиска совпадения в ретроспективной проверке реализован так, что при поиске механизму должна подаваться строка фиксированной длины, для того, чтобы в случае несовпадения, механизм мог вернуться назад на фиксированое количество символов и продолжить поиск совпадений в других позиционных проверках. Думаю, что сразу это понять сложно, но представьте себе как происходит поиск совпадения в части (?)(?<=>) вышеописанного регулярного выражения. Берется строка, в которой происходит поиск, отсчитывается от начала столько символов, сколько символов будет в совпадении позиционной проверки, в нашем варианте это 4: <, T, D, > с этого места происходит "заглядывание назад" (ретроспективные проверки на английском языке звучит как lookbhind assertions), т.е. все предыдущие 4 символа проверяются на совпадение со строкой <TD>, если механизм не нашел совпадения, то ему надо вернуться на 4 символа назад, выполнить тоже самое с проверкой (?<=>), т.е. отсчитать один символ, "заглянуть" назад, попробовать найти проверку предыдущего символа с символом '>'. Представьте себе, что условие совпадения состоит из строки нефиксированной длинны: (??) подобная запись должна означать, что перед ценой, не должен стоять тег <TD> в количестве максимимум один экземпляр (либо вообще не стоять). Вот и получается, что после того, как механизм отсчитает 4 символа от начала, он проверит на совпадение с <TD>, но в условии указано, что тега может и не быть вообще, тогда возникает вопрос, на сколько знаков верунться назад, чтобы проверить на совпадение другие проверки. На 4 или вообще не возвращаться?

Сразу возникает вопрос, а зачем идти вперед, чтобы потом "заглянуть" назад? Делается это для того, чтобы в случае совпадения всех проверок сразу же начать проверку тех символов, которые идут после позиционных проверок.

Пример №2

Как-то мне нужно было получить все изображения, которые использовались на сайте. Что для этого надо сделать? Правильно, надо в браузере нажать на "Сохранить как", указать куда сохранить страницу. Появится файл с исходным кодом страницы и папка с изображениями. Но вы никогда не сохраните в эту папку изображения, которые прописаны в стилях объектов по крайней мере в эксплорере:

style="background-image:url(/editor/em/Unlink.gif);"

Для проведения вышеописанной операции надо:

  1. попросить хозяина хоста использовать контент, размещенный на его сайте.
  2. найти в тексте все строки, подобные приведенной выше, и выделить в них относительный путь к файлу
  3. сформировать файл в котором будут выводиться изображения при помощи <img src=полный_путь_к_изображению>

Делаем: В переменную $content получаем исходный код страницы. А дальше используя регулярные выражения ищем относительные пути, которые прописаны в стилях. Каждый раз, когда я описываю, как я реализовал пример, я сначала тщательно описываю, что ищем, и тщательно описываю, в каком контексте происходит поиск. Проанализировав исходный код страницы стало понятно, что кроме как в описании стилей относительные пути к изображениям нигде не используются. Слева от относительного пути идет последовательность символов: url(

Справа от относительного пути стоит закрывающаяся круглая скобка.

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

Начнем с простого. Символы латинсокого алфавита, цифры, точка и слеш описываются символьным классом: [a-z.\/] их может быть сколько угодно, на самом деле больше 3 (имя файла, минимум один символ, точка, расширение, минимум один символ), но в данном случае, зная контекст, это некритично, поэтому указываем квантификатор * [a-z.\/]*

Слева должны идти 'url(' и мы это описываем при помощи позитивной ретроспективной проверки: (?<=url()

Но обратите внимание на то, что скобка в регулярных выражениях является спецсимволом группировки, поэтому чтобы она стала символом, надо перед ней поставить другой спецсимвол - слеш. (?<=url\()

Справа от относительного пути должна стоять закрывающаяся круглая скобка. Это условие описывается при помощи позитивной опережающей проверки: (?=\))

Как видите, перед одной из скобок стоит слеш, что означает, что она интепретируется не как спецсимвол, а как литерал.

Ниже приведен полный код на PHP, который выполняет все действия, кроме вопроса о разрешении использовать контент:

preg_match_all("/(?<=url\()[a-z.\/]*(?=\))/i", $content, $matches);
foreach($matches[0]
as $item) {
echo
"<img src =
http://www.a???s.ru".$item.">";
}

Когда и почему не следует использовать регулярные выражения

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

Описание проблемы поставленной на форуме было таким: Есть некая страница, созданная в результате работы шаблонизатора, в ней присутствуют элементы типа <input type=checkbox>, авторизированный пользователь работает с этой страницей и отмечает какие-то чекбоксы <input type=checkbox checked>, после отправки формы на сервер страницы снова генерируются шаблонизатором и программист делает так, что эта страница сохраняются в кеше, причем уже с отмеченными чекбоксами.

Задача программиста: Ему надо по результатам отправленной формы запомнить выделенные чекбоксы и при последующем обращении этого пользователя к этой странице выводить его выбор с уже отмеченными элементами.

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

Его аргументация: Так быстрее работает чем обработка первоначального шаблона.

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

Представим себе текст, в котором n символов, а также регулярное выражение, в котором

  1. есть три условия выбора part1|part2|part3|
  2. каждая из частей parti состоит из символьных классов, а также заданных последовательностей литералов.
  3. максимальная длинна совпадения регулярного выражения равна m символов.
  4. программист использовал функцию preg_replace, мы знаем принцип работы механизма регулярных выражений, а значит можем повторить его работу.

Приведу эти шаги одновременно с аргументами против подхода, который хотел применить программист, которые из них вытекают:

  1. берем символ из текста с индексом 1 пытаемся применить регулярное выражение к тексту, который начинается с этого символа. Перебираем каждый символ от начального и сравниваем его с шаблоном (подходит ли).
  2. следуя каждой инструкции выбора в регулярном выражении мы можем совершить максимум три выбора и перебрать m последующих символов для КАЖДОГО i-го символа текста.
  3. несложно посчитать, какое количество символов будет перебрано впустую, если совпадение не будет найдено.
  4. закешированный документ уже является результатом работы регулярного выражения (многие шаблонизаторы работают на регулярных выражениях) таким образом просто напросто дополнительно вызывается механизм регулярных выражений, чтобы отпарсить сырой документ-полуфабрикат, вместо того, чтобы сразу его сделать готовым за один проход.
  5. если сравнить регулярные выражения, на которых работают шаблонизаторы и регулярное выражение, которое получится у нашего программиста, то сразу будет видно, что первые проще! По этой причине совпадения будут находиться быстрее.
  6. Часто работа регулярных выражений в шаблонизаторе будет происходить с маленькими участками текста (не секрет, что страницы могут собираються из маленьких кусочков), а не сразу к сгенерированной странице.

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

В заметке описан случай, когда человек, знал как пользоваться регулярными выражениями, а также знал, как использовать в своей работе шаблонизатор, т.е. у него был выбор, он просто не мог остановиться на одном из вариантов. Есть категория начинающих программистов, которая увидев, кучу значков, при помощи которых решаются много проблем со строками, воздвигают себе кумира в виде статуи этому "волшебному средству" и даже не догадываются, что их задача по замене слова на другое слово в строке решается простой функцией str_replace(). Не понимая, как работают регулярные выражения, они приходят на форум и спрашивают, как заменить abcd на asdf при помощи preg_replace и очень обижаются, когда дают ссылку на str_replace в мануале на http://www.php.net/. Это наверное самый распространенный случай неправильного применения регулярных выражений, когда их можно заменить при помощи функций работы со строками.

Наверх

Хочу добавить описание граблей на которые я наступал:
1. Для проверки пустого значения используется комбинация - /^$/.
2. Если хотите использовать альтернативный поиск от начала и до конца строки используйте /(^[...]*$)|(^[...]$)/, а не /^([...])|([...])$/. Вторая комбинация почему-то не отрабатывается правильно.

К автору : Спасибо, статья хорошая.

К содержанию


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

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

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

Регулярные выражения представляют собой образцы для поиска заданных комбинаций символов в текстовых строках и замены их на другие комбинации символов (эти операции называются соответственно сопоставление с образцом и подстановка). Регулярное выражение в языке PERL имеет вид

/pattern/modifiers

Здесь pattern — это строка, задающая регулярное выражение, а modifiers — необязательные однобуквенные модификаторы, уточняющие правила использования этого регулярного выражения.

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

Таблица. Метасимволы в регулярных выражениях
Символ | Описание
\ | Для символов, которые обычно трактуются буквально, означает, что следующий символ является метасимволом. Например, /n/ соответствует букве n, а /\n/ соответствует символу перевода строки. Для метасимволов означает, что символ должен пониматься буквально. Например, /^/ означает начало строки, а /\^/ соответствует просто символу ^. /\\/ соответствует обратной косой черте \.
^ | Соответствует началу строки (ср. модификатор m).
$ | Соответствует концу строки (ср. модификатор m).
. | Соответствует любому символу, кроме разрыва строки (ср. модификатор s).
* | Соответствует повторению предыдущего символа нуль или более раз.
+ | Соответствует повторению предыдущего символа один или более раз.
? | Соответствует повторению предыдущего символа нуль или один раз.
(pattern) | Соответствует строке pattern и запоминает найденное соответствие.
x|y | Соответствует x или y.
{n} | n — неотрицательное число. Соответствует ровно n вхождениям предыдущего символа.
{n,} | n — неотрицательное число. Соответствует n или более вхождениям предыдущего символа. /x{1,}/ эквивалентно /x+/. /x{0,}/ эквивалентно /x*/.
{n,m} | n и m — неотрицательные числа. Соответствует не менее чем n и не более чем m вхождениям предыдущего символа. /x{0,1}/ эквивалентно /x?/.
[xyz] | Соответствует любому символу из заключенных в квадратные скобки.
[^xyz] | Соответствует любому символу, кроме заключенных в квадратные скобки.
[a-z] | Соответствует любому символу в указанном диапазоне.
[^a-z] | Соответствует любому символу, кроме лежащих в указанном диапазоне.
\a | Соответствует символу звонок (BEL).
\A | Соответствует только началу строки, даже с модификатором m.
\b | Соответствует границе слова, т. е. позиции между \w и \W в любом порядке.
\B | Соответствует любой позиции, кроме границы слова.
\с X | Соответствует символу Ctrl+X. Например, /\cI/ эквивалентно /\t/.
\C | Соответствует одному байту, даже при директиве use utf8.
\d | Соответствует цифре. Эквивалентно [0-9].
\D | Соответствует нецифровому символу. Эквивалентно [^0-9].
\e | Соответствует символу escape (ESC).
\E | Конец преобразований \L, \Q, \U.
\f | Соответствует символу перевода формата (FF).
\G | Соответствует позиции в строке, равной pos().
\l | Преобразует следующий символ в нижний регистр.
\L | Преобразует символы в нижний регистр до \E.
\n | Соответствует разрыву строк.
\p property | Соответствует символам Unicode, обладающим свойством property. Если property задается несколькими символами, используйте синтаксис \p{property}.
\P property | Соответствует символам Unicode, не обладающим свойством property. Если property задается несколькими символами, используйте синтаксис \P{property}.
\Q | Добавляет символ "\" перед метасимволами до \E.
\r | Соответствует символу возврата каретки (CR).
\s | Соответствует символу пробела. Эквивалентно /[ \f\n\r\t]/.
\S | Соответствует любому непробельному символу. Эквивалентно /[^ \f\n\r\t]/.
\t | Соответствует символу табуляции (HT).
\u | Преобразует следующий символ в верхний регистр.
\U | Преобразует символы в верхний регистр до \E.
\w | Соответствует латинской букве, цифре или подчеркиванию. Эквивалентно /[A-Za-z0-9_] /.
\W | Соответствует любому символу, кроме латинской буквы, цифры или подчеркивания. Эквивалентно /[^A-Za-z0-9_] /.
\X | Соответствует последовательности символов Unicode из основного символа и набора диакритических значков. Эквивалентно выражению /C<(?:\PM\pM*)>/.
\z | Соответствует только концу строки, даже с модификатором m.
\Z | Соответствует только концу строки или разрыву строк в конце строки, даже с модификатором m.
\n | n — положительное число. Соответствует n-ной запомненной подстроке. Если левых скобок до этого символа меньше, чем n, и n > 9, то эквивалентно \0n.
\0 n | n — восьмеричное число, не большее 377. Соответствует символу с восьмеричным кодом n. Например, /\011/ эквивалентно /\t/.
\x n | n — шестнадцатеричное число, состоящее из двух цифр. Соответствует символу с шестнадцатеричным кодом n. Например, /\x31/ эквивалентно /1/.
\x{n} | n — шестнадцатеричное число, состоящее из четырех цифр. Соответствует символу Unicode с шестнадцатеричным кодом n. Например, /\x{2663}/ эквивалентно /♣/.

2. Модификаторы

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

i
Игнорирует регистр символов при сопоставлении с образцом. При использовании директивы use locale приведение символов к одному регистру производится с учетом национальной настройки.
m
Рассматривает исходную строку как буфер из нескольких строк текста, разделенных разрывами строк. Это означает, что метасимволы ^ и $ соответствуют не только началу и концу всей строки, но и началу и концу строки текста, ограниченной разрывами строк.
s
Рассматривает исходную строку как единую строку текста, игнорируя разрывы строк. Это означает, что метасимвол . соответствует любому символу, включая разрыв строки.
x
Разрешает использование пробелов и комментариев. Пробелы, не имеющие предшествующего символа \ и не заключенные в [], игнорируются. Символ # начинает комментарий, который также игнорируется.

3. Классы символов Unicode и POSIX

Мы можем использовать в регулярных выражениях синтаксис

[:class:]

где class задает название класса символов POSIX, т. е. мобильного стандарта на язык C. При использовании директивы use utf8 вместо классов POSIX можно использовать классы символов Unicode в конструкции

\p{class}

В следующей таблице сведены все классы символов POSIX, соответствующие классы символов Unicode и метасимволы, если они есть.

Таблица. Классы символов
POSIX | Unicode | Метасимвол | Описание
alpha | IsAlpha |  | Буквы
alnum | IsAlnum |  | Буквы и цифры
ascii | IsAscii |  | Символы ASCII
cntrl | IsCntrl |  | Управляющие символы
digit | IsDigit | \d | Цифры
graph | IsGraph |  | Буквы, цифры и знаки пунктуации
lower | IsLower |  | Строчные буквы
print | IsPrint |  | Буквы, цифры, знаки пунктуации и пробел
punct | IsPunct |  | Знаки пунктуации
space | IsSpace | \s | Символы пробела
upper | IsUpper |  | Прописные буквы
word | IsWord | \w | Буквы, цифры и подчеркивание
xdigit | IsXDigit |  | Шестнадцатеричные цифры

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

/\d+/
/[:digit:]+/
/\p{IsDigit}+/  # use utf8

Для указания того, что символ не принадлежит к заданному классу, используются конструкции

[:^class:]
\P{class}

Например, следующие выражения имеют одинаковый смысл:

[:^digit:]  \D  \P{IsDigit}
[:^space:]  \S  \P{IsSpace}
[:^word:]   \W  \P{IsWord}

4. Запоминание подстрок

Использование круглых скобок в регулярном выражении приводит к тому, что подстрока, соответствующая образцу в скобках, запоминается в специальном буфере. Для доступа к n-ной запомненной подстроке внутри регулярного выражения используется конструкция \n, а вне него — $n, где n может принимать любые значения, начиная с 1. Однако, следует помнить, что PERL использует выражения \10, \11 и т. д. как синонимы для восьмеричных кодов символов \010, \011 и т. д. Неоднозначность здесь разрешается так. Символ \10 считается обращением к 10-й запомненной подстроке, если перед ним в регулярном выражении стоит не менее десяти левых круглых скобок; в противном случае, это символ с восьмеричным кодом 10. Метасимволы \1, … \9 всегда считаются обращениями к запомненным подстрокам. Примеры:

if (/(.)\1/) { # ищем первый повторяющийся символ
  print "'$1' - первый повторяющийся символ\n";
}

if (/Time: (..):(..):(..)/) { # извлекаем компоненты времени
  $hours = $1;
  $minutes = $2;
  $seconds = $3;
}

Помимо переменных $1, $2, … есть еще несколько специальных переменных, в которых сохраняются результаты последней операции с регулярным выражением, а именно:

Переменная | Описание
$& | Последняя найденная подстрока.
$` | Подстрока перед последней найденной подстрокой.
$' | Подстрока после последней найденной подстроки.
$+ | Последняя запомненная подстрока.

Приведем пример:

'AAA111BBB222'=~/(\d+)/;
print "$`\n";   # AAA
print "$&\n";   # 111
print "$'\n";   # BBB222
print "$+\n";   # 111

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

5. Расширенные образцы

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

(?#text)
Комментарий. Вся конструкция игнорируется.
(?modifiers-modifiers)
Включает или выключает заданные модификаторы. Модификаторы, стоящие до символа -, включаются, стоящие после него — выключаются. Пример:
if (/aaa/) { … }        # сопоставление с учетом регистра
if (/(?i)aaa/) { … }    # сопоставление без учета регистра
(?:pattern)
(?modifiers-modifiers:pattern)
Позволяет группировать подвыражения регулярного выражения без запоминания найденного соответствия. Вторая форма дополнительно включает или выключает заданные модификаторы. Например, выражение /ко(?:т|шка)/ — это краткая запись выражения /кот|кошка/.
(?=pattern)
Соответствие с заглядыванием вперед без запоминания найденного соответствия. Например, выражение /Windows (?=95|98|NT|2000)/ соответствует "Windows" в строке "Windows 98", но не соответствует в строке "Windows 3.1". После сопоставления поиск продолжается с позиции, следующей за найденным соответствием, без учета заглядывания вперед.
(?!pattern)
Несоответствие с заглядыванием вперед без запоминания найденного соответствия. Например, выражение /Windows (?!95|98|NT|2000)/ соответствует "Windows" в строке "Windows 3.1", но не соответствует в строке "Windows 98". После сопоставления поиск продолжается с позиции, следующей за найденным соответствием, без учета заглядывания вперед.
(?<=pattern)
Соответствие с заглядыванием назад без запоминания найденного соответствия. Например, выражение /(?<=\t)\w+/ соответствует слову, следующему за символом табуляции, и символ табуляции не включается в $&. Фрагмент, соответствующий заглядыванию назад, должен иметь фиксированную ширину.
(?<!pattern)
Несоответствие с заглядыванием назад без запоминания найденного соответствия. Например, выражение /(?<!\t)\w+/ соответствует слову, перед которым нет символа табуляции. Фрагмент, соответствующий заглядыванию назад, должен иметь фиксированную ширину.

6. Операции с регулярными выражениями

До сих пор мы заключали регулярные выражения в символы //. На самом деле символы-ограничители регулярного выражения определяются q-операцией, которую мы к ним применяем. В этом разделе подробно описаны все операции языка PERL с регулярными выражениями.

6.1. Сопоставление с образцом

Синтаксис: /pattern/modifiers
           m/pattern/modifiers

Эта операция сопоставляет заданную строку с образцом pattern и возвращает истину или ложь в зависимости от результата сопоставления. Сопоставляемая строка задается левым операндом операции =~ или !~, например:

$mynumber = '12345';
if ($mynumber =~ /^\d+$/) { # если строка $mynumber состоит из десятичных цифр, то…
  ...
}

Если строка не задана, то производится сопоставление с содержимым специальной переменной $_. В частности, предыдущий пример можно переписать так:

$_ = '12345';
if (/^\d+$/) {
  ...
}

Помимо стандартных, здесь могут употребляться следующие модификаторы:

Модификатор | Описание
c | Не сбрасывать позицию поиска при неудачном сопоставлении (только с модификатором g).
g | Глобальное сопоставление, т. е. поиск всех вхождений образца.
o | Компилировать регулярное выражение только один раз.

Если регулярное выражение заключено в //, то начальное m необязательно. Конструкция с начальным m позволяет использовать в качестве ограничителей регулярного выражения любые символы, допустимые в q-операциях. Полезные частные случаи:

  • Если ограничителями являются символы '', то интерполяция строки pattern не производится. В остальных случаях происходит интерполяция образца и если он содержит переменные, то при каждом сопоставлении производится его компиляция. Чтобы избежать этого, используйте модификатор o (разумеется, если вы уверены, что значения переменных, входящих в образец, остаются неизменными).
  • Если ограничителями являются символы ??, то применяется правило единственного сопоставления.

Если pattern является пустой строкой, то вместо него используется последнее успешно сопоставленное регулярное выражение.

Если не задан модификатор g и результат сопоставления присваивается списку, то при неудачном сопоставлении возвращается пустой список. Результат удачного сопоставления зависит от наличия круглых скобок в образце. Если их нет, то возвращается список (1). В противном случае возвращается список, состоящий из значений переменных $1, $2 и т. д., т. е. список всех запомненных подстрок. Следующий пример

($w1, $w2, $rest) = ($x =~ /^(\S+)\s+(\S+)\s*(.*)/);

заносит в переменную $w1 первое слово строки $x, в переменную $w2 ее второе слово, а в переменную $rest — остаток этой строки.

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

$_ = "12:23:45";
@result = /\d+/g;
foreach $elem (@result) {
  print "$elem\n";
}

выведет на экран строки 12, 23 и 45.

В скалярном контексте сопоставление с модификатором g каждый раз ищет следующее соответствие образцу и возвращает истину или ложь в зависимости от результата поиска. Позиция в строке после последнего сопоставления может быть считана или изменена функцией pos(). Неудачный поиск обычно сбрасывает позицию поиска в нуль, но мы можем избежать этого, добавив модификатор c. Изменение строки также приводит к сбросу позиции поиска в ней.

Дополнительные возможности предоставляет метасимвол \G, который имеет смысл только в сочетании с модификатором g. Этот метасимвол соответствует текущей позиции поиска в строке. Использование конструкции m/\G…/gc удобно, в частности, для написания лексических анализаторов, выполняющих различные действия для встреченных в анализируемом тексте лексем. Следующий пример

$_ = 'Word1, word2, and 12345.';
LOOP:
  {
    print("number "),  redo LOOP if /\G\d+\b[,.;]?\s*/gc;
    print("word "),    redo LOOP if /\G[A-Za-z0-9]+\b[,.;]?\s*/gc;
    print("unknown "), redo LOOP if /\G[^A-Za-z0-9]+/gc;
  }

выведет на экран строку word word word number.

6.2. Единственное сопоставление с образцом

Синтаксис: ?pattern?
           m?pattern?

Эта конструкция полностью аналогична конструкции m/pattern/ с единственным отличием: успешное сопоставление с образцом выполняется только один раз между вызовами функции reset(). Это удобно, например, когда нам нужно найти только первое вхождение образца в каждом файле из просматриваемого набора, например:

while (<>) {
  if (?^$?) {
    ...         # обработать первую пустую строку файла
  }
} continue {
  reset if eof; # сбросить статус ?? для следующего файла
}

6.3. Создание регулярного выражения

Синтаксис: qr/string/modifiers

Эта конструкция создает регулярное выражение с текстом string и модификаторами modifiers и компилирует его. Если ограничителями являются символы '', то интерполяция строки string не производится. В остальных случаях происходит интерполяция образца и если он содержит переменные, то при каждом сопоставлении производится его компиляция. Чтобы избежать этого, используйте модификатор o (разумеется, если вы уверены, что значения переменных, входящих в образец, остаются неизменными).

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

$re = qr/\d+/;
$string =~ /\s*${re}\s*/; # включение в другое регулярное выражение
$string =~ $re;           # самостоятельное использование
$string =~ /$re/;         # то же самое

$re = qr/$header/is;
s/$re/text/;              # то же, что s/$header/text/is

Подстановка

Синтаксис: s/pattern/string/modifiers

Эта операция сопоставляет заданную строку с образцом pattern и заменяет найденные фрагменты на строку string. Она возвращает количество произведенных замен или ложь (точнее, пустую строку), если сопоставление закончилось неудачей. Сопоставляемая строка задается левым операндом операции =~ или !~. Она должна быть скалярной переменной, элементом массива или элементом ассоциативного массива, например:

$path = '/usr/bin/perl';
$path =~ s|/usr/bin|/usr/local/bin|;

Если строка не задана, то операция подстановки производится над специальной переменной $_. В частности, предыдущий пример можно переписать так:

$_ = '/usr/bin/perl';
s|/usr/bin|/usr/local/bin|;

Помимо стандартных, здесь могут употребляться следующие модификаторы:

Модификатор | Описание
e | Обрабатывать string как выражение PERLа.
g | Глобальная подстановка, т. е. замена всех вхождений образца.
o | Компилировать регулярное выражение только один раз.

Мы можем использовать вместо // любой символ, допустимый в q-операциях. Если pattern заключен в парные скобки, то string должен иметь собственную пару ограничителей, например s(foo)[bar] или s<foo>/bar/.

Если ограничителями являются символы '', то интерполяция строки pattern не производится. В остальных случаях происходит интерполяция образца и если он содержит переменные, то при каждом сопоставлении производится его компиляция. Чтобы избежать этого, используйте модификатор o (разумеется, если вы уверены, что значения переменных, входящих в образец, остаются неизменными).

Если pattern является пустой строкой, то вместо него используется последнее успешно сопоставленное регулярное выражение.

По умолчанию производится замена только первого найденного образца. Чтобы заменить все вхождения образца в строке, нужно использовать модификатор g.

Модификатор e указывает, что string является выражением. В этом случае к string сначала применяется функция eval(), а затем производится подстановка. Пример:

$_ = '123';
s/\d+/$&*2/e;  # $_ = '246'
s/\d/$&*2/eg;  # то же самое

Приведем еще несколько типичных примеров использования операции подстановки. Удаление комментариев вида /*…*/ из текста Java- или C-программы:

$program =~ s {
  /\*     # Начало комментария
  .*?     # Минимальное количество символов
  \*/     # Конец комментария
}[]gsx;

Удаление начальных и конечных пробелов в строке $var:

for ($var) {
  s/^\s+//;
  s/\s+$//;
}

Перестановка двух первых полей в $_. Обратите внимание, что в строке замены используются переменные $1 и $2, а не метасимволы \1 и \2:

s/([^ ]*) *([^ ]*)/$2 $1/;

Замена табуляций на пробелы с выравниванием по колонкам, кратным восьми:

1 while s/\t+/' ' x (length($&)*8 - length($`)%8)/e;

6.5. Транслитерация

Синтаксис: tr/list1/list2/modifiers
           y/list1/list2/modifiers

Транслитерация состоит в замене всех символов из списка list1 соответствующими символами из списка list2. Она возвращает количество замененных или удаленных символов. Списки должны состоять из отдельных символов и/или диапазонов вида a-z. Преобразуемая строка задается левым операндом операции =~ или !~. Она должна быть скалярной переменной, элементом массива или элементом ассоциативного массива, например:

$test = 'ABCDEabcde';
$test =~ tr/A-Z/a-z/; # замена строчных букв на прописные

Если строка не задана, то операция подстановки производится над специальной переменной $_. В частности, предыдущий пример можно переписать так:

$_ = 'ABCDEabcde';
tr/A-Z/a-z/;

Мы можем использовать вместо // любой символ, допустимый в q-операциях. Если list1 заключен в парные скобки, то list2 должен иметь собственную пару ограничителей, например tr(A-Z)[a-z] или tr<A-Z>/a-z/.

Обычно эта операция называется tr. Синоним y введен для фанатиков редактора sed и используется только ими. Транслитерация поддерживает следующие модификаторы:

Модификатор | Описание
c | Заменять символы, не входящие в list1.
d | Удалять символы, для которых нет замены.
s | Удалять повторяющиеся символы при замене.
U | Преобразовывать в/из кодировку UTF-8.
C | Преобразовывать в/из однобайтовую кодировку.

Модификатор c вызывает транслитерацию всех символов, не входящих в список list1. Например, операция tr/a-zA-Z/ /c заменит все символы, не являющиеся латинскими буквами, на пробелы.

По умолчанию, если list2 короче, чем list1, он дополняется последним своим символом, а если он пуст, то принимается равным list1 (это удобно для подсчета количества символов определенного класса в строке). Модификатор d изменяет эти правила: все символы из list1, которым нет соответствия в list2, удаляются из строки. Например, операция tr/a-zA-Z//cd удалит из строки все символы, не являющиеся латинскими буквами.

Модификатор s удаляет повторы: если несколько символов подряд заменились на один и тот же символ, то будет оставлено только один экземпляр этого символа. Например, операция tr/ / /s удаляет в строке повторяющиеся пробелы.

Модификаторы C и U предназначены для перекодировки символов из системной кодировки в UTF-8 и обратно. Первый из них указывает на исходную кодировку, а второй — на кодировку результата. Например, tr/\0-\xFF//CU перекодирует строку из системной кодировки в UTF-8, а tr/\0-\xFF//UC выполнит обратную перекодировку.

Транслитерация производится без интерполяции списков символов, поэтому для использования в ней переменных необходимо вызвать функцию eval(), например:

eval "tr/$oldlist/$newlist/";


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

Часто приходится слышать от разных разработчиков, познакомившихся с Perl, что основной и уникальной "фичей" этого языка являются регулярные выражения или просто "регэкспы" (сокращение от REGular EXPressions). Ну, во-первых, регулярные выражения не являются изобретением разработчиков Perl-а, просто язык изначально создавался для обработки текстов, а использование регулярных выражений (далее РВ) заметно упрощает эту задачу и РВ были слиты с синтаксисом Perl. На самом деле возможность использования РВ присутствует во многих программных системах, таких как awk, Python, Emacs и многих других, существуют библиотеки для работы с ними практически для всех языков и платформ. Хотя, для вышеупомянутых приверженцев Perl, РВ неизменно ассоциируются с чем-то слитым с синтаксисом языка, а никак не с библиотекой и фраза "А в .NET Framework реализованы регэкспы" вызывает изумленный ответ "Вау! Значит, в C# можно использовать РВ? Все, подайте мне сюда компилер". На самом деле, наличие библиотеки работы с РВ в .NET Framework позволяет использовать РВ в люблм языке, для которого существует компилятор, совместисый с .NET. Возможно даже написать интерпретатор языка Perl, который будет использовать эту библиотеку для выполнения кода. Причем возможность генерации напрямую MSIL-кода (MSIL - MicroSoft Indermediate Language) позволит заметно повысить эффективность (хотя этой возможностью нужно пользоваться осторожно, но об этом позже). Итак, для работы с РВ в .NET Framework присутствует целая библиотека классов, объявленных в пространстве имен System.Text.RegularExpressions. Разберем эти классы подробнее.

Первый класс, который мы разберем, это класс Regex. Regex реализует собственно парсер для РВ. Для разбора используется недетерминированный конечный автомат (Nondetermined Finite Automation) или НКА, который генерирует либо набор специальных инструкций по применению этого РВ, либо напрямую MSIL-код. Арзитектура (алгоритм и возможности РВ), принятая у Microsoft, хорошо балансирует между скоростью и возможностями, такими как обратные ссылки (backreferences) и откат (backtracking). Синтаксис соответствует исползуемому в Perl 5. В целом, Regex - неизменяемый (immutable) класс, что позволяет использовать один его экземпляр в нескольких потоках одновременно. При создании экземпляра указывается РВ, которое будет применять вновь созданный экземпляр:

Regex r = new Regex("\w+:\d+");

Этот фрагмент кода создает экземпляр класса Regex для разборки выражения, описывающего строки вида "abc23:0123", то есть состоящих из набора алфавитно-цифровах символов, затем двоеточия, и в конце - ряда цифр.

Основными возможностями Regex являются поиск и замена. Поиск (matching) - позволяет найти в переданной строке все неперекрывающиеся подстроки, соответствующие заданому РВ. Для поиска моджно использовать метод Match или коллекцию Matches. Поясним сказанное на примере:

        using System;
using System.Text.RegularExpressions;

public class RegexTest
{
    public static void Main()
    {
        Regex r = new Regex(@"abc");
        Match m = r.Match("adsfsdfsfabcafaabc");

        Console.WriteLine("Match: {0}", m);

        ////////////////////////////////////////////////////////////////////////////////////////////

        MatchCollection mc = r.Matches("abcdefabcabcabghdkeabc");

        foreach (Match mt in mc)
        {
            Console.WriteLine("> Match: {0}", mt);
        }

    }
}

Разберем код по порядку. Сначала создается экземпляр класса Regex, отвечающий выражению "abc", которое представляет собой просто строку "abc". Затем выполняется поиск подстрок, которым отвечает вышеуказанное выражение в строке "adsfsdfsfabcafaabc" и первое совпадение записывается в объект m класса Match, хранящего информацию о совпадении: текст найденной подстроки , позицию в которой она была найдена и некоторую дополнительную информацию, которая будет подробнее разобрана позже.

Вторая часть кода (она отделена комментарием) демонстрирует использование коллекции Matches: выполняется поиск подстрок, отвечающих выражению, описываемому созданным объектом Regex в строке "abcdefabcabcabghdkeabc", затем все совпадения заносятся в коллекцию соответствий (представляемую объектом mc класса MatchCollection). После этого на экран выводятся все соответствия, для чего используется перечислитель (enumerator). Я не зря обратил внимание на способ перебора всех соответствий - использование перечислителей может вызвать проблемы при многопоточном программировании - это практически единственный узкий момент при использовании пакета классов System.Text.ResularExpressions в многопоточых приложениях. В остальных случаях классы этого пакета устойчивы к одновременной работе в нескольких потоках (thread safe).

Как уже упоминалось выше, от класса Match можно получить некоторую дополнительную информацию. Первое, что мы разберем - это список сохраненных групп (captured groups). Поясним их смысл на примере: мы хотим получить адреса всех сотрудников, данные о которых перечислены в файле вида:
Name=John Smith;BirthDate=18.09.1976;Address:29, 7th st.

using System;
using System.Text.RegularExpressions;

public class GetAddress
{
    public static void Main()
    {
        Regex r = new Regex(@"Address:(?<address>[^;]+)");

        MatchCollection mc = r.Matches("Name=John Smith;BirthDate=18.09.1976;Address:29, 7th st.
");

        foreach (Match mt in mc)
        {
            Console.WriteLine("Address: {0}", mt.Groups["address"]);
        }
    }
}

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

[^;]+) сопоставляет части соответствующей подстроки, отвечающей выражению [^;]+ имя "address". То есть, если в строчке была найдена подстрока, соответствующая выражению "Address:(?
[^;]+)", то часть ее, соответствующая "[^;]+" будет сохранена в группе с именем "address". К этой группе можно обращаться не только внешним образом, но и в самом выражении, используя обратные ссылки (backreferences), о которых несколько подробнее будет сказано в дальнейшем. Группы можно выделить не только явным назначением имен. Имена даются автоматичекси всем выражениям, заключенным в скобки, причем им даются номерные имена, соответствующие вложенности озватывающих скобок. Доступ к таким группам извне осуществляется с помощью числового индексатора (m.Groups[1]). Группе с индексом 0 соответствует все выражение. Также можно перебрать все группы коллекции Groups (представляющей собой экземпляр класса GroupCollection) с помощью перечислителей.

Но и это еще не все… На самом деле каждая группа может иметь несколько захватов (captures) вследствие использования множителей (quantifiers). Опять же, вместо долгого и туманного описания того, что имеется в виду, продуктивнее показать пример:

        using System;
using System.Text.RegularExpressions;

public class RegexTest
{
    public static void Main()
    {
        Regex r = new Regex(@"(dotsite)+");
        Match m = r.Match("dotsitedotsitedotsiterulez");

        foreach (Group g in m.Groups)
        {
            Console.WriteLine("> Group: {0} ({1})", g.Value, g.Index);

            foreach (Capture c in g.Captures)
            {
                Console.WriteLine("> Capture: {0} ({1})", c.Value, c.Index);
            }
        }
    }
}

Эта программа найдет две группы - "dotsite" (она представлена индексом 1) и "dotsitedotsitedotsite" (она представлена индексом 0 и представляет все выражение). При этом захват второй группы произойдет один раз (это и понятно, одно соответствие всегда содерджит ровно один захват всего выражения), а вторая - 3 раза (по числу подстрок "dotsite" в строке поиска).

Еще одна возможность Regex, которую мы здесь разберем - это замена. Она осуществляется при вызове метода Replace. При вызове метода передаются три строки - первая задает строку, в которой необходимо произвести замену, вторая представляет собой регулярное выражение и описывает фрагменты, которые подлежат замене, а вторая - на что нужно заменить найденный подстроки, причем во второй строке можно использовать подстановки (substitutions) - в указанные места будут подставлены значения захваченных групп с заданными именами. Синтаксис подстановок следующий:

Синтаксическая конструкция | Значение
$n | Подставляет последнюю подстроку, соответствующую группе с номером n. Например Replace(str, "(a+)", "_$1_") обрамляет все последовательности букв 'a' символами подчеркивания (например, строка "bacghghaaab" превратится в "b_a_cghgh_aaa_b"
$(имя) | Подставит последнюю подстроку, соответствующую группе с именем имя.Например, Replace(str, @"\b(? \d{1,2})/(? \d{1,2})/(? \d{2,4})\b", "${day)-${month}-${year) переведет дату из формата мм/дд/гггг в дд-мм-гггг

Настройка класса Regex

Работа класса Regex зависит от установленных опций. Опции задаются параметром оptions расширенного конструктора экземпляра класса Regex (значение этого параметра должно представлять собой побитовую комбинацию значений перечисления RegexOptions или заданы прямо в регулярном выражении с помощью синтаксической конструкции (?imnsx-imnsx), в которой буквы до дефиса указывают какие опции необходимо включить, а после дефиса - какие выключить. Следует заметить, что некоторые опции не могут быть заданы в строке поиска. Ниже следует список всех опций класса Regex:

Имя опции-члена RegexOptions | Символ, задающий опцию в строке поиска | Описание
None | отсутствует | Указывает, что ни одна опция не является активной.
IgnoreCase | i | Задает нечувствительный к регистру поиск.
Multiline | m | Задает многострочный режим. Изменяет значение символов ^ и $ таким образом, что они соответствуют началу и концу каждой строки
ExplicitCapture | n | Указывает, что только верные явно названные или пронумерованные (с помощью конструкции (?<имя>)) группы сохраняются. Это позволяет избежать излишнего использования конструкции (?:…).
Compiled | Отсутствует | Указывает, что регулярное выражение должно быть скомпилировано в MSIL-код, чем достигается более быстрая обработка, но приводит к увеличению времени загрузки. При выключенно опции выражение компилируется во внутренний код, специфичный для класса Regex. При исользовани этой опции также следует помнить о том, что полученный MSIL-код будет удален из памыти только при удалении всего домена приложения (application domain) и не увлекаться использованием этой опции для разных выражений. Код для выражений кешируется и для разных экземпляров Regex, использующих одно и то же выражения, используется один и тот же код.
Singleline | s | Задает однострочный режим. В этом режиме символ . означает любой символ (включая символ перевода строки).
IgnorePatternWhitespace | x | Указывает что пробелы в регулярном выражении (за исключением предваренный обратной косой чертой) должны игнорироваться и позволяет использовать комментарии, начинающиеся с символа '#'. NB: Пробелы не игнорируются в символьных классах.
RightToLeft | отсутствует | Задает поиск справа налево. Регулярное выражение подствляется начиная с конца.
EcmaScript | отсутствует | Задает стиль EcmaScript. Может быть использован только совместно с аттрибутами Multiline и IgnoreCase.

Обратные ссылки

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

Синтаксис | Определение
\номер | Задает ссылку на нумерованную группу. Например, конструкция (\w)\1 ищет двойные буквы (например, в словах tall, small, millennium)
\k<имя> \k'имя' | Задает ссылку на именованную группу. Например, поведение, описанное выше, реализуется выражением (? \w)\k .

Итог

Итак, в заключение хочется подвести итоги. Компания Microsoft предоставила в составе .NET Framework действительно мощное средство работы с текстом, которое может быть использовано в самых разных приложениях (например, оно используется в ASP.NET для одного из валидаторов (RegularExpressionValidatior)).


ProLib - Регулярные выражения в perl

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

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

Для начала разберемся - что же такое регулярное выражение. По-английски пишется так - Regular Expression (отсюда часто встречается сокращение "regexp" и даже по-русски "регэксп"). Во-первых, не стоит искать смысл в самом термине - это дословный перевод с английского языка, который представляется слишком абстрактным. Но что бы понять по какому принципу работают регулярные выражения, нам и нужно именно что абстрагироваться на уровень предположений. Пример с поиском вхождения подстроки должен быть понятен всем. Но, на самом деле, хотя с помощью регулярных выражений можно легко найти любое вхождение, этот пример не раскрывает всей прелести регэкспов. Лучше вспомните как работает поиск файлов по шаблону (или по маске). Алгоритм подразумевает использование определенных символов (wildcards), которые позволяют как бы закрыть ту часть имени, которая для нас не имеет значения. Однако сами wildcards не используются в именах файлов (что делает алгоритм менее гибким). Так вот, поиск файлов по шаблону позволяет отобрать те имена файлов, которые удовлетворяют заданному условию. При этом, можно указать и точное имя, а можно в каком-то месте имени сделать предположение (с помощью все тех же wildcards). Так вот, регулярные выражения позволяют выполнять аналогичный поиск в пределах некоторой последовательности байт. Добавьте к этому возможность работы с различными частями образованной маски как с отдельными единицами и вы поймете прелесть регэкспов.

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

Ну вроде как с базовой теорией разобрались. Здесь остается добавить, что поняв философию регулярных выражений, вы сможете самостоятельно разобраться с любым форматом регэкспов. Так, например SQL так же подразумевает возможность использования регулярных выражений, но в отличии от perl, формат описания шаблонов в SQL несколько иной.

По частям и все сразу

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

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

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

/^([^\s]*)\s(.*)/
Пробежимся по шаблону слева-направо. Слэши указывают границы регэкспа, так что их сразу можно выкинуть. Символ ^ относится к мнимым символам. Он привязывает шаблон к началу строки. Что это значит? Это значит, что мы найдем искомое, только в случае если оно находится в начале исходной строки. Элементарно, Ватсон! Смотрим простейший пример
$source = 'Pupkin';
$source =~ /^Pupkin/; # Оператор вернет истину, так
                      # как в $source Pupkin с самого начала
$source = 'Vasya Pupkin';
$source =~ /^Pupkin/; # А здесь уже будет ложь, так как перед
                      # Пупкиным стоит его имя.
Так вот, если убрать из шаблона мнимый символ привязки к началу строки (^), то результатом работы второго оператора то же будет истина. Для самых непонятливых перепишу шаблоны
$source = 'Pupkin';
$source =~ /Pupkin/;  # Оператор вернет истину, так
                      # как в $source Pupkin с самого начала
$source = 'Vasya Pupkin';
$source =~ /Pupkin/;  # Здесь то же будет истина, так как
                      # Пупкин в строке есть, хотя и не с начала.
                      # Но ведь и шаблон не требовал Пупкина в начале строки
Теперь понятно, что такое мнимые символы? Просто дополнительное условие, а не часть искомого.

Итак, вернемся к нашим баранам

([^\s]*)\s(.*)
Слэши мы откинули как ограничители, с привязкой к началу строки то же разобрались. Далее у нас круглые скобки. Вот здесь, круглые скобки имеют то же самое значение, что и вообще в языках программирования - они изменяют приоритет и группируют операторы. Так и здесь - нужно рассматривать все то что в скобках как некое объединение. Сразу замечу, что пара круглых скобок образуют контейнер (или ссылка на найденный текст в стандартном определении).

И что мы видим? У нас два контейнера, разделенных \s. \s - это специальный символ, указывающий на любой символ из подмножества пробельных (пробел, табуляция, etc...) Уточню. То что у нас между контейнерами указывает на единичный пробельный символ. Мы подошли к самой важной основополагающей - в регулярном выражении (попросту шаблоне) любой символ соответствует самому себе, если только он не является метасимволом со специальным значением. Вот \s как раз и относится к таким метасимволам. Признаюсь, что наш пример вообще сплошь и рядом состоит из метасимволов. Да, да, в нем нет ни одного символа, соответствующего самому себе.

Итак, что же мы выяснили? Мы выяснили, что будем искать нечто, состоящее из двух контейнеров, которые разделены между собой единичным пробельным символом. Теперь пора разобраться с содержимым контейнеров. Начнем с правого - он проще. Точка в регэкспе определяет любой символ, кроме символа новой строки (есть некоторые ситуации, когда абсолютно любой). Надеюсь, что такое любой символ понятно? Это может быть "1","2","8","D","a","r" или "b" и так далее и тому подобное от кодов с нуля до самого 255.

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

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

Пора приступать к содержимому левого контейнера. Напомню как он выглядит

[^\s]*
Квадратные скобки определяют класс символов. Что такое класс символов? Предположим, что искомое не может быть представлено последовательностью символов, то есть подстрокой. Иначе говоря, в примере с Пупкиным мы не можем явно указать
/Pupkin/
Не важно, по каким причинам. Может быть искомое очень длинное, а может быть искомое - произвольные варианты строк, состоящих из определенных символов. Так вот в таком случае мы определим класс символов. Например символы латинского алфавита определяются таким классом
[a-zA-Z]
Заметьте как удобно - мы не указываем все символы подряд. Мы просто определяем границы с помощью метасимвола - (это как бы даже и не совсем метасимвол, а только в данном случае). Вместо перечисления цифровых символов мы можем записать
[0-9]
Хотя для цифровых символов есть более эффективное решение - метасимвол \d. Итак, у нас в левой части определен класс символов. Но какой-то интересный класс получается - вроде привязанный к началу строки. Нет, метасимвол ^ внутри класса указывает на отрицание символов класса. Это значит, что на месте этой части шаблона должен находиться любой символ, не входящий в состав класса. То есть, для примера
[^0-9]
указывает, что здесь может быть любой нецифровой символ. Так и в нашем примере. Ну а с метасимволом \s вы уже знакомы. Учитывая отрицание получаем - любой непробельный символ. Учтите, что класс определяет только множество для соответствия или отрицания, но не множество для отбора. То есть, если у вас класс, то под шаблон попадет только один символ, удовлетворяющий условию. Для того, чтобы отобрать несколько символов нужно использовать квантификатор, что мы и делаем после описания класса символов. Теперь, что бы разобраться для отбора каких строк можно воспользоваться этим шаблоном давайте напишем пример.
#!C:/per/bin/perl -w
use strict;
reg("Vasya Pupkin");
reg(" Vasya Pupkin");
reg("Vasya\t\tpupkin");

sub reg{
    print "\$1=$1\n\$2=$2\n\n" if $_[0] =~ /([^\s]*)\s(.*)/;
}
В результате получится
$1=Vasya
$2=Pupkin

$1=
$2=Vasya Pupkin

$1=Vasya
$2=	pupkin
Теперь давайте разберемся почему и как. Первый тест однозначно попадает под шаблон: Vasya не состоит из пробельных символов, далее следует один пробельный символ (натурально пробел), а Pupkin составляет оставшуюся часть строки. Результат второго теста у нас какой то странный. Первый контейнер у нас оказался пуст, а второй почему-то содержит всю строку без ведущего пробела. С чем это связано? Да с тем, что квантификатор * означает ноль или более символов. Так как первым в строке у нас пробельный символ, в правый контейнер, согласно условию, попадает ноль непробельных символов. Далее, пробел то не входит в состав контейнеров. Ну а второй контейнер жрет всю строку до конца. Третий вариант, я думаю, понятен. Я уже говорил, что каждый символ регулярного выражения соответствует единичному. И только квантификаторы позволяют кушать несколько символов одного класса. В шаблоне контейнеры разделены одиночным пробельным символом. В левый контейнер попадает Vasya. Самым законным образом первый пробельный символ (табуляция в примере) пропускается, а правый контейнер кушает все что осталось - в том числе и второй табулятор. Таким образом, получаем Пупкина с ведущей табуляцией.

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

/([^\s]*)\s*(.*)/
Теперь наше регулярное выражение будет пропускать между именем и фамилией все пробельные символы. Результат должен быть таким.
$1=Vasya
$2=Pupkin

$1=
$2=Vasya Pupkin

$1=Vasya
$2=pupkin
Осталось выяснить, каким образом правильно интерпретировать значения второго теста. Во-первых нужно избавиться от привязки к началу строки (по моему этот спецсимвол уже успел потеряться в наших примерах :). Итак, шаблон должен обрабатывать ситуации, когда в начале строки может быть один или несколько пробельных символов. Ну это же элементарно, скажете вы, нужно просто добавить в начало шаблона \s и сделать из него квантификатор.
/\s*([^\s]*)\s*(.*)/
Поздравляю! Вы прошли вводный курс по регэкспам ;)

Про обжору и другие тонкости

Теперь стоит поговорить о тонкостях, которые имеют место быть при составление регулярных выражений. Самое известное - это прожорливость квантификатора. Означает это следующее: квантификатор имеет привычку вбирать в себя максимальную строку, какую только может съесть. Для примера можно взять следующий шаблон
/.*pupkin/
Смысл его очевиден - искать Пупкина перед которым может быть что то еще. Однако, если источник содержит несколько Пупкиных, то квантификатор сожрет все вплоть до последнего Пупкина. Например поиск по этому регэкспу в строке
Vasya pupkin pupkin
приведет к тому, что квантификатор сожрет "Vasya pupkin ", а не "Vasya " как можно было ожидать. Для решения этой проблемы, достойной пристального внимания, имеется ряд специальных символов. Прежде всего символ вопроса ? позволяет ограничить апетит квантификатора минимальной строкой совпадения. Возвращаясь к нашему примеру с несколькими Пупкиными получим
/.*?pupkin/
для корректного поедания "Vasya " из строки "Vasya pupkin pupkin". Далее, конструкции с фигурными скобками позволяют определять границы апетита квантификатора. Внутри фигурных скобок (естественно после самого квантификатора) может быть указано одно или два значения, перечисленных через запятую, которые соответственно определяют пределы жадности. Впомним про спецификатор *. Аналогичный ему + превращает шаблон в обжору, которого не удовлетворяет менее одного совпадения. То есть при использовании + условие отбора является истинным только когда имеются 1 и более совпадений. Заметьте, что верхний предел у нас неопределен и может быть опущен внутри конструкции с фигурными скобками. Если внутри фигурных скобок указать всего одно значение без запятых, то квантификатор сожрет только такую строку, в которой совпадений с шаблоном будет именно указанное количество.

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

/(.{2,10})/
Это регулярное выражение будет помещать в контейнер от двух до десяти символов строки. При чем, учитывая жадность, по возможности квантификатор будет вбирать наибольшую строку. То есть если строка длиной 10 или более символов, то в контейнер попадут именно 10, а не 2 и не 5 символов.
$1=Vasya Pupkin
$2=in

$1= Vasya Pupkin
$2=kin

$1=Vasya		pupkin
$2=kin
В общем с квантификаторами можно еще много баловаться. Всего рассказать не смогу. Тут только одно средство - практиковаться.

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

/^(http|ftp)/
Мнимый символ привязки к началу строки может быть помещен и внутри круглых скобок - результат от этого не меняется. Странно, ведь конструкция с круглыми скобками используется для определения алтернатив, ведь она же используется и для группировки в контейнер. Совершенно верно. Альтернативные шаблоны приводят к автоматическому возникновению нового контейнера. Здесь важно не облажаться и правильно определить номер контейнера при извлечении результатов. Контейнер, который был открыт ранее, имеет наименьший номер. Таким образом можно разобраться даже во вложенных контейнерах.

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

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

  • одиночные символы (characters) - он и есть одиночный, чего его комментировать ;)
  • классы символов (character classes) - [], [^]
  • альтернативные шаблоны (alternative match patterns) - (X|X|X)
  • квантификаторы (quantifiers) - {}, ?, +, *
  • мнимые символы (assertions) - \s, ^, $, etc...
  • контейнеры (backreferences) - $1,$2,$x
  • дополнительные конструкции
В perl имеются три основных оператора которые работают со строками. Это
  • m// - проверка совпадений (или поиск)
  • s/// - подстановка
  • tr/// - замена
Каждый оператор имеет свои свои модификаторы. Первый - m// (или просто //) используется для поиска совпадений с указанным шаблоном. Это как раз то, на чем мы тренировались выше. Там же и пример, как можно его использовать. Второй оператор s/// позволяет не только находить определенные участки, совпадающие с заданным шаблоном, но и выполнять неравнозначную подстановку. Фактически, s/// это то же что и m// (даже модификаторы совпадают), но с возможностью произвольной подстановки. Смысл неравнозначной подстановки открывается когда мы обращаемся к третьему оператору tr///. Оператор замены может заменять участки только на равнозначные по длине. Как следствие - он работает быстрее s///. Из всех операторов s/// самый гибкий - он позволяет выполнять все то, что могут m// и tr///. С его помощью можно свернуть горы. Но, за все приходится платить и здесь мы расплачиваемся скоростью. tr/// можно вообще не рассматривать (если конечо вы не фанат скорости). А вот на s/// хочется остановиться поподробнее.

Прежде всего хочу предупредить - не пытайтесь запихать в правую часть оператора s/// (то есть в ту, которая определяет что будем подставлять вместо найденного шаблона) квантификаторы, мнимые символы и вообще всякие другие неопределенности. Все должно быть четко и однозначно. Работа оператора s/// (в прочем как и m///) подразумевает компиляцию на каждом этапе обращения к регулярному выражению. Если вы не ленились (да и так он часто встречается) то уже знаете про модификатор глобального поиска g, который заставляет работать регэксп на протяжении остатка от предыдущего результата и так до конца строки. Так вот, если в правой части разместить имя переменной-контейнера и заюзать регэксп с модификаторами o и g, то наверняка выйдет бардак, так как o запрещает повторную компиляцию шаблона. В общем тут нужно быть предельно внимательным. Еще хочу обратить ваше внимание на модификаторы e и ee. Они позволяют выполнять код непосредственно в процессе работы регулярного выражения. Если у вас очень сложное задание и его очень трудно реализовать в одном регулярном выражении, разбейте их на составные в правой части - и работать будет быстрее и отлаживать проще.

Наверх


doks.gorodok.net -> PHP -> Регулярные выражения

PHP => Регулярные выражения

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

Программирование на PHP. Пятый выпуск. Введение в регулярные выражения. Синтаксис.

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

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

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

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

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

/^\w+([\.\w]+)*\w@\w((\.\w)*\w+)*\.\w{2,3}$/

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

Регулярные выражения пришли к нам из Unix и Perl. В PHP существует два различных механизма для обработки регулярных выражений: POSIX-совместимые и Perl-совместимые. Их синтаксис во многом похож, однако Perl-совместимые регулярные выражения более мощные и, к тому же, работают намного быстрее (в некоторых случаях до 10 раз быстрее). Поэтому здесь мы будем вести речь только о Perl-совместимых регулярных выражениях. Кстати, необходимо заметить, что полное описание синтаксиса регулярных выражений, имеющееся в PHP Manual (http://www.php.net/manual/en/pcre.pattern.syntax.php), занимает более 50 килобайт и, естественно, здесь мы не будем рассматривать весь синтаксис. Нам необходимы только основы, которые помогут вам понять, как именно пишутся регулярные выражения.

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

/\d{3,4}\s[A-Z]{5}/
Синтаксис регулярных выражений

Регулярные выражения, как уже было сказано выше, представляют собой строку. Строка всегда начинается с символа разделителя, за которым следует непосредственно регулярное выражение, затем еще один символ разделителя и потом необязятельный список модификаторов. В качестве символа разделителя обычно используется слэш ('/'). Таким образом в следующем регулярном выражении: /\d{3}-\d{2}/m, символ '/' является разделителем, строка '\d{3}-\d{2}' - непосредственно регулярным выражением, а символ 'm', расположенный после второго разделителя - это модификатор.

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

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

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

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

Метасимвол | Значение
Метасимволы для задания символов, не имеющих изображения
\n | Символ перевода строки (код 0x0A)
\r | Символ возврата каретки (код 0x0D)
\t | Символ табуляции (код 0x09)
\xhh | Вставка символа с шестнадцатиричным кодом 0xhh, например \x41 вставит латинскую букву 'A'
Метасимволы для задания групп символов
\d | Цифра (0-9)
\D | Не цифра (любой символ кроме символов 0-9)
\s | Пустой символ (обычно пробел и символ табуляции)
\S | Непустой символ (все, кроме символов, определяемых метасимволом \s)
\w | "Словесный" символ (символ, который используется в словах. Обычно все буквы, все цифры и знак подчеркивания ('_'))
\W | Все, кроме символов, определяемых метасимволом \w

С остальными имеющимися метасимволами, построенными по этому принципу вы при желании сможете ознакомиться в оригинальном (http://www.php.net/manual/en/pcre.pattern.syntax.php) описании.

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

Regexp | Комментарии
/\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')
  • Символ '^'. Если этот символ стоит первым в секции задания подмножества символов (и только в этом случае!) он будет рассматриваться как символ отрицания. Т.о. можно задать все сиволы, которые не описаны в данной секции.

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

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

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

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

Допустим, у нас есть текст:

12 aaa bbb
aaa 27 ccc
aaa aaa 45

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

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

Символ вертикальной черты ' '. Используется для задания списка альтернатив. Например регулярное выражение:

/(красное зеленое) яблоко/

Найдет в тексте все словосочетания 'красное яблоко' и 'зеленое яблоко'. О значении круглых скобок в этом выражении см. далее.

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

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

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

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

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

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

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

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

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

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

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

/\((\d{3,5})\)\s+(\d{3}-\d{2}-\d{2})/

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

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

/\((\d{3,5})\)\s+(\d{3}-\d{2}-\d{2})/

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

/\((\d{3,5})\)\s+(\d{3}-\d{2}-\d{2})/

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

/\((\d{3,5})\)\s+(\d{3}-\d{2}-\d{2})/

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

/\((\d{3,5})\)\s+(\d{3}-\d{2}-\d{2})/

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

/\((\d{3,5})\)\s+(\d{3}-\d{2}-\d{2})/

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

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

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

Звездочка '*'. Указывает, что символ должен быть повторен 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/. После этого выражение будет работать так как надо.

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

Как уже было сказано ранее - механизм регулярных выражений позволяет добавлять модификаторы, влияющие на обработку регулярного выражения. Ниже рассмотрены наиболее употребительные, об остальных вы можете прочитать в оригинальном (http://www.php.net/manual/en/pcre.pattern.modifiers.php) описании.

Модификатор | Значение
i | Включение режима case-insensitive, т.е. большие и маленькие буквы в выражении не различаются.
m | Указывает на то, что текст, по которому ведется поиск, должен рассматриваться как состоящий из нескольких строк. По умолчанию механизм регулярных выражений рассматривает текст как одну строку вне зависимости от того, чем она является на самом деле. Соответственно метасимволы '^' и '$' указывают на начало и конец всего текста. Если же этот модификатор указан, то они будут указывать соответственно на начало и конец каждой строки текста.
s | По умолчанию метасимвол '.' не включает в свое определение символ перевода строки. Т.е. для многострочного текста выражение /.+/ вернет только первую строку, а не весь текст, как ожидается. Указание этого модификатора снимает это ограничение.
U | Делает все количественные метасимволы "не жадными" по умолчанию (про "жадность" количественных метасимволов см. выше)
Функции PHP для работы с регулярными выражениями

В PHP существует несколько функций для работы с регулярными выражениями. Все они используют один и тот же парсер регулярных выражений для своей работы, но при этом преследуют различные цели. Ниже мы рассмотрим все эти функции. Я буду приводить описание синтаксиса каждой функции в том виде, в котором она описана в PHP Manual (http://www.php.net/manual/en/), чтобы вам легче было разобраться.

Функция (см. http://www.php.net/manual/function.preg-match.php)
preg_match()

Синтаксис:

int preg_match (string pattern, string subject [, array matches])

Эта функция предназначена для проверки того, совпадает ли заданная строка (subject) с заданным регулярным выражением (pattern). В качестве результата функция возвращает 1, если совпадения были найдены и 0, если нет. Если при вызове функции был задан необязательный параметр matches, то после работы функции ему будет присвоен массив, содержащий результаты поиска по заданному регулярному выражению. Заметьте, что вне зависимости от того, сколько именно совпадений было найдено при поиске - вам будет возвращено только первое совпадение. Рассмотрим пример того, как это работает:

<?php
$str = "123 234 345 456 567";                       // Строка для поиска
$result = preg_match('/\d{3}/',$str,$found);        // Производим поиск
echo "Matches: $result<br>";                        // Выводим количество найденных совпадений
print_r($found);                                    // Выводим результат поиска
?>

Результатом работы этой программы будет:

Matches: 1

Array
(
    [0] => 123
)

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

<?php
$str = "123 234 345 456 567";
// Теперь мы не просто ищем трехзначное число,
// но и получаем его среднюю цифру
$result = preg_match('/\d(\d)\d/',$str,$found);
echo "Matches: $result<br>";
print_r($found);
?>

Результат будет следующим:

Matches: 1

Array
(
    [0] => 123
    [1] => 2
)

Как видите - здесь присутствуют результаты поиска по всем имеющимся регулярным выражениям.

Функция (см. http://www.php.net/manual/function.preg-match-all.php)
preg_match_all()

Синтаксис:

int preg_match_all (string pattern, string subject, array matches [, int order])

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

<?php
$str = "123 234 345 456 567";
$result = preg_match_all('/\d{3}/',$str,$found);
echo "Matches: $result<br>";
print_r($found);
?>

Результат работы:

Matches: 5
   Array
   (
       [0] => Array
           (
               [0] => 123
               [1] => 234
               [2] => 345
               [3] => 456
               [4] => 567
           )
)

Как видите - здесь мы получили все найденные совпадения и их количество в качестве результата.

Необходимо обратить ваше внимание на дополнительный параметр, появившийся в этой функции по сравнению с preg_match(): order. Значение этого параметра определяет структуру выходного массива с найденными совпадениями. Его значение может быть одним из перечисленных ниже:

  • PREG_PATTERN_ORDER - результаты поиска будут сгруппированы по номеру регулярного выражения, которое возвратило этот результат (это значение используется по умолчанию).
  • PREG_SET_ORDER - результаты поиска будут сгруппированы по месту их нахождения в тексте

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

Сначала посмотрим на то, как выглядит результат при использовании PREG_PATTERN_ORDER:

<?php
$str = "123 234 345 456 567";
$order = PREG_PATTERN_ORDER;
$result = preg_match_all('/\d(\d)\d/',$str,$found,$order);
print_r($found);
?>

Результат:

Array
  (
      [0] => Array
          (
              [0] => 123
              [1] => 234
              [2] => 345
              [3] => 456
              [4] => 567
          )
      [1] => Array
          (
              [0] => 2
              [1] => 3
              [2] => 4
              [3] => 5
              [4] => 6
          )
)

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

Теперь попробуем то же самое, но с PREG_SET_ORDER:

<?php
$str = "123 234 345 456 567";
$order = PREG_SET_ORDER;
$result = preg_match_all('/\d(\d)\d/',$str,$found,$order);
print_r($found);
?>

Результат:

Array
 (
     [0] => Array
           (
               [0] => 123
               [1] => 2
           )
     [1] => Array
           (
               [0] => 234
               [1] => 3
           )
     [2] => Array
           (
               [0] => 345
               [1] => 4
           )
     [3] => Array
           (
               [0] => 456
               [1] => 5
           )
     [4] => Array
           (
               [0] => 567
               [1] => 6
           )
)

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

Функция (см. http://www.php.net/manual/function.preg-replace.php)
preg_replace()

Синтаксис:

mixed preg_replace (mixed pattern, mixed replacement, mixed subject [, int limit])

Эта функция позволит вам произвести замену текста по регулярному выражению. Как и в предыдущих функциях, здесь производится поиск по регулярному выражению pattern в тексте subject, и каждый найденный фрагмент текста заменяется на текст, заданный в replacement. Задание необязятельного параметра limit позволит ограничить количество заменяемых фрагментов в тексте.

Например, нам необходимо "сжать" текст, убрав из него все лишние пробелы и символы перевода строки:

<?php
$text = "there     is\t\n\t\t some text  \n  \t just  \n\n\n for     test";
echo "<b>Перед заменой:</b>\n$text\n\n";
$text = preg_replace("/(\n \s{2,})/"," ",$text);
echo "<b>После замены:</b>\n$text";
?>

Результатом работы данной программы будет следующий текст:

Перед заменой:
there     is
         some text
     just

 for     test

После замены:
there is some text just for test

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

Однако основная прелесть этой функции, которая и придает ей всю ее мощь - это тот факт, что вы можете ссылаться на результаты поиска при генерации замещающего текста. В качесте примера покажу, как можно очень быстро и элегантно решить задачу, которая возникает достаточно часто - конвертация дат из одного формата в другой. Как вы знаете, на Западе обычно используется формат mm/dd/yyyy, тогда как у нас обычно - dd.mm.yyyy. Следующий пример осуществляет конвертацию дат между этими форматами в заданном тексте:

<?php
$text = 'Today is 11/16/2001';
$text = preg_replace("/(\d{2})\/(\d{2})\/(\d{4})/","\\2.\\1.\\3",$text);
echo $text;
?>

Результат работы этой программы:

Today is 16.11.2001

Обратите внимание на текст, используемый для замены. В нем использованы т.н. backreferences, т.е. ссылки на найденный ранее текст. Всего таких ссылок может быть не более 100 с номерами от 0 до 99 (соответственно в тексте они выглядят как \0, \1, \2 ... \99). Backreference с номером 0 будет заменена на весь найденный текст, \1 - на текст, найденный первым внутренним регулярным выражением, \2 - вторым и т.д. Номерв внутренним регулярным выражениям присваиваются по мере их находжения в тексте, т.е. слева-направо. В нашем случае \1 - это месяц, \2 - день, \3 - год.

Помимо стандартного синтаксиса регулярных выражений, в PHP, совместно с функцией preg_replace() используется еще один дополнительный модификатор - 'e'. Его использование заставляет PHP рассматривать текст замены не как текст, а как PHP код, что дает возможность еще больше расширить сферу применения этой функции в вашем коде. Следующий пример демонстрирует использование этого модификатора - он производит замену всех целых десятичных чисел в тексте на их шестнадцатиричные эквиваленты:

<?php
$text = "123 234 345 456 567";
$text = preg_replace("/\d+/e","'0x'.dechex('\\0')",$text);
print_r($text);
?>

Результатом работы этой программы будет:

0x7b 0xea 0x159 0x1c8 0x237

И еще одно. Функция preg_replace() также умеет работать с массивами регулярных выражений. Т.е. это позволит вам осуществить поиск и замену сразу по множеству условий! В качестве примера приведу фрагмент кода, описанный в PHP Manual (http://www.php.net/manual/function.preg-split.php) и осуществляющий конвертацию HTML документа в текст при помощи всего лишь одного вызова preg_replace()!

// $document should contain an HTML document.
// This will remove HTML tags, javascript sections
// and white space. It will also convert some
// common HTML entities to their text equivalent.

$search = array ("'<script[^>]*?>.*?</script>'si",  // Strip out javascript
                 "'<[\/\!]*?[^<>]*?>'si",           // Strip out html tags
                 "'([\r\n])[\s]+'",                 // Strip out white space
                 "'&(quot #34);'i",                 // Replace html entities
                 "'&(amp #38);'i",
                 "'&(lt #60);'i",
                 "'&(gt #62);'i",
                 "'&(nbsp #160);'i",
                 "'&(iexcl #161);'i",
                 "'&(cent #162);'i",
                 "'&(pound #163);'i",
                 "'&(copy #169);'i",
                 "'&#(\d+);'e");                    // evaluate as php

$replace = array ("",
                  "",
                  "\\1",
                  "\"",
                  "&",
                  "<",
                  ">",
                  " ",
                  chr(161),
                  chr(162),
                  chr(163),
                  chr(169),
                  "chr(\\1)");

$text = preg_replace ($search, $replace, $document);

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

Функция (см. http://www.php.net/manual/function.preg-replace-callback.php)
preg_replace_callback()

Синтаксис:

mixed preg_replace_callback (mixed pattern, mixed callback, mixed subject [, int limit])

Эта функция является расширенной версией функции preg_replace() (хотя, казалось бы, чего еще можно пожелать?). Единственным отличием ее от preg_replace() является то, что в качестве текста для замены в ней задается не сам текст, а имя функции, которая будет производить обработку найденного текста и возвращать замещающий текст. Т.е. с использованием этой функции мощь инструментария PHP по обработке текста становится поистине безграничной! В качестве примера хочу привести фрагмент кода, который выполняет работу, аналогичную той, что производится механизмом сессий в PHP: добавление дополнительного аргумента (идентификатора сессии) к каждой ссылке внутри HTML документа.

<?php
// Список тегов и аттрибутов, к котроым необходимо
// добавить дополнительный параметр.
// Формат строки:
// <имя тега>[ <имя аттрибута>]+
// Т.е. сначала идет имя тега, а затем, через пробел,
// имена одного или нескольких аттрибутов.
$tagsList = array(
    'a href',
    'area href',
    'frame src',
    'input src',
    'img src',
    'form action'
);
// Идентификатор сессии
$sid = 12345;
// HTML документ для обработки. Здесь, в качестве примера
// мы берем его из внешнего файла, но вообще-то метод
// получения исходного документа может быть различным.
$document = join('',file('document.html'));
// Начинаем обработку всех тегов, указанных в массиве $tagsList
foreach($tagsList as $tag)
{
// Разделяем список аттрибутов на составляющие
    $attrs = explode(' ',$tag);
// Получаем имя тега (в массиве $attrs остается лишь список аттрибутов)
    $tag = array_shift($attrs);
// Выполняем "патч" всех имеющихся в документе ссылок, содержащихся
// в каждом из аттрибутов текущего тега
    foreach($attrs as $attr)
        $document = preg_replace_callback("/<".$tag.".+?".$attr."=[\'\"](.+?)[\'\"]/si",
                                          'callback',$document);
};
// Выводим документ и выходим
echo $document;
exit();
// Эта функция будет вызываться для каждой найденной
// ссылки в тексте HTML документа.
// На входе она получает результат поиска (массив,
// аналогичный возвращаемому функцией preg_match()).
// На выходе из функции должна быть строка с текстом замены.
function callback($data)
{
// Регулярное выражение, использованное для поиска находит полные
// HTML теги, содержащие аттрибуты, в которых могут находиться
// URL адреса. Поскольку текст, возвращаемый данной функцией будет
// использован для замещения всего найденного фрагмента текста -
// нам необходимо взять полный текст, чтобы не потерять его при
// дальнейшей обработке. Он будет возвращен без изменений, если
// окажется, что аттрибут не содержит URL адреса.
    $href = $data[0];
// Используем функцию PHP для разбора URL адреса на составляющие.
// В качестве "исходного материала" передаем содержимое интересующего
// нас аттрибута, найденного внутренним регулярным выражением.
// Подробнее о том, что возвращает эта функция см. PHP Manual.
    $parts = parse_url($data[1]);
// Мы должны добвлять идентификатор сессии только к ссылкам, которые
// являются "локальными" для данного сайта. Т.е. мы не должны обрабатывать:
//  - полные URL адреса (<a href="http://www.php.net/">)
//  - указатели на "якоря" внутри страницы (<a href="#part2">)
    if ((!isset($parts['scheme'])) &&   // Если URL содержит идентификатор
        (!isset($parts['host'])) &&     // протокола или имя домена - это
// полный URL адрес.
        (substr($data[1],0,1)!='#'))    // Если URL начинается с символа '#'
// то это ссылка на "якорь" внутри страницы
    {
// Берем путь к странице, указанный в URL и добавляем разделитель для параметров
// потому что нам необходимо будет добавить по крайней мере 1 параметр
        $href = $parts['path'].'?';
// Если в этом URL уже были какие-либо параметры - добавляем их и добавляем
// разделитель. Заметьте, что в качестве разделителя используется &amp;, а не &,
// это позволяет нам добиться совместимости с XHTML.
        if (isset($parts['query']))
            $href .= $parts['query'].'&amp;';
// Добавляем наш собственный параметр - идентификатор сессии
        $href .= 'sid='.$GLOBALS['sid'];
// Если в оригинальном URL была ссылка на фрагмент документа - возвращаем ее
// на место.
        if (isset($parts['fragment']))
            $href .= '#'.$parts['fragment'];
// "Вставляем" новый URL на место того, который был там раньше
        $href = str_replace($data[1],$href,$data[0]);
    };
// Возвращаем результат
    return($href);
};
?>

Пример может показаться немного громоздким, но это исключительно из-за обилия комментариев.

Функция (см. http://www.php.net/manual/function.preg-split.php)
preg_split()

Синтаксис:

array preg_split (string pattern, string subject [, int limit [, int flags]])

Данная функция выполняет действие, аналогичное функциям (см. http://www.php.net/manual/function.split.php) split() и (см. http://www.php.net/manual/function.explode.php) explode() - разбивает строку на части по какому-либо признаку и возвращает массив, содержащий части строки. Однако ее возможности по заданию правил разбиения больше, чем у этих функций, потому что в ее основе лежит механизм регулярных выражений, в мощи которого, я надеюсь, вы уже смогли убедиться. Если говорить более конкретно, то строка subject разбивается на части по разделителю, заданному регулярным выражением pattern. При этом количество фрагментов может быть ограничего необязятельным параметром limit. Кроме того эта функция поддерживает необязательный параметр flags, который позволяет в некоторой степени контролировать процесс разбиения строки.

Параметр flags может принимать следующие значения (или их комбинации с использованием знака ' '):

  • PREG_SPLIT_NO_EMPTY - возвращать только непустые части строк, полученные в результате разбиения.
  • PREG_SPLIT_DELIM_CAPTURE - возвращать также результаты поиска по внутренним регулярным выражениям.

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

<?php
$text = join('',file('my_text.txt'));
$words = preg_split("/\s+/s",$text);
print_r($words);
?>

Как видите - мы получаем содержимое файла 'my_text.txt' в виде строки, разбиваем его на отдельные слова и выводим содержимое массива слов, чтобы убедиться, что все работает правильно.

Еще один пример производит разбиение заданного слова на буквы (он описан в PHP Manual (см. http://www.php.net/manual/function.preg-split.php)):

<?php
$str = 'string';
$chars = preg_split('//',$str,-1,PREG_SPLIT_NO_EMPTY);
print_r($chars);
?>

Значение -1 для параметра limit означает отсутствие лимита.

Функция (см. http://www.php.net/manual/function.preg-quote.php)
preg_quote()

Синтаксис:

string preg_quote (string str [, string delimiter])

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

. \ + * ? [ ^ ] $ ( ) { } = ! < >   :

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

Эта фцнкция также имеет необязательный параметр delimiter. Если этот параметр задан, то символ, переданный в качестве этого параметра тоже будет "отквочен" данной функцией.

Функция (см. http://www.php.net/manual/function.preg-grep.php)
preg_grep()

Синтаксис:

array preg_grep (string pattern, array input)

Действие этой функции похоже на действие команды grep в Unix. Она ищет текст по регулярному выражению pattern, в массиве input и возвращает новый массив, содержащий только элементы, в которых были найдены совпадения с заданным регулярным выражением. К примеру у нас есть файл, содержащий в каждой строке числовую и текстовую информацию. Нам необходимо получить из этого файла только строки, содержащие числа:

Файл data.txt:

123
abc
php4

Код:

<?php
// Считываем содержимое файла в массив
$data = file('data.txt');
// Получаем массив, содержащий цифровую информацию
$numbers = preg_grep("/\d+/",$data);
// Выводим результат работы
print_r($numbers);
?>

Результат работы будет:

Array
(
    [0] => 123
    [2] => php4
)

Как видите - мы получили все строки, содержащие цифры. Если же нам, например нужно получить только цифры - то выражение необходимо немного изменить: /^\s*\d+\s*$/.

Примеры регулярных выражений

Замена всех ссылок по тексту в переменной $text на рабочие ссылки в html

$text = preg_replace("!(https?|ftp)://(.*?)([\s<'\"]){1,}!ise", "'<a href=\"\\1://\\2\" target=\"_blank\">\\1://\\2</a>\\3'", $text);

Замена всех адресов почты по тексту в переменной $text на рабочие адреса почты в html

$text = preg_replace("!([a-z0-9_-]{1,20})@(([a-z0-9-]+\.)+(com|net|org|mil|".

"edu|gov|arpa|info|biz|inc|name|[a-z]{2})|[0-9]{1,3}\.[0-9]{1,3}\.[0-".

"9]{1,3}\.[0-9]{1,3})!is", "<a href=\"mailto:\\1@\\2\" >\\1@\\2</a>", $text);

Замена всех символов табуляции в переменной $text на 8 символов пробела для корректного отображения html кода

$text = str_replace("\t", "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;", $text);

Замена всех двойных пробелов в переменной $text на 2 символов пробела для корректного отображения html кода

$text = str_replace(" ", "&nbsp;&nbsp;", $text);

Удаление символа «возврат карреткм» из переменной $text

$text = str_replace("\r", "", $text);

Удаление всех скриптов из переменной $text

$text = preg_replace("!<([\s\w]*)script([\s]?)(.*?)>(.*?)<([\s]?)([\/]?)([\s\w]*)script([\s]?)>!si", "", $text);

Удаление всех кадров из переменной $text

$text = preg_replace("!<([\s\w]*)frame([\s]?)(.*?)>(.*?)<([\s]?)([\/]?)([\s\w]*)frame([\s]?)>!si", "", $text);

$text = preg_replace("!<([\s\w]*)layer([\s]?)(.*?)>(.*?)<([\s]?)([\/]?)([\s\w]*)layer([\s]?)>!si", "", $text);

$text = preg_replace("!<([\s]?)frameset([\s]?)(.*?)>(.*?)<([\s]?)([\/]?)([\s]?)frameset([\s]?)>!si", "", $text);

Удаление всех комментариев из переменной $text

$text = preg_replace("/<!--(.*?)-->/i", "", $text);

Замена всех повторяющихся пробелов в переменной $text на один пробел

$text = preg_replace("/ +/is"," ", $text);

Замена всех повторяющихся символов перевода строки в переменной $text на один символ перевода строки

$text = preg_replace("/([\n] +)/is"," ", $text);

Возвращает тип документа в переменной $type из текста который находится в переменной $text

$type = preg_replace("/(.*)Content-Type:([\s]?)([A-Za-z0-9\/]*)\;([\s]?)(.*)/is", "\\3", $text);

Возвращает кодировку документа в переменной $charset из текста который находится в переменной $text

$charset = preg_replace("/(.*)charset([\s]?)=([\s]?)([A-Za-z0-9-]*)(\s)(.*)/is", "\\4", $text);


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

Регулярные выражения предоставляют сжатую и гибкую систему обозначений, используемую при поиске и замене образцов текста. Доступные в Visual Studio регулярные выражения расширяют набор выражений, используемых в Visual C++ 6.0, обеспечивая более сложный синтаксис.

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

Примечание. Перед тем, как задействовать регулярные выражение для задания критериев поиска, следует установить флажок ВлИспользоватьВ» в диалоговом окне ВлНайтиВ», ВлЗаменитьВ», ВлНайти в файлахВ» или ВлЗаменить в файлахВ».

Для обозначения знаков и цифр в строке поиска можно использовать следующие выражения.

Выражение | Синтаксис | Описание
Любой знак | . | Обозначает любой одиночный знак, за исключением перевода строки.
Максимально тАУ ноль или более | * | Обозначает ноль или более вхождений предшествующего выражения.
Максимально тАФ одно или более | + | Обозначает наличие по крайней мере одного вхождения предшествующего выражения.
Минимально тАФ ноль или более | @ | Обозначает ноль или более вхождений предшествующего выражения; соответствие устанавливается для минимально возможного числа знаков.
Минимально тАФ одно или более | # | Обозначает одно или несколько вхождений предшествующего выражения; соответствие устанавливается для минимально возможного числа знаков.
Повторить n раз | ^n | Обозначает n вхождений предшествующего выражения. Например, [0-9]^4 обозначает любую последовательность из четырех цифр.
Набор знаков | [] | Обозначает любой из знаков внутри []. Для задания диапазона знаков следует указать начальный и конечный знаки, разделенные тире, например, [a-z].
Отсутствующий в наборе знак | [^...] | Обозначает любой знак, отсутствующий в наборе знаков, следующем за ^.
Начало строки | ^ | Указывает, что соответствие следует искать в начале строки.
Конец строки | $ | Указывает, что соответствие следует искать в конце строки.
Начало слова | < | Соответствие обнаруживается только, если в данной точке текста начинается слово.
Конец слова | > | Соответствие обнаруживается только, если в данной точке текста заканчивается слово.
Группировка | () | Группирует подвыражение.
ИЛИ | | | Указывает, что для поиска соответствия следует использовать выражение, расположенное либо до, либо после символа ИЛИ (|). Обычно используется в группе. Например, (sponge|mud) bath соответствует "sponge bath" и "mud bath".
Escape-знак | \ | Обозначает знак, следующий за обратной косой чертой (\). Позволяет искать знаки системы обозначений, используемой для задания регулярных выражений, такие как { и ^. Например, с помощью \^ производится поиск знака ^.
Помеченное выражение | {} | Помечает текст, обозначаемый выражением в фигурных скобках.
n-й помеченный текст | \n | Применяется в выражениях, используемых для поиска или замены, указывая текст, обозначаемый n-м помеченным выражением, где n является числом от 1 до 9. В выражениях, используемых для замены, совпавший текст полностью вставляется с помощью \0.
Выровненное по правому краю поле | \(w,n) | В выражениях, используемых для замены, выравнивает по правому краю n-е помеченное выражение в поле длиной, по крайней мере, w знаков.
Выровненное по левому краю поле | \(-w,n) | В выражениях, используемых для замены, выравнивает по левому краю n-е помеченное выражение в поле длиной, по крайней мере, w знаков.
Предотвращение нахождения соответствия | ~(X) | Препятствует нахождению соответствия, если X появляется в выражении на данной позиции. Например, real~(ity) обозначает real в словах realty и really, но не в слове reality.
Буквенно-цифровой знак | :a | Соответствует выражению ([a-zA-Z0-9]).
Знак алфавита | :c | Соответствует выражению ([a-zA-Z]).
Десятичная цифра | :d | Соответствует выражению ([0-9]).
Шестнадцатеричная цифра | :h | Соответствует выражению ([0-9a-fA-F]+).
Идентификатор | :i | Соответствует выражению ([a-zA-Z_$][a-zA-Z0-9_$]*).
Рациональное число | :n | Соответствует выражению (([0-9]+.[0-9]*)|.([0-9]*.[0-9]+)|([0-9]+)).
Строка, заключенная в кавычки | :q | Соответствует выражению (("[^"]*")|.('[^']*'))
Строка алфавитных знаков | :w | Соответствует выражению ([a-zA-Z]+).
Десятичное целое число | :z | Соответствует выражению ([0-9]+).
Escape-знак | \e | Знак Юникода U+001B.
Знак Bell | \g | Знак Юникода U+0007.
Знак BACKSPACE | \h | Знак Юникода U+0008.
Перенос строки | \n | Обозначает независимый от используемой платформы знак переноса строки. В выражениях, используемых для замены, с помощью данного выражения вставляется перевод строки.
Знак TAB | \t | Обозначает знак табуляции (знак Юникода U+0009).
Знак Юникода | \x#### или \u#### | Обозначает знак, задаваемый значением в кодировке Юникод, где #### представляет собой шестнадцатеричное число. Имеется возможность указать знак, не входящий в Basic Multilingual Plane (то есть, знак-заместитель), с помощью кодовых позиций кодировки ISO 10646 или двух кодовых позиций кодировки Юникод с передачей значения пары символов-заместителей.

В следующей таблице приведены элементы синтаксиса, используемые для поиска соответствия с помощью стандартных свойств знаков Юникода. Двухбуквенные аббревиатуры здесь совпадают с аббревиатурами, приведенными в базе данных свойств знаков Юникода. Эти аббревиатуры могут быть указаны в качестве составляющих набора символов. Например, выражение [:Nd:Nl:No] обозначает любую цифру.

Выражение | Синтаксис | Описание
Буква в верхнем регистре | :Lu | Обозначает любую (одну) прописную букву. Например, :Luhe соответствует "The", но не "the".
Буква в нижнем регистре | :Ll | Обозначает любую (одну) строчную букву. Например, :Llhe соответствует "the", но не "The".
Буква в титульном регистре | :Lt | Обозначает сочетание знаков, состоящее как из букв в верхнем, так и в нижнем регистре, например Nj и Dz.
Знак-модификатор | :Lm | Обозначает буквы или знаки пунктуации, такие как запятые, ударения или двойные штрихи, используемые для видоизменения предшествующей буквы.
Прочая буква | :Lo | Обозначает прочие буквы, такие как готическая буква "ahsa".
Десятичная цифра | :Nd | Обозначает десятичные цифры (0-9) и их двухбайтовые эквиваленты.
Буквенная цифра | :Nl | Обозначает буквенные цифры, такие как римские цифры или идеографическое число ноль.
Прочая цифра | :No | Обозначает прочие цифры, такие как старое итальянское число один.
Открывающий знак пунктуации | :Ps | Обозначает открывающие знаки пунктуации, такие как скобки (в т.ч. фигурные).
Закрывающий знак пунктуации | :Pe | Обозначает закрывающие знаки пунктуации, такие как скобки (в т. ч. фигурные).
Начальные кавычки | :Pi | Обозначает начальные двойные кавычки.
Конечные кавычки | :Pf | Обозначает одинарные кавычки и конечные двойные кавычки.
Тире | :Pd | Обозначает тире.
Соединительные знаки пунктуации | :Pc | Обозначает знак подчеркивания или подчеркивание.
Прочие знаки пунктуации | :Po | Обозначает запятые (,), ?, ", !, @, #, %, &, *, \, двоеточия (:), точки с запятой (;), ' и /.
Разделительный пробел | :Zs | Обозначает пробелы.
Разделитель строк | :Zl | Обозначает знак Юникода U+2028.
Разделитель абзацев | :Zp | Обозначает знак Юникода U+2029.
Несамостоятельный знак | :Mn | Обозначает несамостоятельные знаки.
Объединяющий знак | :Mc | Обозначает объединяющие знаки.
Вмещающий знак | :Me | Обозначает вмещающие знаки.
Математический символ | :Sm | Обозначает +, =, ~, |, < и >
Символ денежной единицы | :Sc | Обозначает $ и символы других денежных единиц.
Символ- модификатор | :Sk | Обозначает символы-модификаторы, такие как диакритические знаки циркумфлекс, гравис или знак долготы над гласным.
Прочий символ | :So | Обозначает прочие символы, такие как знак авторского права, абзаца и возведения в степень.
Прочий управляющий символ | :Cc | Обозначает конец строки
Прочий формат | :Cf | Форматирующие управляющие знаки, такие как двунаправленные управляющие знаки.
Символ-заместитель | :Cs | Обозначает один символ из пары символов-заместителей.
Прочие знаки для личного использования | :Co | Обозначает любой знак из области знаков для личного использования.
Прочие не назначенные знаки | :Cn | Обозначает знаки, не соответствующие ни одному из знаков Юникода.

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

Выражение | Синтаксис | Описание
Буквенное | :Al | Обозначает любой (один) из указанных знаков. Например, выражение :Alhe соответствует словам "The", "then" и "reached".
Числовое | :Nu | Обозначает любое (одно) из указанных чисел или цифр.
Знак пунктуации | :Pu | Обозначает любой знак пунктуации, такой как ?, @, ' и т. д.
Пробел | :Wh | Обозначает все виды пробелов (пустого пространства), включая издательские и идеографические пробелы.
Двунаправленные | :Bi | Обозначает знаки, используемые в способах письма, где применяется направление "справа налево"; характерных, например, для арабской или еврейской письменности.
Хангыль | :Ha | Обозначает знаки корейского алфавита Хангыль и объединенного Джамо.
Хирагана | :Hi | Обозначает знаки хираганы.
Катакана | :Ka | Обозначает знаки катаканы.
Идеографические /Хан/Кандзи | :Id | Обозначает идеографические знаки (Хан и Кандзи).

Visual Studio

Подстановочные знаки

Для замены знаков или цифр в строке поиска можно использовать следующие выражения:

Примечание. Перед тем, как задействовать регулярные выражение для задания критериев поиска, следует установить флажок ВлИспользоватьВ> в диалоговом окне ВлНайтиВ>, ВлЗаменитьВ>, ВлНайти в файлахВ> или ВлЗаменить в файлахВ>.
Выражение | Синтаксис | Описание
Любой одиночный знак | ? | Обозначает любой одиночный знак.
Любая одиночная цифра | # | Обозначает любую одиночную цифру. Например, выражение 7# соответствует числам, содержащим цифру 7, за которой следует любая другая цифра, таким как 71 (но не 17).
Отсутствующий в наборе знак | [! ] | Обозначает любой одиночный знак, не указанный в наборе.
Один или несколько знаков | * | Обозначает один или несколько знаков (любых). Например, выражение, new* обозначает любой текст, включающий "new", например, "newfile.txt".
Набор знаков | [ ] | Обозначает любой (один) из знаков в наборе.


Регулярные выражения (шаблоны)

Чаще всего регулярные выражения используются в Perl в операторах поиска и замены, таких как s//, m/, операторах связки =~ или != и т.д. Как правило все эти операторы имеют схожие опции такие как:

i | - не различать строчные и заглавные буквы.
m | - считать строку многострочной.
s | - однострочная строка.
x | - расширенный синтаксис ( использование пробелов и комментариев)

Обычно все эти опции обозначают как '/x'. Их можно использовать даже внутри шаблонов, используя новую конструкцию (?...)

Регулярные выражения или шаблоны (pattern) то же самое, что и regexp процедуры в Unix. Выражения и синтаксис заимствованы из свободно распространяемых процедур V8 Генри Спенсера (Henry Spencer), там же они подробно и описаны.

В шаблонах используются следующие метасимволы (символы обозначающие группы других символов) часто называемые egrep - стандартом:

\ | - считать следующий метасимвол как обычный символ.
^ | - начало строки
. | - один произвольный символ. Кроме '\n' - конец строки.
$ | - конец строки
| | - альтернатива (или)
() | - группировка
[] | - класс символов

Метасимволы имеют модификаторы (пишутся после метасимвола):

* | - повторяется 0 или большее число раз
+ | - -//- 1 или большее число раз
? | - 1 или 0 раз
{n} | - точно n раз
{n,} | - по меньшей мере раз
{n,m} | - не меньше n, но и не больше m

Во все других случаях фигурные скобки считаются обычными (регулярными) символами. Таким образом '*' эквивалентна {0,} , '+' - {1,} и '?' - {0,1}. n и m не могут быть больше 65536.

По умолчанию действие метасимволов "жадно" (greedy). Совпадение распространяется столько раз, сколько возможно, не учитывая результат действия следующих метасимволов. Если вы хотите "уменьшить их аппетит", то используйте символ '?'. Это не изменяет значение метасимволов, просто уменьшает распространение. Таким образом:

*? | - станет 0 и более
+? | - 1 и более
?? | - 0 или 1 раз
{n}? | - точно n раз
{n,}? | - не меньше n раз
{n,m}? | - больше или равно n и меньше m раз

Шаблоны работают так же, как и двойные кавычки, поэтому в них можно использовать `\` - символы (бэкслэш-символы):

\t | - символ табуляции
\n | - новая строка
\r | - перевод каретки
\а | - перевод формата
\v | - вертикальная табуляция
\a | - звонок
\e | - escape
\033 | - восьмеричная запись символа
\x1A | - шестнадцатеричная
\c[ | - control символ
\l | - нижний регистр следующего символа
\u | - верхний регистр -//-
\L | - все символы в нижнем регистре до \E
\U | - в верхнем -//-
\E | - ограничитель смены регистра
\Q | - отмена действия как метасимвола

Дополнительно в Perl добавлены следующие метасимволы:

\w | - алфавитно-цифровой или '_' символ
\W | - не -//-
\s | - один пробел
\S | - один не пробел
\d | - одна цифр
\D | - одна не цифра

Обратите внимание, что все это "один" символ. Для обозначения последовательности применяйте модификаторы. Так:

\w+ | - слово
\d+ | - целое число
[+-]?\d+ | - целое со знаком
[+-]?\d+\.?\d* | - число с точкой

Кроме того существуют мнимые метасимволы. Обозначающие не существующие символы в месте смены значения. Такие как:

\b | - граница слова
\B | - не граница слова
\A | - начало строки
\Z | - конец строки
\G | - конец действия m//g

Граница слова (\b) - это мнимая точка между символами \w и \W. Внутри класса символов '\b' обозначает символ backspace (стирания). Метасимволы \A и \Z - аналогичны '^' и '$', но если начало строки '^' и конец строки '$' действуют для каждой строки в многострочной строке, то \A и \Z обозначают начало и конец всей многострочной строки.

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

$+ | - обозначает последнее совпадение
$& | - все совпадение
$` | - все до совпадения
$' | - все после совпадения

Пример:

$s = "Один 1 два 2 и три 3";
  if ($s =~ /(\d+)\D+(\d+)/)
  {
  print "$1\n"; # Результат '1'
  print "$2\n"; # '2'
  print "$+\n"; # '2'
  print "$&\n"; # '1 два 2'
  print "$`\n"; # 'Один '
  print "$'\n"; # ' и три 3'
  }

Perl версии 5 содержит дополнительные конструкции шаблонов:

(?#комментарий) | - комментарий в теле шаблона.
(?:шаблон) | - группировка как и '( )', но без обратной ссылки
(?=шаблон) | - "заглядывание" вперед. Например /\w+(?=\t)/ соответствует слову, за которым идет табуляция, но символ '\t' не включается в результат.

Пример:

$s = "1+2-3*4";
if ($s =~ /(\d)(?=-)/)  # Найти цифру за которой стоит '-'
{
print "$1\n";   # Результат '2'
}
else
{
print "ошибка поиска\n";
}

(?!шаблон)- "заглядывание" вперед по отрицанию:

Пример:

$s = "1+2-3*4";
if ($s =~ /(\d)(?!\+)/) # Найти цифру за которой не стоит '+'
{
print "$1\n";   # Результат '2'
}
else
{
print "ошибка поиска\n";
}

(?ismx) - "внутренние" модификаторы. Удобно применять в шаблонах, где например нужно внутри шаблона указать модификатор.

Правила регулярного выражения. (regex)

  1. Любой символ обозначает себя самого, если это не метасимвол. Если вам нужно отменить действие метасимвола, то поставьте перед ним '\'.
  2. Строка символов обозначает строку этих символов.
  3. Множество возможных символов (класс) заключается в квадратные скобки '[]', это значит, что в данном месте может стоять один из указанных в скобках символов. Если первый символ в скобках это '^' - значит ни один из указанных символов не может стоять в данном месте выражения. Внутри класса можно употреблять символ '-', обозначающий диапазон символов. Например, a-z - один из малых букв латинского алфавита, 0-9 - цифра и т.д.
  4. Все символы, включая специальные, можно обозначать с помощью '\' как в языке С.
  5. Альтернативные последовательности разделяются символом '|' Заметьте что внутри квадратных скобок это обычный символ.
  6. Внутри регулярного выражения можно указывать "подшаблоны" заключая их в круглые скобки и ссылаться на них как '\номер' Первая скобка обозначается как '\1'.


Синтаксис регулярных выражений. Краткий справочник

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

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

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

Символ | Синоним | Описание | Примеры | OO | TE
. |  | Любой символ | “.+” - любая ненулевая строка в кавычках | + | +
^ |  | Начало строки | ^[а-я] – абзац, начинающийся со строчной буквы | + | +
$ |  | Конец строки | ^$ - пустая строка | + | +
[ ] |  | Набор символов | 0x[0a-c] – числа, начинающиеся с 0×0, 0xa, 0xb или 0xc | + | +
[^ ] |  | Набор символов КРОМЕ | 0×0[1-9][^0-9a-f] – любое байтовое число от 1 до 9 | + | +
| |  | Альтернатива (ИЛИ) | рабоч(его|ее) - ищем “рабочего” или “рабочее” | + |
( ) | \( \) - SciTE | Выделение подвыражения | (рабоч)(его|ее) – два подвыражения | + |
\n |  | Использование подвыражения | \(0x.., *\)\1 – два одинаковых байта рядом в массиве |  | +
* | {0,} | 0 и более раз | ^.* - выделение строк (пустых и не пустых) | + | +
+ | {1,} | 1 и более раз | ^.+ - выделение непустых строк (абзацев) | + | +
? | {0,1} | 0 или 1 | мест[^ ]? - ищет “мест”, “места”, “место”… | + |
 |  | “Не жадный” режим | 0{2,4} в строке нолей найдёт “0000”, а 0{2.4}? - “00” |  |
 |  |  | Применяется с другими метасимволами: *? +? ?? {n}? … |  |
{n} |  | Итератор “n раз” | 0x[0-9a-f]{2}[^0-9a-f] – найдёт только байтовые числа | + |
{n,} |  | Итератор “не менее n раз” | ^.{40,} - абзацы, в которых не менее 40 символов | + |
{n,m} |  | Итератор “не менее n, не более m” | \<[:alpha:]{2,4}\> - искать слова от 2 до 4 символов | + |
\xnn |  | Шестнадцатиричное значение | \x20 – пробел | + |
\xnnnn |  | Шестнадцатиричное (Unicode) | \x0410 – буква “А” русского алфавита | + |
\t | \x09 | TAB | ^\t – найти строку, начинающуюся с TAB | + | +
\n | \x0A | NL |  |  |
\s | [ \t\n\r\f] | Разделитель |  |  |
\S | [^ \t\n\r\f] | НЕ разделитель |  |  |
\d | [0-9] | Цифра |  |  |
\D | [^0-9] | НЕ цифра |  |  |
\w | [a-zA-Z0-9_] | Символ слова |  |  |
\W | [^\w] | НЕ символ слова |  |  |
\b |  | Граница слова |  |  |
\B |  | НЕ граница слова |  |  |
\< |  | Начало слова | \(\< - найти круглую открывающую скобку в начале слова | + | +
\> |  | Конец слова | [a-z_]+\>\[\] - найти объявление массива | + | +
\A |  | Начало текста |  |  |
\Z |  | Конец текста |  |  |
[:digit:]? | \d | Цифра |  | + |
[:space:]? | [ ] | Пробел |  | + |
[:print:]? |  | Все печатаемые и пробелы |  | + |
[:cntrl:]? | \t | Непечатаемые (без пробелов) |  | + |
[:alnum:]? | [a-zA-Z0-9] | Цифра или буква |  | + |
[:alpha:]? | [a-zA-Z] | Буква |  | + |
[:lower:]? |  | Буква в нижнем регистре |  | + |
[:upper:]? |  | Буква в верхнем регистре |  | + |
Для того, чтобы искать символ, совпадающий с метасимволом, его следует предварить обратной косой чертой - \\ \^ \+ …
Чтобы определить для поиска в перечне символ [ или ] , нужно поместить их в начало перечня, но лучше предварить косой чертой - []a-z] [a-z\]]
Чтобы определить для поиска в перечне символ - , нужно поместить его в начало или конец перечня, но лучше предварить косой чертой
Символ | не работает в перечне, поэтому б[о|а]р найдёт “бор”, “бар” или “б|р”.
Символы для замены
& |  | Найденное выражение (OpenOffice) | Find: [:digit:]В , replace: &ольт. 5В заменится на 5Вольт | + |
\( \) |  | Подвыражение (SciTE) | Find: \(//\)\(.*\) - находим строковый комментарий в С |  | +
\n |  | Найденное выражение (SciTE) | Replace: /*\2*/ - замена строкового на блочный |  | +
( ) |  | Подвыражение | Распространённый способ выделения подвыражения |  |
$n |  | Найденное выражение | Ещё один синтаксис подстановки подвыражения |  |
 |  |  | OpenOffice | OO |
 |  |  | SciTE |  | TE


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

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

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

Служебные символы делятся на три класса:

  • первый означает какой-либо класс (набор) символов (к примеру \w означает любую букву)
  • второй в отличии от первого не имеет длины (например ^ - начало строки, \b - начало слова)
  • третий класс - это операторы. Операторы применяются к метасимволам, к обычным символам или к другим операторам.

Любое выражение можно сгруппировать (заключить в скобки) и применить оператор ко всей группе.

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

  • Синтаксис
  • Спецсимволы
  • Расширенные спецсимволы
  • Операторы
  • Расширенные операторы

Синтаксис

Все регэкспы должны заключаться в прямые слэши (/.../). После конечного слэша могут идти параметры:

/.../i | - не различать регистр.
/.../x | - игнорировать пробелы и переводы строк (для удобства).
/.../s | - считать регэксп одной единственной строкой (трактовать спецсимвол . (точка) как "любой символ, в том числе и символ перевода строки").

Примеры:

\ совпадет только со словом 'Valery'
/Valery/
\ совпадет со словами 'VALERY', 'valery', 'Valery' и т. д.
/Valery/i
\ совпадет с 'foobar', 'foobar barfoo'
/foobar/
\ совпадет с 'foobar', 'FOOBAR', 'foobar and two other foos'
/ FOO bar /ix
\ совпадет с 'Valery%crlf%Kondakoff'
/Valery.*Kondakoff/s

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

Спецсимволы

^ | Начало строки
$ | Конец строки
. | Любой символ кроме переводов строки (без параметра /.../s)
[ ... ] | Любой из перечисленного набора символов. Внутри квадратных скобок не работают другие операторы, но можно пользоваться метасимволами. С помощью дефиса можно указывать наборы символов: от первого до последнего. Например, [a-f] означает любую букву из числа a, b, c, d, e, f.
[^ ... ] | Ни один из перечисленного набора символов. Внутри квадратных скобок не работают другие операторы, но можно пользоваться метасимволами. С помощью дефиса можно указывать наборы символов: от первого до последнего. Например, [^0-9] означает любой символы, кроме 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
\# | Следующий за слэшем символ # (кроме a-z и 0-9). Например, \\ означает символ \, \. означает символ . (точка), \$ означает символ $ и т. д.
\b | Начало слова
\B | Конец слова
\xNN | NN - шестнадцатеричный код ASCII-символа (\x20 - пробел, \x4A - J, \x6A - j и т. д.)
\n | 0x10 (lf)
\r | 0x13 (cr)
\t | 0x09 (tab)
\s | Пробел (tab/space/cr/lf)
\S | Не пробел
\w | Символ слова (буквы, цифры, _)
\W | Символ не-слова
\d | Число
\D | Не число
\u | Символ в верхнем регистре
\l | В нижнем

Примеры:

\ совпадает со словом 'help' с точкой
/help\./
\ совпадает со словами 'cats', 'cars' и т. д.
/ca.s/
\ совпадает со словами 'testing', 'tester', но не 'the test'
/^test/
\ совпадает с выражением 'see me', но не с 'meter' или 'me and you'
/me$/
\ совпадает с одной (латинской) гласной буквой
/[aeiou]/
\ совпадает с одной буквой или цифрой
/[a-z0-9]/
\ совпадает с 'footer', 'footing', 'a foot', но не с 'afoot'
/\bfoot/
\ совпадает с 'afoot', 'foot.' (точка не считается частью слова)
\ не совпадает с 'footing'
/foot\B/
\ совпадает со словом 'foot' целиком
/\bfoot\B/
\ совпадает со словами 'q2w', 'r5t' и т. д.
/\D\d\D/

Расширенные спецсимволы

В отличии от обычных символов эти классы не совместимы с перловыми:

\N Ссылка внутри регэкспа на его же разобранную скобку, число N - номер нужной группы (скобки). Этот оператор работает с некоторыми ограничениями на тип ссылаемого блока - он работает, только если в ссылаемой скобке нет операторов повторения.

Пример:

\ совпадает с фразами 'man to man', '
\ hand to hand', '100 to 100' и т. д.
(\b\w+\B) to \1

Операторы

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

( ... ) | Сгруппировать символы в один паттерн и запомнить
| | Предыдущий или следующий паттерн (логическое "ИЛИ")
* | Ноль или больше раз
+ | Один или больше раз
? | 0 или 1 раз предыдущая маска
{n} | Повторять n раз
{n,} | Повторять n или больше раз
{n,m} | Повторять от n до m раз

Примеры:

\ совпадает со словами 'cat' или 'mouse'
/(cat)|(mouse)/
\ совпадает со словами 'dogs', 'doggie'
/dog(s|gie)/
\ совпадает с 'ma', 'maaa', 'maaaaaaa'
/ma+/
\ совпадает с 'm', 'maaa'
/ma*/
\ совпадает с 'yada yada yada'
/(yada ){2,}/
\ совпадает с 'fooandbar', 'foobar'
/foo(and)?bar/

Если после оператора добавить ?, то он превращается из жадного в нежадный. К примеру жадный * будет нежадным после замены его на *?. Жадные операторы производят максимальный захват в строке, а нежадные захватывают по минимуму.

Расширенные операторы

?#N | Это оператор "просмотра назад". N - число символов для просмотра.
?~N | Отрицание просмотра назад.
?= | Просмотр вперед.
?! | Отрицание просмотра вперед.

Заметьте, что хотя последние два оператора существуют и в перле, в нем они записываются в виде (?=foobar). В nnCron оператор выглядит как (foobar)?=.

Примеры:

\ совпадет с любым словом, после которого знак табуляции
\ при этом сам знак табуляции не войдет в число совпавших символов
/\w+(\t)?=/
\ совпадет с любым появлением 'foo', которое не продолжается 'bar'
/foo(bar)?!/
\ совпадет с любым появлением 'bar', которому предшествует 'foo'
/(foo)?#3bar/

Еще немного примеров:

\ совпадет с "foobar", "bar"
/(foo)?bar/
\ совпадет _только_ с "foobar"
/^foobar$/
\ совпадет с "foobar", "for", "far"
/f[obar]+r/
\ задает любое число с десятичной запятой
/([\d\.])+/
\ совпадет с "foofoofoobarfoobar", "bar"
/((foo)|(bar))+/


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

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

Введение

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

Простое сравнение

Любой символ совпадает с самим собой, если он не относится к специальным метасимволам описанным чуть ниже.

Последовательность символов совпадает с такой же последовательностью во входной строке, так что шаблон "bluh" совпадет с подстрокой "bluh'' во входной строке.

Если необходимо, чтобы метасимволы или escape-последовательности воспринимались как обычные символы, их нужно предварять символом "\", например, метасимвол "^" обычно совпадает с началом строк, однако, если записать его как "\^", то он будет совпадать с символом "^", "\\" совпадает с "\" и т.д.

Примеры:
foobar находит 'foobar'
\^FooBarPtr находит '^FooBarPtr'

Escape-последовательности

Любой символ может быть определен с помощью escape последовательности, так же как это делается в языках C или Perl: "\n'' означает начало строки, "\t'' - табуляцию и т.д.. Вообще, \xnn, где nn это последовательность шестнадцатеричных цифр, означает символ с ASCII-кодом nn. Если необходимо определить двухбайтный (Unicode) символ, используйте формат '\x{nnnn}', где 'nnnn' - одна или более шестнадцатеричных цифр.

\xnn символ с шестнадцатеричным кодом nn
\x{nnnn} символ с шестнадцатеричным кодом nnnn (более одного байта можно задавать только в режиме Unicode)
\t табуляция (HT/TAB), можно также \x09
\n новая строка (NL), можно также \x0a
\r возврат каретки (CR), можно также \x0d
\f перевод формата (FF), можно также \x0c
\a звонок (BEL), можно также \x07
\e escape (ESC), можно также \x1b

Примеры:
foo\x20bar находит 'foo bar' (обратите внимание на пробел посередине)
\tfoobar находит 'foobar' предшествуемый табуляцией

Перечни символов

Вы можете определить перечень, заключив символы в []. Перечень будет совпадать с любым одним символом перечисленным в нем.

Если первый символ перечня (сразу после "['') - "^'', то такой перечень совпадает с любым символом не перечисленным в перечне.

Примеры:
foob[aeiou]r находит 'foobar', 'foober' и т.д. но не 'foobbr', 'foobcr' и т.д.
foob[^aeiou]r находит 'foobbr', 'foobcr' и т.д.. но не 'foobar', 'foober' и т.д.

Внутри перечня символ "-'' может быть использован для определения диапазонов символов, например a-z представляет все символы между "a'' и "z'', включительно.

Если Вам необходимо включить в перечень сам символ "-'', поместите его в начало или конец перечня или предварите '\'. Если Вам необходимо поместить в перечень сам символ ']', поместите его в самое начало или предварите '\'.

Примеры:
[-az] 'a', 'z' и '-'
[az-] 'a', 'z' и '-'
[a\-z] 'a', 'z' и '-'
[a-z] все 26 малых латинских букв от 'a' до 'z'
[\n-\x0D] #10, #11, #12, #13.
[\d-t] цифра, '-' или 't'.
[]-a] символ из диапазона ']'..'a'.

Метасимволы

Метасимволы - это специальные символы, являющиеся важнейшим понятием в регулярных выражениях. Существует несколько групп метасимволов.

Метасимволы - разделители строк

^ начало строки
$ конец строки
\A начало текста
\Z конец текста
. любой символ в строке

Примеры:
^foobar находит 'foobar' только если он в начале строки
foobar$ находит 'foobar' только если он в конце строки
^foobar$ находит 'foobar' только если это единственное слово в строке
foob.r находит 'foobar', 'foobbr', 'foob1r' и т.д.

Метасимвол "^" по умолчанию совпадает только в начале входного текста, а метасимвол "$" - только в конце текста. Внутренние разделители строк, имеющиеся в тексте, не будут совпадать с "^'' и "$''.

Однако, если Вам необходимо работать с текстом как с многострочным, чтобы "^'' совпадал после каждого разделителя строки внутри текста, а "$'' - перед каждым разделителем, то Вы можете включить модификатор /m.

Метасимволы \A и \Z аналогичны "^'' и "$'', но на них не действует модификатор /m, т.е. они всегда совпадают только с началом и концом всего входного текста.

Метасимвол ".'' по умолчанию совпадает с любым символом, однако, если Вы выключите модификатор /s, то '.' не будет совпадать с разделителями строк.

Разделители строк интерпретируються так, как это рекомендовано на www.unicode.org ( http://www.unicode.org/unicode/reports/tr18/ ):

"^" совпадает с началом входного текста, а также, если включен модификатор /m, с точкой непосредственно следующей после \x0D\x0A, \x0A или \x0D. Обратите внимание, что он не совпадает в промежутке внутри последовательности \x0D\x0A.

"$" совпадает с концом входного текста, а также, если включен модификатор /m, с точкой непосредственно предшествующей \x0D\x0A, \x0A или \x0D. Обратите внимание, что он не совпадает в промежутке внутри последовательности \x0D\x0A.

"." совпадает с любым символом, но если выключен модификаторr /s, то "." не совпадает с \x0D\x0A и \x0A и \x0D.

Обратите внимание, что "^.*$" (шаблон для пустой строки) не совпадает с пустой строкой вида \x0D\x0A, но совпадает с \x0A\x0D.

Метасимволы - стандартные перечни символов

\w буквенно-цифровой символ или "_"
\W не \w
\d цифровой символ
\D не \d
\s любой "пробельный" символ (по умолчанию - [ \t\n\r\f])
\S не \s

Стандартные перечни \w, \d и \s можно использовать и внутри перечней символов.

Примеры:
foob\dr находит 'foob1r', ''foob6r' и т.д. но не 'foobar', 'foobbr' и т.д.
foob[\w\s]r находит 'foobar', 'foob r', 'foobbr' и т.д. но не 'foob1r', 'foob=r' и т.д.

Метасимволы - повторения

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

* ноль или более раз ("жадный"), то же что {0,}
+ один или более раз ("жадный"), то же что {1,}
? ноль или один раз ("жадный"), то же что {0,1}
{n} точно n раз ("жадный")
{n,} не менее n раз ("жадный")
{n,m} не менее n но не более m раз ("жадный")
*? ноль или более раз ("не жадный"), то же что {0,}?
+? один или более раз ("не жадный"), то же что {1,}?
?? ноль или один раз ("не жадный"), то же что {0,1}?
{n}? точно n раз ("не жадный")
{n,}? не менее n раз ("не жадный")
{n,m}? не менее n но не более m раз ("не жадный")

Т.о. {n,m} задает минимум n повторов и максимум - m. Повторитель {n} эквивалентен {n,n} и задает точно n повторов. Повторитель {n,} задает минимум n повторов. Теоретически величина параметров n и m не ограничена, но рекомендуется не задавать большие значения, поскольку в некоторых ситуациях это может потребовать существенных затрат времени и ОЗУ при обработке такого повторителя в связи с рекурсивным характером работы.

Если фигурные скобки встречаются в "неправильном" месте, где они не могут быть восприняты как повторитель, то они вопринимаются просто как символы.

Примеры:
foob.*r находит 'foobar', 'foobalkjdflkj9r' и 'foobr'
foob.+r находит 'foobar', 'foobalkjdflkj9r' но не 'foobr'
foob.?r находит 'foobar', 'foobbr' и 'foobr' но не 'foobalkj9r'
fooba{2}r находит 'foobaar'
fooba{2,}r находит 'foobaar', 'foobaaar', 'foobaaaar' и т.д.
fooba{2,3}r находит 'foobaar', или 'foobaaar' но не 'foobaaaar'

Небольшое пояснение по поводу "жадности". "Жадные" варианты повторителей пытаются захватить как можно большую часть входного текста, в то время как "не жадные" - как можно меньшую. Например, 'b+' как и 'b*' примененные к входной строке 'abbbbc' найдут 'bbbb', в то время как 'b+?' найдет только 'b', а 'b*?' - вообще - пустую строку; 'b{2,3}?' найдет 'bb', в то время как 'b{2,3}' найдет 'bbb'.

Вы можете переключить все повторители в выражении в "не жадный" режим, воспользовавшись модификатором /g.

Метасимволы - варианты

Вы можете определить перечень вариантов, используя метасимвол "|'' для их разделения, например "fee|fie|foe" найдет "fee'' или "fie'' или "foe'', (так же как "f(e|i|o)e"). В качестве первого варианта воспринимается все от предыдущего метасимвола "('' или "['' или от начала выражения до первого метасимвола "|'', в качестве последнего - все от последнего "|'' до конца выражения или до ближайшего метасимвола ")''. Обычно, чтобы не запутаться, набор вариантов всегда заключают в скобки, даже если без этого можно было бы обойтись.

Варианты пробуются начиная с первого и попытки завершаются сразу же как удастся подобрать такой при котором совпадет вся последующая часть выражения (подробнее см.Механизм работы). Это означает, что варианты не обязательно обеспечат "жадное" поведение. Например, если применить выражение "foo|foot" ко входной строке "barefoot'', то будет найдено "foo'' так это первый вариант который позволил совпасть всему выражению.

Обратите внимание, что метасимвол "|'' воспринимается как обычный символ внутри перечней символов, например, [fee|fie|foe] означает ровно то же самое что и [feio|].

Примеры:
foo(bar|foo) находит 'foobar' или 'foofoo'.

Метасимволы - подвыражения

Метасимволы ( ... ) могут также использоваться для задания подвыражений - по завершении поиска выражения Вы можете обратиться к любому подвыражению используя свойства MatchPos, MatchLen и Match, а также подставлять подвыражения в некий шаблон, используя метод Substitute).

Подвыражения нумеруются слева направо, в порядке появления открывающих скобок.

Первое подвыражение имеет номер '1' (выражение в целом - '0', к нему можно обращаться в Substitute как '$0' так и '$&').

Примеры:
(foobar){8,10} находит строку содержащую 8, 9 или 10 копий 'foobar'
foob([0-9]|a+)r находит 'foob0r', 'foob1r' , 'foobar', 'foobaar', 'foobaar' и т.д.

Метасимволы - обратные ссылки

Метасимволы от \1 до \9 воспринимаются как обратные ссылки. \<n> совпадает с ранее найденным подвыражением #<n>.

Примеры:
(.)\1+ находит 'aaaa' и 'cc'.
(.+)\1+ также находит 'abab' и '123123'
(['"]?)(\d+)\1 находит "13" (в дв.кавычках), или '4' (в один.кавычках) или 77 (без кавычек) и т.д.

Модификаторы

Вы можете изменять модификаторы несколькими способами.

Любой модификатор может меняться с помощью специальной конструкции (?...) внутри регулярного выражения.

i
Регистро-независимый режим (по умолчанию использует выбранный в ОС язык по умолчанию).

m
Воспринимать входной текст как многострочный, при этом метасимволы "^'' и "$'' будут совпадать не только в начале и конце текста в целом, но и в начале и в конце всех имеющихся в тексте строк (см. также Разделители строк).

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

g
Не стандартный модификатор. Выключая его Вы переключаете все повторители в "не жадный" режим (по умолчанию этот модификатор включен). Т.е. если его отключить, то все '+' работают как '+?', '*' как '*?' и т.д.

x
Позволяет форматировать шаблон чтобы обеспечить более легкую читаемость (см. описание ниже).

r
Не стандартный модификатор. Если включен, то диапазоны вида а-я включают также букву 'ё', А-Я включают 'Ё', а а-Я включает вообще все русские буквы.

Модификатор /x заставляет игнорировать пробелы, табуляции и разделители строк, что позволяет форматировать текст выражения. Кроме того, если встречается символ #, то все последующие символы до конца строки воспринимаются как комментарий, например:

(
(abc) # Комментарий 1
| # Пробелы внутри выражения также игнорируются
(efg) # Комментарий 2
)

Естественно, это означает что, если Вам нужно вставить в выражение пробел, табуляцию или разделитель строки или #, то в расширенном (/x) режиме это можно сделать только предваряя их '/' или используя /xnn (внутри перечней символов все эти символы воспринимаются как обычно)

Расширения Perl

(?imsxr-imsxr)

Позволяет изменять значения модификаторов

Примеры:
(?i)Saint-Petersburg находит 'Saint-petersburg' и 'Saint-Petersburg'
(?i)Saint-(?-i)Petersburg находит 'Saint-Petersburg' но не 'Saint-petersburg'
(?i)(Saint-)?Petersburg находит 'Saint-petersburg' и 'saint-petersburg'
((?i)Saint-)?Petersburg находит 'saint-Petersburg', но не 'saint-petersburg'

(?#text)
Комментарий, просто игнорируется. Обратите внимание, что в комментарии такого вида невозможно поместить символ ")", поскольку он воспринимается как конец комментария.