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

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

Начнем с одной народной программистской мудрости:

Некоторые люди, сталкиваясь с проблемой, думают: «Ага, я умный, я решу её с помощью регулярных выражений». Теперь у них две проблемы.

Это довольно-таки объемный и сложный урок. Но, если ты дошел до сюда, то ты способен осилить и это. Просто почти теорию, не надо учить её наизусть, а когда дойдешь до задачек, вернись и проясни непонятные моменты. Есть также документация по регулярным выражениям в PHP, ссылка: https://secure.php.net/manual/ru/pcre.pattern.php

Также, важно помнить, что есть несколько диалектов регулярных выражений. Они во многом похожи, но могут отличаться отстутвием каких-то возможностей. В PHP используется диалект PCRE и выражения, написанные на другом диалекте, могут не заработать.

Примеры шаблонов

Начнем с простейшего регулярного выражения:

Выражение /кит/ui, где '/' - это разделитель, 'ui' - флаги, 'кит' - обычные символы

Выражение /кит/ui ищет последовательность букв «кит» в любом регистре. То есть оно найдет, например, подстроку «кит» в строке «Китай». Разберемся, как устроено это выражение.

Любое выражение начинается с символа-ограничителя (delimiter по англ.). Этот символ обозначает начало и конец шаблона, соответствие которому мы ищем в тексте. В качестве него обычно используют символ /, но можно использовать и другие символы, не имеющие специального назначения в регулярках, например, ~, # или @. Альтернативные разделители используют, если в выражении может встретиться сам символ /. Первый ограничитель обозначает начало шаблона, второй — конец. За ним может идти одна или несколько латинских букв-флагов. Они задают дополнительные опции при поиске текста. Вот значение использованных флагов:

Шаблон составляется из обычных символов и символов, имеющих специальное значение (их называют спецсимволы или метасимволы). Вот полный список таких символов. Ниже мы разберем, что значит каждый из них:

\ ^ $ . [ ] | ( ) ? * + { }

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

/к.т/ui

Это выражение ищет в тексте подстроку из 3 символов, где первый символ - это буква «к», второй символ - любой символ, кроме \n, и третий - это буква «т». Под этот шаблон подходит, например, слово «кот» и начало слова «каток». Точка соответствует ровно одному символу и, если бы мы написали выражение /к...т/ui, то оно бы обозначало, что между буквами «к» и «т» должно быть 3 любых символа.

Регулярки в программе

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

Для проверки, соответствует ли текст или его часть шаблону, в PHP есть функция preg_match($regexp, $text, $match). Она принимает на вход регулярку, текст и пустой массив. Она проверяет, есть ли в тексте подстрока, соответствующая данному шаблону и возвращает 0, если нет, или 1, если она есть. А в переданный массив $match в элемент с индексом 0 кладется первая найденная подстрока, совпавшая с регуляркой (это немного необычный способ вернуть результат, но так уж тут сделано). Напишем простую программу, применяющую регулярное выражение к разным строкам:

КодРезультат
$regexp = "/к.т/ui";

// строки, к которым мы будем по очереди применять регулярку
$lines = [
  'рыжий кот',
  'рыжий крот',
  'скиталец'
];

foreach ($lines as $line) {
    echo "Строка: $line\n";

    // сюда будет помещено первое
    // совпадение с шаблоном
    $match = [];
    if (preg_match($regexp, $line, $match) > 0) {
        echo "+ Найдено слово '{$match[0]}'\n";
    } else {
        echo "- Ничего не найдено\n";
    }
}
Строка: рыжий кот
+ Найдено слово 'кот'
Строка: рыжий крот
- Ничего не найдено
Строка: скиталец
+ Найдено слово 'кит'

Протестировать выражение можно так же на сайте https://regex101.com/, но он на английском языке.

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

Регулярные выражения в PHP, начало

Скобки в регулярных выражениях

Давай повторим, что обозначают разные виды скобок:

Примечание: в квадратных скобках можно указывать диапазоны символов, но помни, что русская буква ё идет отдельно от алфавита и чтобы написать «любая буква русского алфавита», надо писать [а-яё].

Бекслеши

Если ты смотрел другие учебники по регулярным выражениям, то наверно заметил, что бекслеш везде пишут по-разному. Где-то пишут один бекслеш: \d, а здесь в примерах он повторен 2 раза: \\d. Почему?

Язык регулярных выражений требует писать бекслеш один раз. Однако в строках в одиночных и двойных кавычках в PHP бекслеш тоже имеет особое значение: мануал про строки. Ну например, если написать $x = "\$"; то PHP воспримет это как специальную комбинацию и вставит в строку только символ $ (и движок регулярных выражений не узнает о бекслеше перед ним). Чтобы вставить в строку последовательность \$, мы должны удвоить бекслеш и записать код в виде $x = "\\$";.

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

В остальных случаях один или два бекслеша дадут один и тот же результат: "\\d" и "\d" вставят в строку пару символов \d - в первом случае 2 бекслеша это последовательность для вставки бекслеша, во втором случае специальной последовательности нет и символы вставятся как есть. Проверить, какие символы вставятся в строку, и что увидит движок регулярных выражений, можно с помощью echo: echo "\$";. Да, сложно, а что поделать?

Специальные конструкции в регулярках

Также, есть удобное условие для указания на границу слова: \b. Эта конструкция обозначает, что с одной стороны от нее должен стоять символ, являющийся буквой/цифрой/знаком подчеркивания (\w), а с другой стороны - не являющийся. Ну, например, мы хотим найти в тексте слово «кот». Если мы напишем регулярку /кот/ui, то она найдет последовательность этих букв в любом месте - например, внутри слова «скотина». Это явно не то, что мы хотели. Если же мы добавим условие границы слова в регулярку: /\bкот\b/ui, то теперь искаться будет только отдельно стоящее слово «кот».

Мануал

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

Задачка

Напиши программу, получающую на вход автомобильный номер, и проверяющую, правильно ли он введен. Автомобильный номер имеет вид «а123вг», то есть начинается с буквы, за которой идет 3 цифры, и еще 2 буквы. Никаких посторонних символов быть в нем не должно.

Эту программу надо решить с помощью preg_match() и регулярного выражения. Протестировать его ты можешь например на сайте Regex101.

Задачка на проверку телефонов

Дан текст, который по идее должен быть номером телефона в виде 8-(911)-506 56 56 (т.е. человек может ввести не только цифры, но и скобки, минусы, может что-то еще). Но в реальности, пользователь может вместо номера написать что угодно. Напиши скрипт для проверки правильности введенного номера («8(911)-506 56 56» — правильный номер, «8-911-50-656-56» — правильный, «89115065656» — правильный, «02» — неправильный, «89115065656 позвать Люду» — неправильный).

Задачу надо проверить на большом числе телефонов, чтобы убедиться что твой код правильный. Для этого давай добавим в программу тесты, чтобы сразу было видно, верно все работает или нет. Сделай 2 списка номеров (правильные и нет), добавь их в программу и напиши цикл, который их по очереди прогоняет через регулярку и проверяет, что они определяются как надо (если нет — надо вывести, какой именно номер не распознается правильно).

Вот список номеров:

// Правильные: 
$correctNumbers = [ 
  '84951234567',  '+74951234567', '8-495-1-234-567', 
  ' 8 (8122) 56-56-56', '8-911-1234567', '8 (911) 12 345 67', 
  '8-911 12 345 67', '8 (911) - 123 - 45 - 67', '+ 7 999 123 4567', 
  '8 ( 999 ) 1234567', '8 999 123 4567'
];

// Неправильные: 
$incorrectNumbers = [
  '02', '84951234567 позвать люсю', '849512345', '849512345678', 
  '8 (409) 123-123-123', '7900123467', '5005005001', '8888-8888-88',
  '84951a234567', '8495123456a', 
  '+1 234 5678901', /* неверный код страны */
  '+8 234 5678901', /* либо 8 либо +7 */
  '7 234 5678901' /* нет + */
];

Также, на regex101 https://regex101.com/r/qF7vT8/3 уже введены номера и можно простестировать свою регулярку. Помни что на этом сайте надо писать бекслеш один раз, например \s, а не \\s. Флаг m там стоит чтобы ^ и $ в регулярке обозначали «начало и конец любой строки», а не «начало и конец всего текста». Флаг g (его нет в PHP, он только на этом сайте) значит что надо искать все совпадения с регуляркой, а не только первое.

Подсказка: не надо строить сложных выражений и предусматривать все возможные комбинации символов. Достаточно написать: сначала идет +7 или 8, за ними ровно 10 цифр, между которыми может быть любое число скобок, минусов, пробелов

Повторим

Задачки (пока без картинок)

Опечаточники

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

Например, государственные органы, которые хотят провести закупки, обязаны организовать публичные торги и разместить объявление о них на сайте госзакупок. Чтобы помешать всем желающим участвовать в тендере (и чтобы отдать заказ «своим людям» и получить потом от них в свой карман часть денег), они заменяют в описании заказа некоторые буквы кирилицы на похожие на них латинские. Таким образом, не предупрежденные заранее организации не смогут найти объявление через поиск и принять участие в конкурсе.

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

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

Для проверки работоспособности, попробуй применить программу к тексту из поля «Наименование заказа» на странице (осторожно, спойлер!) http://zakupki.gov.ru/pgz/public/action/orders/info/common_info/show?notificationId=5193640 или http://zakupki.gov.ru/pgz/public/action/orders/info/common_info/show?notificationId=5138013 ололо кто бы поверил!

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

Опечаточники

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

P.S. На сайте программистских комиксов xkcd есть комикс про регулярные выражения: перевод, оригинал (англ.).

дальше: Повторим? →

-----

Куда вводить код? Что надо скачать? Читай первый урок.

Есть вопросы? Задай гуглу или автору.

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

Как связаться с автором? Я хочу переодеть его в платье школьницы и жениться на нем. Ящик codedokode (кот) gmail.com ждет ваших писем. А вконтактик и фейсбучек ждут ваших лайков. Но ответ на банальные вопросы лучше искать в Гугле или на stackoverflow.

Я решил задачку!!! Молодец, делай следующий урок

Ideone не работает!11 Ну так открой Гугл и найди сайты вроде https://repl.it/languages/php , http://phptester.net/ , http://sandbox.onlinephpfunctions.com/ , http://codepad.org/ или http://www.runphponline.com/ . Не ленись.

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

На сайте установлена система Google Analytics (и еще несколько аналогичных систем от юкоза). Данные о твоем IP-адресе, посещаемых страницах, времени посещения отправляются в Google Corporation, США. Хочу знать, кто и зачем сюда заходит. Поверь, другие сайты делают точно так же. Все сайты пишут логи.