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

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

1. Вид функции для поиска соответствий

Формат функции такой:

preg_match (патерн, строка для поиска, массив результатов);

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

Строка для поиска -  любые данные, в которых мы будем что-то искать.

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

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

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

'/набор/модификаторы'

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

Модификаторы расширяют возможность поиска по строке. В строке модификаторов игнорируются пробелы, неверный символ вызывает ошибку типа warning. Вот все известные модификаторы:

i – регистро-независимый поиск по строке. Регистр букв не учитывается, что строчные, что прописные становятся равнозначны.

примеры:
preg_match ('/upper/', 'UPPER CASE'); – вернёт FALSE
preg_match ('/upper/i', 'UPPER CASE'); – вернёт TRUE

m – достаточно эксцентричный модификатор, имеющий специфическое применение. По умолчанию установка в набор символов ^ или $ рассматривается как поиск ОТ начала или ДО конца ОДНОЙ СТРОКИ. Т.е. если строка для поиска форматированный текст с переносами и указаны признаки поиска от начала или до конца, то без этого модификатора поиск будет произведён только по первой строке данных, все остальные строки будут проигнорированы. Если в наборе нет конкретной привязки к началу или к концу строки, то использование этого модификатора не имеет смысла.

примеры:
preg_match ('/^fird.*/', 'first line
second line
fird line
fourth line'); – вернёт FALSE

preg_match ('/^fird.*/m', 'first line
second line
fird line
fourth line'); – вернёт TRUE

s – если установлен этот модификатор, то все точки в наборе соответствуют не только любому символу, но и символу новой строки.

примеры:
preg_match ('/test.test/', 'test@test'); – вернёт TRUE
preg_match ('/test.test/', 'test-test'); – вернёт TRUE
preg_match ('/test.test/', "test\ntest"); – вернёт FALSE

preg_match ('/test.test/s', 'test@test'); – вернёт TRUE
preg_match ('/test.test/s', 'test-test'); – вернёт TRUE
preg_match ('/test.test/s', "test\ntest"); – вернёт TRUE

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

примеры:
preg_match ('/t e s t/', 't e s t'); – вернёт TRUE
preg_match ('/t e s t/', 'test'); – вернёт FALSE

preg_match ('/t e s t/x', 't e s t'); – вернёт FALSE
preg_match ('/t e s t/x', 'test'); – вернёт TRUE
preg_match ('/t\ e\ s\ t/x', 'test'); – вернёт FALSE
preg_match ('/t\ e\ s\ t/x', 't e s t'); – вернёт TRUE

e – применим только к функции preg_replace() и заставляет эту функцию обрабатывать полученное значение как php скрипт, экранируя любые кавычки обратной косой чертой.

примеры:
Преобразование всех тегов в HTML документе в верхний регистр:
$html = '<select><option value="all">Everything</option></select>';
preg_replace ('/(<\/?)(\w+)([^>]*>)/e', "'\\1'.strtoupper('\\2').'\\3'", $html);

вернёт:

<SELECT><OPTION value=\"all\">Everything</OPTION></SELECT>

S – этот модификатор следует использовать для наборов, которые применимы многократно и ускоряет процесс поиска.

примеры:
preg_match_all ('/[\w]/', $string, $matches); - выполняется за 0.17 секунд
preg_match_all ('/[\w]/S', $string, $matches); - выполняется за 0.15 секунд

U – интересный модификатор для поиска неопределённых значений. Если в наборе задан поиск *, чего угодно, то даже при первом нахождении такого условия php всё равно продолжит поиск до конца переменной для нахождения наилучшего, по его мнению, соответствия. Это ненужная работа, и дополнительная ненужная нагрузка, которая побеждается этим модификатором.

примеры:
preg_match ('/foo(.*)bar/', 'foobar foo--bar fubar'); - успокоится только по нахождению «bar foo--bar fu»
preg_match ('/foo(.*)bar/U', 'foobar foo--bar fubar');  - будет достаточно и первого слова

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

2.2 Принципы составления регулярных выражений.

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

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

^ – этот символ имеет 2 значения, в зависимости от того, где он стоит. Если этот символ стоит в начале набора, он символизирует, что поиск соответствия нужно начинать с самого начала СТРОКИ (помните пример с модификатором m?), многие ошибочно считают, что с начала данных для поиска.

примеры:
'/^test/' найдёт слово test в ”test it”, но не найдёт его в ”for test”, потому что первое слово явно не test.
'/test/' найдёт слово test в обоих случаях, так как нет привязки к началу строки.

Если этот символ стоит внутри класса, то означает отрицание

примеры:
'/[^test]/' будет истинно для всего чего угодно, но только не test.
'/^[^test]/' а это означает, всё что угодно с начала строки, но только не test.

$ – этот символ (доллар) признак конца документа, если стоит в самом конце набора и не экранирован.

примеры:
'/^test$/' не найдёт слово test ни в ”test it”, ни в ”for test”, потому что стоит жёсткая привязка к концу и к началу строки. По этому выражению поиск будет удачным, если строка состоит из единственного слова test.
'/test$/' найдёт слово test в ”for test”, это конец документа и соблюдение нашего условия.

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

Теперь переходим к классам. Класс – это сложное условие, заключённое в квадратные скобки, которые вы уже видели раньше.

[] – обозначение сложного условия. Мы применяли ранее его для обозначения отрицания, но возможности его велики.

примеры:
[ABCD] будет истинно, если есть хоть одна их этих букв. Классы можно делать сквозными, то есть это же условие эквивалентно [A-D]. Заметьте, что знак «минус» внутри класса обозначает промежуток, если он стоит вне класса, он расценивается как элемент для поиска.

preg_match ('/[A-D]/', 'testB'); – вернёт TRUE, так же как и для testA, testC, testD или в любой другой строке, где есть A, B, C, D независимо от места их расположения в строке. Если добавить модификатор i, то мы так же сможем успешно искать и a, b, c, d.

[a-zA-Zа-яА-Я] класс, который будет истинной ко всем буквам.
[0-9] класс для всех цифр.

Но есть уже заранее предопределённые классы в php:

\w – все буквы
\d – все цифры. Ещё иногда обозначается [[:digit:]], но \d однозначнее приятнее выглядит
\W – отрицание класса букв, то есть все, что не буквы
\D – отрицание класса цифр
\s – пробел, знак табуляции, символ новой строки, в общем любой разделитель.
\S – под этот класс подходят все «видимые» символы.

Вы обратили внимание на обратную косую черту? А зря. Этот знак в начале чего-либо вызывает экранирование, то есть чтобы обозначить, что дальше после него что-то будет… как вы уже поняли, буквы экранировать не надо, а надо экранировать так называемые мета-символы. Неэкранированные мета-символы являются системными, будьте внимательны! То есть если мы напишем \[ это уже не будет означать начало класса, а просто поиск [.

Запомните, все классы работают по условию ИЛИ.

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

примеры:
preg_match ('/a.cd/', 'azcd'); – вернёт TRUE, как и для любого ”a*cd”

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

примеры:
preg_match ('/a{4}/', 'a'); – вернёт FALSE
preg_match ('/a{4}/', 'aaaa'); – вернёт TRUE, но и для любого ”aaaaaaaaaaaaaaaa”, пока нет конкретной привязки к чему либо
preg_match ('/b{2,4}/', 'ab'); – вернёт FALSE, b должно быть от 2 до 4 по количеству
preg_match ('/^b{2,4}$/', 'bbb'); – вернёт TRUE, так как уже есть привязка к началу и концу строки, то “bbbbbbbbbbb” вернёт FALSE, и будет только истинно для строки с b в количестве от 2 до 4.
preg_match ('/^b{2,}$/', 'bbbbbbbbbbbbb'); – вернёт TRUE, так как не указано конечное количество и такой квантатор читается как не менее.

* – квантатор, который символизирует любое количество, вплоть до полного отсутствия элемента.

примеры:
preg_match ('/abc*d/', 'abcd'); – вернёт TRUE
preg_match ('/abc*d/', 'abcccccccccccccccccd'); – вернёт TRUE
preg_match ('/abc*d/', 'abd'); – вернёт TRUE, потому что “c” как бы присутствует в количестве 0.

+ – квантатор, который символизирует количество, не менее одного.

примеры:
preg_match ('/abc+d/', 'abcd'); – вернёт TRUE
preg_match ('/abc+d/', 'abcccccccccccccccccd'); – вернёт TRUE
preg_match ('/abc+d/', 'abd'); – вернёт FALSE, потому что “c” отсутствует.

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

| – символ, символизирующий о наличии нескольких возможных вариантов.

примеры:
preg_match ('/very nice|good/', 'very nice'); – вернёт TRUE
preg_match ('/very nice|good/', 'very good'); – вернёт TRUE
preg_match ('/very nice|good/', 'very cool'); – вернёт FALSE, потому что слово “cool” в альтернативах нет

? – символизирует о необязательном присутствии предшествующего элемента.

примеры:
preg_match ('/colou?r/', 'color'); – вернёт TRUE
preg_match ('/colou?r/', 'colour'); – вернёт TRUE
preg_match ('/colouu?r/', 'color'); – вернёт FALSE
preg_match ('/colouu?r/', 'colour'); – вернёт TRUE

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

примеры:
preg_match ('/colou?r\s(\w+)/', 'color black', $matches);
$matches[1]; – вернёт “black”
preg_match ('/\S+\s(\S+)\s\S+\s(\S+)/', 'name Vasya from Muhosransk', $matches);
$matches[1]; – вернёт “Vasya”
$matches[2]; – вернёт “Muhosransk”

(?:)  - Чтобы скобки нужны для выделения альтернатив или объединения проверки на существование группы символов и если в если эти данные не нужны в результатах буферизации, используется такой вид.

примеры:
preg_match_all ('/^(?:https?\:\/\/)?(?:www\.)?(.*)/m', "https://www.ptipti.ru\nhttp://ptipti.ru\nhttps://ptipti.ru\nwww.ptipti.ru\nptipti.ru", $matches);
Результатом, как и ожидалось, будет массив:
Array
(
[0] => Array
(
[0] => https://www.ptipti.ru
[1] => http://ptipti.ru
[2] => https://ptipti.ru
[3] => www.ptipti.ru
[4] => ptipti.ru
)

[1] => Array
(
[0] => ptipti.ru
[1] => ptipti.ru
[2] => ptipti.ru
[3] => ptipti.ru
[4] => ptipti.ru
)

)
Рассмотрим сложную конструкцию (?:https?\:\/\/)? детальнее. вопросик после скобок ()? говорит о том, что эта часть может как присутствовать, так и отсутствовать. Начало в скобках (?: говорит о том, что эту часть не надо будет буферизировать в результаты. Ну и самое простое s? означает, что этой буквы может и не быть. Очень важно самому не запутаться 🙂

(?!) и (?=) - Отрицательный и положительный вперёдсмотрящие.
Эта конструкция иногда необходима, для определения части обязательного текста или выражения.
примеры:
preg_match_all('/href\=(?:\'|\")?((?:\/|[^\s\'\"]*ptipti.ru\/)(?!wp\-content|wp\-includes)[^\s\'\"]*)/i', file_get_contents('http://ptipti.ru'), $matches);
Этот пример демонстрирует работу отрицательного вперёдсмотрящего (?!wp\-content|wp\-includes) и найдёт все ссылки с моей главной страницы за исключением тех, которые содержат wp-content или wp-includes. Обратите внимание, что вперёдсмотрящий стоит именно в месте, где предполагается наличие wp-content или wp-includes в ссылке.

Теперь пример с положительным вперёдсмотрящим (?=wp\-content|wp\-includes):
preg_match_all('/href\=(?:\'|\")?((?:\/|[^\s\'\"]*ptipti.ru\/)(?=wp\-content|wp\-includes)[^\s\'\"]*)/i', file_get_contents('http://ptipti.ru'), $matches);
Этот пример наоборот найдёт ссылки, которые будут иметь wp-content или wp-includes в ожидаемом месте ссылки.

(?<!) и (?<=) - Отрицательный и положительный назадсмотрящий
Эта конструкция схожа с предыдущей только с той разницей, что производит поиск перед найденной частью.
примеры:
preg_match_all('/^\w+(?<!John|Johny)\sBrown/mi', "Eve Brown\nKate Brown\nJessie Brown\nJohn Brown\nLeslie Brown\nJohny Brown", $matches);
Из семейки Браунов это выражение найдёт всех, кто не John или не Johny. Дополнительный модификатор i исключает возможность сим товарищам прорваться, если они напишут себя с маленькой буквы.

Кстати, очень важное замечание. Вперёд- и назадсмотрящие могут содержать только фиксированный текст, максимум альтернативы, поэтому мы и использовали John|Johny, так просящаяся конструкция Johny? вызовет ошибку и работать не будет.

3. Примеры использования

Теперь знания закрепим практикой и рассмотрим некоторые «боевые» примеры использования регулярных выражений:

1. Считаем ссылки:

preg_match ('/<a href="([^"]+)">([^<]+)<\/a>/', '<a href="http://ptipti.ru">Сайт Pti_the_Leader</a>', $matches);

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

Array (
[0] => <a href="http://ptipti.ru">Сайт Pti_the_Leader</a>
[1] => http://ptipti.ru
[2] => Сайт Pti_the_Leader
)

Как мы описали ссылку:

/<a href="([^"]+)">([^<]+)<\/a>/'

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

<a href="адрес">анкор</a>

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

<a href="(адрес)">(анкор)</a>

Уже есть начало. Как же нам описать адрес? Ведь в нём может быть http и косые, а может и не быть… В общем можно используя все полученные знания привлечь, чтобы составить огромный паттерн… но мы пойдём другим путём. Посмотрим на атрибут ссылки href и повнимательнее. Он заключён в кавычки, это всегда так… первую кавычку мы не трогаем, тоже понятно, и надо найти всё, что угодно до второй кавычки… то есть вторая кавычка – это стоп, говорящий, что всё значение найдено. Вот давайте и обозначим ссылку, что это всё, что угодно, кроме кавычки:

<a href="([^"]+)">(анкор)</a>

Вот мы создали класс [], в котором минимум один символ + и всё что угодно, кроме кавычки ^”

Как теперь описать анкор? \w+? А пробелы? Сделаем опять точно так же, анкор стоит между >< значит первый > мы от него и начинаем поиск и до <,

<a href="([^"]+)">([^<]+)</a>

В итоге и получаем очень красивое регулярное выражение.

2. Считывание данных.

Вернёмся к примеру из буферизации:

preg_match ('/\S+\s(\S+)\s\S+\s(\S+)/', , $matches);

рассмотрим наш паттерн '/\S+\s(\S+)\s\S+\s(\S+)/'

Не разделитель больше одного, разделитель, (не разделитель больше одного), то есть Vasya, разделитель, не разделитель больше одного, разделитель, (не разделитель больше одного), то есть Muhosransk. Этот паттерн не универсален и будет работать только максимум со строкой, в которой разделители будут ещё или знаки табуляции или знаки новой строки. А если к тому же разделителей будет несколько, и нужные нам данные будут стоять, к примеру, через двоеточие? Понятное дело, что этой паттерной мы уже данные не сможем считать. Давайте опять подумаем, нам нужно считать именно слова, значит будем использовать \w класс и его отрицание.

'/\w+\W+(\w+)\W+\w+\W+(\w+)/i'

Это более улучшенный вариант, который найдёт и Васю и его город проживания и в ”name Vasya from Muhosransk”, и в ”name : Vasya from -   Muhosransk”, и даже в ”name : $+Vasya+$ from -   --==Muhosransk==--”

4. Заключение

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

😃+

12 комментариев on “Составление регулярных выражений (regexp) с примерами”

1
A1R

очень помогла статья, хорошо написана! спасибо огромное!!!

21.08.2011 в 01:36
2
Dimz

Огромное спасибо! Нашел то что искал и даже больше=)

12.09.2011 в 20:02
3
CepeLLlka

Большое спасибо автору.. знает что надо нам нубам 🙂 Видимо сам недавно учился 🙂 Спасибо ещё раз..

06.11.2011 в 19:46
4
Scorpios

Большой респект!
Автор смог в одной статье разложить сложную тему в отличном понятном стиле!

14.04.2012 в 00:34
5
Carf

Ура, я нашел нормальное описание этой функции! Спасибо! =)

26.04.2012 в 10:00
6
Виктор

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

25.03.2013 в 20:18
7
Гузель

Отличная статья! Все оказывается не так страшно =) Спасибо за доступное изложение, выручили!

23.07.2013 в 13:01
8
Alex

Хорошая статья! Автору респект. Добавил в закладки. Но снег нада почистить ))) Потому что сильно мешает въезжать в смысл написанного. Сайт ведь информативный а не развлекательный. Может сможете сделать, чтобы снег падал где-то только в шапке или в фоне на полях вне сайта, там где не мешает читать. Заранее благодарен.

09.02.2014 в 13:07
9
ptipti

Снег через 2 недели сам умрёт 🙂 Точнее, растает

09.02.2014 в 18:29
10
Илья

Огромное спасибо!
Раньше вообще не понимал, как эти регулярные выражения составляют… А тут и читать научился и составлять могу. Сделал проверку на правильность введённого номера телефона, а также повторил пример из этой статьи.
Ещё раз спасибо!

16.03.2015 в 16:43
11
Александр

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

16.12.2016 в 11:19
12
blah

Сайт в ЧС из-за шлака в виде снега на этой странице.

09.01.2017 в 16:25

Leave a comment

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