Профессиональная разработка Web-приложений.  
Боишься нашего дизайна?
Новости
PDF журнал
Участники проектa
Сотрудничество
Ссылки
Карта сайта
Комментарии
Комментарии к статье
Добавить комментарий
Обсудить на форуме
Информация об авторе
Оценка статьи

Краткое описание регулярных выражений: POSIX и PCRE

Описание базовых принципов и понятий работы регулярных выражений.
Описание синтаксиса языка регулярных выражений стандарта POSIX. Функции. Примеры.
реимущества стандарта реглярных выражений PCRE над POSIX. Рассыльщик почты с обильным использованием PCRE.


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


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

Начну с того, что php поддерживает два стандарта регулярных выражений: POSIX и, начиная с четвертой версии, совместимые с Perl. Первый стандарт используется и сервером Apache в mod_rewrite а так же... MySQL в своих запросах (поищите слово "REGEXP" в руководстве по mysql, может сразу поймете, а я об этом позже расскажу). Второй, как ясно из названия, используется в системе perl. Два этих стандарта различаются несильно - во втором есть специальные символы, заменяющие наиболее часто используемые классы символов (например, цифры - \d, а буквы и цифры - \w) и специальные параметры шаблонов, позволяющие определять регистрозависимость поиска, привязку к концам строк и т.д (в функциях стандарта POSIX регистрозависимость реализована просто: есть функции ereg и ereg_eeplace, есть eregi (insensitive) и eregi_replace). В остальном же оба стандарта совместимы, а приемы написания шаблонов одинаковые.

Если вы работали с Norton/Volkov/Windows Commander или Far, то знаете такую вещь как wildcards. Например: delete c:\windows\*.* удаляет все файлы из указанной директории. :) В именах файлов особых изощрений делать не приходится, поэтому система простая: символ * означает любой набор символов, в том числе пустой (*.txt), символ ? - любой символ или никакого символа (document?.txt) и еще какие-то обозначения для букв и цифр (я, честно говоря, ими давно не пользовался, поэтому так не вспомню).

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

Итак, задача системы - помимо четко заданных символов ("Вася(.*)Пупкин") позволить указать пользователю поиск заданного количества заданных символов. В приведенном примере с Васей Пупкиным между словами задано любое количество любых символов. Если надо найти шесть цифр, то пишем "[0-9]{6}" (если, например, от шести до восьми цифр, тогда "[0-9]{6,8}"). К чему это все? К тому, что в отличие от wildcard из операционной системы, здесь разделены такие вещи как указатель набора символов и указатель необходимого количества: <набор символов><квантификатор> Вместо набора символов может быть использовано обозначение любого символа - точка, может быть указан конкретный набор символов (поддерживаются последовательности - упоминавшиеся "0-9"). Может быть указано "кроме данного набора символов".

Указатель количества символов в официальной документации по php называется "квантификатор". Термин удобный и не несет в себе кривотолков. Итак, квантификатор может иметь как конкретное значение - либо одно фиксированное ("{6}"), либо как числовой промежуток ("{6,8}"), так и абстрактное "любое число, в т.ч. 0" ("*"), "любое натуральное число" - от 1 до бесконечности ("+": "document[0-9]+\.txt"), "либо 0, либо 1" ("?"). По умолчанию квантификатор для данного набора символов равен единице ("document[0-9]\.txt").

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

Как всякий гибкий инструмент, регулярные выражения гибки, но не абсолютно: зона их применения ограничена. Например, если вам надо заменить в тексте одну фиксированную строку на другую, фиксированную опять же, пользуйтесь str_replace. Разработчики php слезно умоляют не пользоваться ради этого сложными функциями ereg_replace или preg_replace, ведь при их вызове происходит процесс интерпретации строки, а это серьезно потребляет ресурсы системы. К сожалению, это любимые грабли начинающих php-программистов (даже я сам, волею судьбы, сперва увидел в руководстве функцию ereg_replace, а только потом, позже, str_replace).

Пользуйтесь функциями регулярных выражений только если вы не знаете точно, какая "там" строка. Из примеров: поисковый код этого сайта, в котором из строки поиска вырезаются служебные символы и короткие слова а так же вырезаются лишние пробелы (вернее, все пробелы сжимаются: " +" заменяется на один пробел). При помощи этих функций я проверяю email пользователя, оставляющего свой отзыв. Много полезного можно сделать, но важно иметь в виду: регулярные выражения не всесильны. Например, сложную замену в большом тексте ими лучше не делать. Ведь, к примеру, комбинация "(.*)" в программном плане означает перебор всех символов текста. А если шаблон не привязан к началу или концу строки, то и сам шаблон "двигается" программой через весь текст, и получается двойной перебор, вернее перебор в квадрате. Нетрудно догадаться, что еще одна комбинация "(.*)" означает перебор в кубе, и так далее. Возведите в третью степень, скажем, 5 килобайт текста. Получается 125 000 000 000 (прописью: сто двадцать пять миллиардов операций). Конечно же, если подходить строго, там стольких операций не будет, а будет раза в четыре-восемь меньше, но важен сам порядок цифр.

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

Часть 2: POSIX

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

Набор символов

.точкалюбой символ
[<символы>]квадратные скобкикласс символов ("любое из")
[^<символы>] негативный класс символов ("любое кроме")
-тиреобозначение последовательности в классе символов ("[0-9]" — цифры)

Особо объяснять ничего не нужно. Разве что следующее: не пользуйтесь классом символов для обозначения всего лишь одного (вместо "[ ]+" вполне сойдет " +"). Не пишите в классе символов точку — это ведь любой символ, тогда другие символы в классе будут просто лишними (а в негативном классе получится отрицание всех символов).

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

Это, как я уже писал, указатель количества заданных символов. Квантификатором можно указать как конкретное значение, так и пределы. Если число заданных подпадает под пределы квантификатора, фрагмент выражения считается совпавшим с разбираемой строкой. Синтаксис: {<количество>} либо {<минимум>, <максимум>}

Если нужно указать только необходимый минимум, а максимума нет, просто ставим запятую и не пишем второе число: "{5,}" ("минимум 5"). Для наиболее часто употребляемых квантификаторов есть специальные обозначения:

*"звёздочка" или знак умножения{0,}
+плюс{1,}
?вопросительный знак{0,1}

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

Якоря

^привязка к началу строки
$привязка к концу строки

Эти символы должны стоять соответственно в самом начале и в самом конце строки. Чтобы интерпретатор корректно понял символ $ в конце, желательно добавить к нему обратный слэш: ereg("foo\$", $bar)Структура

Сейчас будет сложное описание, мне оно и самому не нравится. Эта вещь необходима для сложных запросов. Например, вам надо, чтобы в тексте были либо только маленькие буквы, либо только большие, либо только цифры. Класс символов "[a-zA-Z0-9]" не подходит. Тогда пишем такое:

if (ereg("[a-z]+|[A-Z]+|[0-9]+", $text)) ...

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

if (ereg("<tag>([a-z]+|[A-Z]+|[0-9]+)</tag>", $text)) ...

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

ereg_replace("<a href=([^>]+)>[^<]+</a>", "\\0 [\\1]", $text);

Первые скобки — первый вложенный шаблон — можно получить "на выходе" через обозначение "\n" (поскольку обратный слэш в php и многих других языках используется для спецсимволов, надо поставить перед ним еще один такой же, чтобы прогамма понимала его буквально). Под нулевым номером — вся совпавшая строка. У себя в печатной версии статьи я не пишу ссылки сразу в тексте, а делаю их список в конце примерно так:

if (ereg("<a href=([^>]+)>([^<]+)</a>", $text, $match)) {
  for ($a=0;$a<sizeof($match[0]);$a++) {
    $b = $a+1;
    $text = str_replace($match[0][$a], $match[0][$a]." [$b]", $text);
    $match[1][$a] = "$b) ". $match[1][$a];
    };
  $text .= "<br><h2>Ссылки, использованные в выпуске:</h2>". implode("<br>", $match[1]);
  };

Функция ereg (и eregi), если ей указать в третьем параметре переменную, то туда будут записаны все подстроки в виде двухмерного массива.

Это, собственно, все. Дальше нужно только уметь составлять шаблоны. Приведу несколько примеров.

  • Переписывание адресов сервером Apache (как я уже отметил, Apache работает со стандартом POSIX).
  • Поиск по базе данных: из пользовательского поискового запроса делается sql-запрос. Если отбросить создание статистики поиска (сколько найдено всего, сколько по каждому слову), то получится, что необходимо всего 6-7 строк кода. Там же описана и подсветка слов в результатах поиска. Кстати, важное замечание: перед тем, как вырезать короткие слова из строки я заменяю пробелы между словами на двойные. Почему? Потому что совпадающие с шаблоном строки не должны наезжать друг на друга.

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

  • Как хранить новости в файлах и не бегать циклом по дате:

    $handle=opendir($newsdir);
    while ($file = readdir($handle)) {
      if (is_file($file) && ereg("^[0-9]{6}\.txt\$", $file))
        print ("<p align=justify><b>". 
        ereg_replace("^([0-9]{2})([0-9]{2})([0-9]{2})\.txt\$", "\\1.\\2.20\\3", $file).  
        "</b> ".implode("", file($file)). "</p>");
    closedir($handle);

    4. Проверка правильного написания email-а:

    if (!eregi("^[a-z0-9\._-]+@[a-z0-9\._-]+\.[a-z]{2,4}\$", $email))   print("Bad email: \"$email\"");

    На этом все. В следующем выпуске — стандарт PCRE, точнее дополнительные возможности, которые он предоставляет.

    Часть 3: PCRE

    И вот, наконец, серия выпусков про регулярные выражения подходит к концу. Поговорим о регулярных выражениях совместимых с Perl (Perl compatible regular expressions — PCRE).

    Самое главное их преимущество перед POSIX, как мне уже подсказывают — возможность "жадного" поиска. Вопросительный знак в PCRE выступает еще и как минимизатор квантификатора: .*? Найдет минимальную подходящую строку. Вроде бы ничего особенного? Нет, это очень особенная вещь. Например, какой пример я приводил в прошлом выпуске про печатную версию текста?

    $text = ereg_replace("<a +href=([^>]+)>[^<]+</a>", "\\0 [\\1]", $text);

    То есть, внури ссылки не должно быть тегов (например "<a href=...><b>...</b></a>"). Если же сделать так:

    $text = ereg_replace("<a +href=([^>]+)>.*</a>", "\\0 [\\1]", $text);

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

    $text = preg_replace("/<as+href=(.*?)>.*?</a>/", "\\0 [\\1]", $text);

    Программа подберет для всех ссылок минимальную подходящую строку, т.е. только до тега "</a>". Описывать значение такой особенности PCRE нет смыла — оно огромное. :) Идем дальше.

    Цифры теперь можно обозначить не как "[0-9]", а просто "\d". Не-цифры ("[^0-9]") как "\D". Очень удобно. Вот остальные обозначения:

    \w[a-z0-9]
    \W[^a-z0-9]
    \s[ ]
    \S[^ ]

    Рекомендую заглянуть в выпуски про поиск — там эти символы используются.

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

    iрегистронезависимый поиск
    mмногостроковый режим. По умолчанию PCRE ищет сопвадения с шаблоном только внутри одной строки, а символы "^" и "$" совпадают только с началом и концом всего текста. Когда этот параметр установлен, "^" и "$" совпадают с началом и концом отдельных строк.
    sсимвол "." (точка) совпадает и с переносом строки (по умолчанию — нет)
    Aпривязка к началу текста
    Eзаставляет символ "$" совпадать только с концом текста. Игнорируется, если установлен парамерт m.
    UИнвертирует "жадность" для каждого квантификатора (если же после квантификатора стоит "?", этот квантификатор перестает быть "жадным").

    Естественно, регистр в параметрах имеет значение. Остальное о них можно прочесть в руководстве по php.

    Теперь о функциях PCRE.

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

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

    Это все, что я могу сказать про регулярные выражения. Дальше — только искусство комбинирования строк и написания алгоритмов.

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

  • Введение в Perl. Регулярные выражения (шаблоны)
  • Фрагмент моего старого поискового кода



For comment register here
   2001-02-07 11:22
Супер :))

   Unknown 2001-02-08 13:45
А Вас ошибка в ссылке... http://http://www.citforum.ru/internet/perl_tut/re.shtml не может работать... ;))

   Unknown 2001-02-10 18:14
Нашел неплохой учебник по регулярным выражениям. Скачать можно с http://www.parfyonov.com/download/regexp.zip

   2001-02-14 17:49
Ochen vazhnoe otli4ie perlovyh regexpov ot posixovyh - vozmojnost nezhadnogo poiska.



PS: Sorry za latinicu - ya iz pod linksa, a on o4en krivo kodiruet formy...

   Unknown 2001-02-14 18:42
По-русски: Очень важное отличие перловых регэкспов от позиксовых - возможность нежадного поиска. :)

Да, я про это и собирался писать.

   Unknown 2001-02-25 06:23
Уважаемый, вы кое-что перепутали в своей заметке о регулярных выражениях. Как раз таки по умолчанию перловские регулярные выражения жадны (greedy). Жадными они называются потому, что стремятся "заглотить" побольше. Знак вопроса делает их ungreedy. Отсюда и "параметр" такой - буква 'u'.

   Unknown 2001-02-25 07:16
Ясно. Я понял "жадность" в смысле "выдать минимальную строку". Исправлю.

   2001-03-14 14:39
Дима, я не очень разбираюсь в Regexp-ах, Но нашел ошибку у тебя в строчке
$text = preg_replace("/<as+href=(.*?)>.*?</a>/", "\1 [\1]", $text);
нужно перед </a> поставить backslash.
$text = preg_replace("/<as+href=(.*?)>.*?</a>/", "\1 [\1]", $text);
И хотел бы попросить у тебя помощи в свой проблеме (тоже Regexp). Уже 2 недели "отжимаюсь" -(....

   2001-03-14 14:42
О! пардон. это репается (пропадает обратный слешь)при загрузке страницы.... я его вписал, а он вырезался при обработке. Видимо у тебя там тоже он есть.

   Unknown 2001-03-14 15:32
Эти обратные слэши... вечно исчезают.

   Unknown 2001-03-14 15:35
Эти обратные слэши... вечно исчезают.

   2001-03-24 05:50
Как сжать все повторные выражения, например [/url][url][/url][/url] должна вывести [/url][url][/url]

   Unknown 2001-03-24 06:43
Разбить explod-ом. Как через рег. выражения - не представляю.

   2001-03-30 15:12
Как написать регулярное выражение что бы оно вырезало содержимое из скобок например "..(456,78).."
насколька я знаю () это служебные символы ? конструкции типа ([0-9]) результатов не дают ....

   Unknown 2001-03-30 16:05
Ну, дык! Конструкции неправильные.

$foo = preg_replace("/([d,]*)/", "", $foo);

   Unknown 2001-04-10 17:34
а как с помощью рег. выражений "выдрать" из страницы все адреса ссылок (то, что находится за <a href=...) и переправить их на что-либо другое?

   Unknown 2001-04-10 18:27
Уф! Я ж писал про подобное.
preg_replace("/<a href=http://(.*?)>/", "<a href=clickcount.php?url=\1>", $text);

   2001-07-27 13:12
Ну тут всё понятно, а если я хочу выризать и "<A href=...>" и "<A hrEf=...>" и "<A HREF=...>" и ...
Что мне делать с регулярным выражением???

   2001-08-22 10:35
Ссылочка на файл не работает.

   2001-09-04 18:00
Задача:
заменить в тексте все вида: http:что-то
на: <A HREF="http:что-то">http:что-то</A>
Пишу команду:
$message = preg_replace("/(http://[w&/-:.?=$%])/i", "<A HREF="1">1</A>", $message);
Если нормальная ссылка вида: http:www.zoo.kharkov.ua, то заменяется корректно.
А в ссылках вида: http://xpoint.ru/cgi-bin/forum.cgi?action=create_form&;id=8 - все что идет после знака &, остается за пределами тега.
И еще если в конце ссылки стоит "." - не могу ее отбросить, она попадает в ссылку.
Может кто дать свой вариант PHP-кода, для замены http://, ftp:// и e-mail на сответствующие сылки.
Пожалуйста!

   Unknown 2001-10-08 13:24
Чувствуется ваша любовь к регекспам и по статьям по этим, и по тому, во что превращается вводимая информация в формах.

   Unknown 2001-10-08 14:17
там где-то лишний htmlspecialchars. сейчас переделываю механизм администрирования.

   2001-10-19 14:05
Статья, конечно хороша, но... Мануал тоже, в принципе, понятен... Так какого этого-самого, если я пишу
if(ereg("([a-z])([0-9])", $myvar))
$myvar = ereg_replace("()([0-9]) ", "<sup>2</sup>", $myvar);
(т.е. Если в слове сначала английская маленькая, а за ней цифра, написать цифру в тегах <sup></sup>), она когда хочет исправляет, когда не хочет, не очень?
С уважением, Костюров. С. А.

   2002-01-30 12:06
Очень хочется узнать, как бороться с кирилицей! [[:alnum:]] не помогает

   2002-03-16 11:34
этот код не очень то справляется с проверкой мыла.. :(

if (!eregi("^[a-z0-9._-]+@[a-z0-9._-]+.[a-z]$", $email))
print("Bad email: "$email"");

в частности, он пропускает мыло в таком виде: pupkin@mail
its no good :(

   2002-03-17 00:09
На сайте были проблемы с отображением обратного слеша. Уже исправлено. Смотрите тексте статьи.

   Unknown 2002-04-04 13:35
юзер вводит в поле текст. текст может содержать ссылки (с
приставками http:// и www), так вот, этот текст затем отправляется
на обработку скрипту, а скрипт вставляет введенный текст в хтмл
страницу, тем самым ссылки стали обычным текстом... Каким обарзом
нужно обработать текст так чтобы например текст:

заходи на www.umnik.vov.ru/?docs/php/php.htm

преобразовался в :

заходи на <a

href=http://www.umnik.vov.ru/?docs/php/php.htm>;www.umnik.vov.ru/?docs/php/php.htm<;/a>

???
помогите мне плз ....

   2002-05-24 20:18
это спасет Васю? :-)))

if( !eregi( "^" .
"[a-z0-9]+([_.-][a-z0-9]+)*" .
"@" .
"([a-z0-9]+([.-][a-z0-9]+)*)+" .
".[a-z]" .
"$", $string, $regs) )

   Unknown 2002-12-06 12:51
Первый слеш нужен для того, чтобы сказать парсеру какой будет разделитель. Вместо него можно использовать др. символ:

preg_match("|regexp|i", ...)

Также этот разделитель необходимо указывать в preg_quote:

preg_quote($quoted, "|")

При этом он тоже будет защищен обратным слешем.

   2005-07-24 10:37
Есть проблема: нужно от начала строки выделить определенный кусок от ":code:" до ":/code:". Причем сколько раз в строке встречается ":code:", столько же ":/code:".

Например:
$str = ":code: Текст, текст :code: .... :/code: :/code: А этот текст не должен войти в результат :code:";
if (eregi("(^:code:)([^\x01])(:/code:])",$x,$re)) echo $re[0] ;
else echo "Соответсвие не найденно";
Результат : ":code: Текст, текст :code: .... :/code: :/code: А этот текст не должен войти в результат :code:";
А должен быть: ":code: Текст, текст :code: .... :/code: :/code:";

--P.s-----------
Для удобства, я бы хотел ":code:" поместить в переменную, а затем в регулярном выражении использовать ее, но я не знаю как.
--P.s-----------
Если кто знает ответы на эти вопросы, прошу будьте добры напишите мне на e-mail или в
ICQ - 1438234
e-mail - warlock12@list.ru

регулярные выражения, примеры, posix, pcre, match, preg, replace, шаблон, маска регулярные выражения, примеры, posix, pcre, match, preg, replace, шаблон, маска

 
 
 
    © 1997-2008 PHPClubTeam
[]