СтатьиРегулярные выражения, Часть IГлава 1. Шерлок Холмс спешит на помощь вебпрограммисту или регулярные выражения на пальцахВведение.Каждый веб-программист сталкивался с задачей, когда в произвольном тексте нужно найти какие-то данные по какому-то закону, проверить данные, которые поступили от пользователя, подвергнуть найденные данные сложной модификации. Можно изобретать велосипед, а можно использовать средства, которые используют программисты всего мира. Иной раз кажется, что профи пользуются какими-то инструментами, приемами, которые доступны только им. Разочарую читателя, что профи используют те же средства и инструменты, что и вы, только разница состоит в том, что они ими умеют пользоваться и умеют выбирать, какой инструмент стоит использовать в конкретном случае. Данный материал призван помочь программистам решать насущные задачи при помощи регулярных выражений. Я постараюсь описать самые основы использования этого инструмента, чтобы вы не смотрели на комбинацию подобную этой:
Общая задача механизма регулярных выражений — находить или не находить совпадения строки или ее части с шаблоном. Проанализируем первое предложение этого абзаца на предмет непонятных или пугающих слов. "Механизм регулярных выражений" и "шаблон" — вот два слова, которые меня повергли в уныние, когда я понял, что без использования регулярных выражений мне не обойтись. Имеем какой-то механизм, который что-то ищет и находит либо ищет и не находит, с этим "что-то" связаны такие понятия как строка и шаблон. Вот с них и будем разбираться и ими же и закончим, потому что после того, как мы разберемся, что делает этот механизм со строками и шаблонами, нам не надо будет лезть в учебники по математике и искать, что означают слова "регулярные выражения". Часть 1.Где мы видели шаблон? Пойдем к знакомой секретарше и спросим у нее. Правильно, ответ — шаблоны Microsoft Word! Чем отличается шаблон "Календарь" от шаблона "Изысканное резюме"? Данными и способом их подачи. Человек, который хотя бы раз видел и то и другое, без труда будет отличать календарь от резюме. Так почему тогда регулярные выражения пугают программиста? Ведь это почти то же самое! О чем думает человек, который видит календарь и знает что это такое, как он его узнает? Календарь — это документ, который разделен на блоки, каждый блок состоит из цифр, которые соответствуют дням месяца. Каждому месяцу соответствует только один блок, в месяце бывает не больше 31 дня, в феврале не бывает больше 28 (за исключением високосного года), дни, которым соответствует день недели воскресенье или государственный праздник выделены красным, можно продолжить систематизировать данные дальше, указав месяцы в которых ровно 30 и 31 день. Что мы сделали? Мы создали описание календаря, по-другому, мы описали данные, найдя которые в произвольном тексте, можно с определенной уверенностью сказать, что перед нами календарь. Подобное описание я называю шаблоном в контексте разговора о регулярных выражениях.
Грядет день, когда программист будет говорить компьютеру, какую он хочет написать программу, а компьютер будет ее писать, но пока что программисту приходится трудиться самому. Т.е. если я скажу, что ищу отрывок текста, который удовлетворяет описанию календаря, то в наше время компьютер меня не поймет. Садитесь поудобней, ибо если гора не идет к Магомету, то Магомету идет к горе. Как все программисты к горе пойдем сидя перед монитором. Наша задача — научиться описывать данные, которые мы хотим найти, в понятной компьютеру форме. А что вы еще не умеете? Стыдно! Это тоже умеет каждая секретарша. Надеюсь, что такое командная строка программисту объяснять не надо. Вот, что я получил: 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, работаем с командной строки. Но задание усложним. Теперь я дам описание данных, которые хочу найти, а вы попробуете это сделать, все инструменты вам знакомы.
Найти и показать все файлы и директории в текущей директории, название которых начинается на какой угодно символ, таких символов может быть сколько угодно (а может не быть вообще), но после них обязательно идет символ
Если вы читали внимательно, то без труда введете: 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
Просто? Еще бы! А теперь представьте, что я хочу найти не сколько угодно, а определенное количество каких угодно символов. Для этого используется символ
Найти и показать все файлы и директории в текущей директории, название которых начинается на какой угодно символ, таких символов может быть сколько угодно, но после них обязательно идет символ
Ее решение: 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 Первая часть закончена. Краткий итог:
Часть 2.В шаблон входят как обычные буквы, так и специальные символы, в прошлой части это была последняя строка, она же первая в этой. Вы сами не заметили, что мыслите абстрактно, описывая данные, которые вы хотите найти. Пока вас нужно к этому подталкивать, но скоро вы научитесь это делать самостоятельно, и я вам уже буду не нужен. Оказывается, что обычные символы и специальные символы имеют название. Каждый символ в отдельности называется литералом, каждый специальный символ называется метасимволом. Оставим в покое литералы, они пока нам не интересны, а лучше разберемся с метасимволами. Пока мы их знаем два, но нам и этого хватает.
Посмотрите, что обозначает символ на какой угодно символ, таких символов может быть сколь угодно (в том числе их может и не быть)
Звездочка
Знак вопроса
Рассмотрим подробнее понятие литерал, немного с другой стороны. Что из этих двух символов литерал, а что метасимвол? Элементарно, Ватсон, Зреет вполне логичный вопрос, а как отличил?
На логичный ответ даем логичный ответ. Мы знаем, что символы Кратко итог второй части:
Часть 3.Ну наконец-то этот автор перестал рассказывать байки про командную строку, а начал что-то говорить про то, ради чего собственно писалась статья, использование регулярных выражений в веб-программировании. Остановитесь и подумайте, все ли понятно в предыдущих двух частях? Если нет, прочтите их еще раз, проделайте все примеры, эксперементируйте! Читать часть 3 без понимания предыдущих частей не советую, потратите время зря.
Из великого и могучего языка PHP нам понадобится только одна функция: Ее общий формат таков:
preg_match("шаблон_поиска", "строка_в_которой_проводится_поиск", массив_с_результами_поиска)
Эта функция реализует обращение к механизму обработки регулярных выражений, поиск совпадения в строке и возврат совпадений в массив. Так. Стоять! Что значит обращение к механизму, поиск и возврат? Мы привыкли оперировать немного другими понятиями. Вернемся снова к команде Глава 2. Основы.Еще одно введениеРегулярные выражения — язык шаблонов (на самом деле это математический термин, кому интересно, читайте про детерминированные и недетерминированные конечные автоматы). Для того, чтобы выполнить какое-то действие, надо указать какое именно, обычно действие указывается функцией:
Тоже самое с функциями стандарта Posix типа
Обработка шаблона происходит посимвольно, как вы будете искать букву Шаблон — это своеобразный указатель, что искать в строке. Искать можно цифры, буквы, невидимые символы (пробел, таб).
Как вы в слове
Что нужно, чтобы указать, что вы ищете в строке то, что может быть любой буквой алфавита? Надо либо перечислить их все
Внимание, смертельный номер, такое возможно с русскими буквами
Но есть еще заглавные Но вот такой символьный класс описывает только один символ, а у вас в строке их вон сколько, это решается при помощи квантификаторов. КвантификаторыИтак, продолжаем, как я уже говорил, что условие поиска можно описать при помощи символьных классов. Один символьный класс может совпасть только с одним символом! Это надо понимать! Как в условии задать поиск двух символов? Приведу простой пример без символьных классов, который уже был затронут выше:
Ищем последовательность
Вернитесь к шагу 3 и вспомните, какой важный шаг вы сделали. Вы запомнили, где нашли первое совпадение. Совпадения следующего символа слова со следующим символом условия не произошло, поэтому надо взять следующий символ слова, в нашем слове с ошибкой это будет Искать по двум определенным символам мы уже научились, теперь будем учиться пользоваться символьными классами. Допустим, у нас есть строки: abcd12345efg fghi56789qwe Условие: найти в строках участки, которые состоят из четырех любых букв латинского алфавита, после которых следуют пять любых цифр.
Выше я рассказывал, как описать символ, который будет совпадать с любой латинской буквой, напомню, для этого мы используем символьный класс:
Символ условия, который совпадет с любой цифрой, описывается вот таким символьным классом: Вернемся к первоначальному заданию. Нам надо найти строки с четырьмя буквами вначале, сразу после которых идут пять цифр.
Из того, что мы уже знаем, можно написать:
Посмотрите сами, такая форма записи условия поиска будет работать! так как мы описали каждым символьным классом один символ в условии поиска. Вам не кажется, что слишком громоздкое условие поиска получается? И тут на помощь приходят квантификаторы. Вспоминаем английский: quantity — количество. Т.е. квантификатор — то, что выражает количество чего-то, в нашем случае количество символов в условии поиска. Упрощаем условие поиска при помощи квантификаторов: Все! кто не догадался, в фигурных скобках написано, сколько символов, описанных в символьном классе, может идти подряд в строке в которой ищем совпадение. Вот и получается, что после пяти любых букв латинского алфавита идет 5 любых цифр. Поиск происходит точно также как я описывал выше, только символы в условии поиска задаются не явно, а при помощи символьных классов. Каждый символьный класс описывает только один символ, количество схожих символов идущих подряд описывается квантификаторами. Естественно подобное задание количества символов в условии поиска не является единственным. Квантификаторы бывают разные!
Но квантификатор в фигурных скобках не является единственным способом задать количество символов идущих подряд, которые описаны символьным классом.
Применение квантификаторов к литералам
Вернемся в самое начало статьи, где мы искали перебором букву
Есть строки: abcdefg abcddefg abcdddefg abcddddefg Требование: Написать условие поиска совпадения для всех строк. Решение: Только что вы видели, как я применил квантификатор к литералу. К литералам можно применять любой из вышеприведенных квантификаторов.
Ясно, что применив условие поиска Символьные классы "для продвинутых"Вы уже увидели, как символьные классы облегчают описание условия совпадения. Разберемся с ними окончательно.
Что может входить в символьный класс? В символьный класс может входить любой литерал, а так же интервалы литералов. Для описания интервалов литералов используется символ
Как вы заметили, в последнем символьном классе я использую два диапазона.
Представим, что вам нужно описать условие, что в определенном месте строки могут стоять символы: либо a, либо g, либо 7, либо 4. Что делать? Писать символьный класс:
Объяснение простое, в символьном классе можно перечислять допустимые в условии поиска литералы. Перечисление литералов можно совмещать с указанием интервалов:
Это означает, что символ в строке может совпадать с 1, 4, буквами латинского алфавита c
И вот пример:
А как написать символьный класс, в который входят все символы кроме заданных? Например, все кроме
Пишем: Запомнить все!Наверное, у многих возникает вопрос, что если надо проверить всю строку на соответствие условию, а вернуть при помощи функций языка только часть строки? Как при помощи функций языка выполнить операцию не над всей строкой, которая удовлетворяет условию, а только над ее частью? Специально для таких целей существуют группирующие и одновременно сохраняющие круглые скобки. Пример: Строка может состоять из 5-ти букв латинского алфавита, после которых следует знак минуса, после которого следуют четыре цифры от 1 до 8. Задание:
Если мы просто хотим проверить строку на соответствие условию совпадения, то условие поиска будет вот таким:
Но нам надо запомнить цифры, поэтому добавляем инструкцию запоминания:
Как видите, то, что надо запомнить, выделено круглыми скобками. Внутри функции, которая будет выполнять операцию со строкой при помощи вышеприведенного условия, совпадение будет запоминаться в специальных переменных, в PHP к ней можно обращаться через Ближе к реалиямТот, у кого хватило желания дочитать до этого места, уже много понимает, пытается писать свои собственные условия поиска по строке, но у него не получается. Скорее всего это происходит из-за того, что вы не знаете многих мелочей работы с регулярными выражениями. Про некоторые я расскажу прямо сейчас.
Строка имеет начало и конец. Вы скажете, что это понятно каждому, но скорее всего вам придется немного изменить свое понятие о строках. Считайте, что вначале каждой строки и в конце каждой строки стоит невидимый символ. При поиске в реальной программе это надо учитывать, поставив в начале условия поиска символ Теперь при помощи этого условия на самом деле можно проверить строки asbvc1234 sdwtsv1234 на соответствие условию.
Не стоит путать
Ваше условие поиска может не работать по причине того, что вы не подозреваете о количестве, а так же виде пробельных символов, которые находятся в строке. Пробелы описываются либо литералом, просто в условии поиска ставьте пробел, либо видимой комбинацией символов:
Считайте, что В программах для того, чтобы показать, где начинается условие поиска (регулярное выражение), используют разделители. Пример:/p>
preg_match("/^[a-z0-9]/", $string, $matches);
Посмотрите, что указано в кавычках первого аргумента функции: сначала идет слеш, потом символ начала строки, потом идет символьный класс, потом идет снова слеш. Вот именно первый и последний слеш символизируют, что внутри них заключено регулярное выражение. Это пришло из Perl, где сразу за разделитемя еще можно указывать модификаторы, про которые позже. На данном этапе нужно знать, что в записи условия поиска нужны ограничители (два идентичных символа, не обязательно слеш, многие используют тильду). Примеры к главеВсе примеры взяты из вопросов, которые люди задают на различных форумах. Примеры буду разбирать на том языке, на котором сам пишу: PHP.
Глава 3.После изучения основ работы, стоит перейти к практическому применению регулярных выражений. Но если посмотреть на большинство регулярных выражений с теми знаниями, которые у вас есть, получится, что они до сих пор являются набором значков, правда, некоторые из них уже узнаваемы. В этой части я буду, базируясь на том, что рассказал в предыдущей главе, расширять ваш кругозор. Спецсимволы.Как показать невидимое? Как указать, например, что в условии поиска присутствует пробел? Наверное. многие догадываются, что для того, чтобы показать невидимое, надо сделать его видимым, т.е. ввести какой-то символ, набор символов, которые будут интерпретироваться, как невидимые. Вот с этими знаниями мы и пришли к изучению спецсимволов в регулярных выражениях.
Как видите, часто спецсимволы описывают какие-то частоиспользуемые множества символов, которые используются программистами каждый день, но эти множества имеют границы, т.е. либо цифры, либо буквы и подчеркивание, либо невидимые символы, но как описать все символы? Просто! Надо поставить точку!
А вы спросите тогда, а что делать, если надо описать именно точку, а не все символы? Поставьте перед точкой обратный слеш:
Но вы не унимаетесь и хотите знать, что делать если вы ищите в тексте обратный слеш, после которого идет точка. Все просто, как видите, обратный слеш, входит практически в любой спецсимвол, а так же нужен, чтобы из спецсимвола сделать литерал, значит этот обратный слеш, сам по себе является спецсимволом, и для того, чтобы превратить его в литерал, надо следовать тем же правилам, которые были использованы при превращении точки из спецсимвола в литерал, а именно перед спецсимволом, поставить еще один спецсимвол, чтобы больше не выдумывать никаких специсимволов, решили пользоваться тем же слешем. Итак чтобы поставить обратный слеш в виде литерала в условии поиска,надо его удвоить вот так: Аналогично, чтобы поставить два обратных слеша их надо тоже удвоить вот так: Альтернативы
Быть или не быть... Извечная альтернатива! После прочтения этой части, вы сможете самостоятельно записать гамлетовскую альтернативу при помощи регулярных выражений. Сначала как всегда разберемся с данными, которые надо обработать. У Гамлета был выбор между быть (
Понятно, что (be) (not\sto\sbe) Теперь вернемся к выбору из двух групп литералов:
Вот эта палочка
preg_match("/^(be)|(not\sto\sbe)$/", $alternate, $answer);
Регулярное выражение совпадет в случае
Вначале объяснения я вопрошал, между чем выбирать будем. Думаю, самое время разобраться, между чем вообще можно выбирать. Выбирать можно между литералами и между группами литералов. Как уже было сказано, группы литералов объединяются круглыми скобками, если надо выбрать между одиночными литералами, то два литерала между, которым стоит вертикальная черта, надо сгруппировать скобками.
Пример выбора из двух литералов:
Пример выбора из двух групп литералов:
В случае с выбором между группами литералов, либо между одиночными литералами, литералы объединяются при помощи круглых скобок, но в первой статье в части Запомнить все, я сказал, что все, что заключается в круглые скобки, запоминается в специальных переменных. Запоминается, значит использует ресурс "память". Что делать, если нам не нужно запоминать выделенные группы литералов, на надо их сгруппировать? Надо заставить скобки не запоминать! Делается это при помощи последовательности
Теперь ни группа литералов
Понятно, что, если надо поставить литерал Пример к главеУ администратора есть список пользователей, которые генерирует программа, либо утилита, генерирует она этот список в формате: фамилия имя отчество фамилия и о фамилия и.о.
Как всегда боссу требуется какая-то статистика, но его абсолютно не интересуют ни имена ни отчества, ему нужна фамилия и инициалы в виде: Надо что-то делать... я сделал это вот так (цикл перебора по всем строкам, которые генерирует утилита, напишете сами):
preg_match("/([^\s]+)\s+([^\s.])[^\s.]*(?:\s|\.)([^\s.])[^\s.]*/",$income_str,$out_arr);
Сначала разберемся с данными, которые поступают на вход, чтобы регулярное выражение срабатывало в 100% случаев. Данные не надо проверять на совпадение с условием, так данные хранящиеся в системе прошли эту проверку при их вводе. И фамилия и имя и отчество могут состоять из любых символов, так как мы не знаем, какие символы разрешает система, и какие символы ввел в систему пользователь, но достоверно известно, что фамилия отделена от имени пробелом, имя и отчество могут быть полными, могут сотоять из одной буквы-сокращения, могут быть разделены пробелом, либо после каждой буквы стоит точка. Если вы научитесь сами сотавлять подобные описания данных, по которым вам нужно проводить поиск, то большая часть вашей работы сделана, потому что осталось только записать предыдущее предложение на языке регулярных выражений:
Для обозначения имени исползуется несколько способов:
Что надо искать? Все, после пробела между фамилией и именем и до следующего пробела, либо точки, причем имя или его сокращение состоит минимум из одной буквы.
Получается, что имя мы описали вот такой конструкцией:
Что идет после имени? в любом случае там идет либо пробел, либо точка. Значит нужно пройти этот участок так, чтобы выполнилось условие выбора из двух символов:
Вам нужно запоминать что стояло между фамилией и отчеством, пробел или точка? Мне нет! Поэтому из сохраняющих и группирующих скобок делаем только группирующие: А теперь посмотрите на отчество, с точки зрения регулярных выражений, символы, из которых оно составлено, подчиняется тем же правилам, что и имя, поэтому описание следующей части регулярного выражения и до конца смотрите выше. |
||