Галопом по основам regex

Скобочные группы

Для нашего шаблона «смеющегося» междометия осталась самая малость — учесть, что буква «х» может встречаться более одного раза, например, «Хахахахааахахооо», а может и вовсе заканчиваться на букве «х». Вероятно, здесь нужно применить квантификатор для группы , но если мы просто напишем , то квантификатор будет относиться только к символу «х», а не ко всему выражению. Чтобы это исправить, выражение нужно взять в круглые скобки: .

Таким образом, наше выражение превращается в — сначала идёт заглавная или строчная «х», а потом произвольное ненулевое количество гласных, которые (возможно, но не обязательно) перемежаются одиночными строчными «х». Однако это выражение решает проблему лишь частично — под это выражение попадут и такие строки, как, например, «хихахех» — кто-то может быть так и смеётся, но допущение весьма сомнительное. Очевидно, мы можем использовать набор из всех гласных лишь единожды, а потом должны как-то опираться на результат первого поиска. Но как?…

Запоминание результата поиска по группе

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

Результат поиска по всему регулярному выражению: «<p>», «<b>», «</b>», «<i>», «</i>», «</p>».
Результат поиска по первой группе: «p», «b», «/b», «i», «/i», «/i», «/p».

На результат поиска по группе можно ссылаться с помощью выражения , где n — цифра от 1 до 9. Например выражению соответствуют строки «aaaa», «abab», но не соответствует «aabb».

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

С использованием этого механизма мы можем переписать наше выражение к виду .

Перечисление

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

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

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

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

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

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

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

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

Например, вы можете искать большие денежные суммы, используя метасимвол ‘\ d’: / ( +) 000 / , Здесь \ d будет искать любую строку числового символа.

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

Символ Описание
, один символ
\ s символ пробела (пробел, табуляция, новая строка)
\ S не-пробельный символ
\ d цифра(0-9)
\ D — не цифра
\ w символ слова (az, AZ, 0-9, _)
\ W — символ без слова
соответствует одному символу в заданном наборе
соответствует одному символу за пределами заданного набора
( foo | bar | baz ) соответствует любой из указанных альтернатив

Задания для закрепления

Найдите время

Время имеет формат часы:минуты. И часы, и минуты состоят из двух цифр, пример: 09:00. Напишите регулярное выражение для поиска времени в строке: «Завтрак в 09:00». Учтите, что «37:98» – некорректное время.

Java

Найдет ли регулярка что-нибудь в строке Java? А в строке JavaScript?

Ответы: нет, да.

  • В строке Java он ничего не найдёт, так как исключающие квадратные скобки в Java означают «один символ, кроме указанных». А после «Java» – конец строки, символов больше нет.
  • Да, найдёт. Поскольку регэксп регистрозависим, то под вполне подходит символ «S».

Цвет

Напишите регулярное выражение для поиска HTML-цвета, заданного как #ABCDEF, то есть # и содержит затем 6 шестнадцатеричных символов.

Итак, нужно написать выражение для описания цвета, который начинается с «#», за которым следуют 6 шестнадцатеричных символов. Шестнадцатеричный символ можно описать с помощью . Для его шестикратного повторения мы будем использовать квантификатор {6}.

Разобрать арифметическое выражение

Арифметическое выражение состоит из двух чисел и операции между ними, например:

  • 1 + 2
  • 1.2 *3.4
  • -3/ -6
  • -2-2

Список операций: «+», «-», «*» и «/».

Также могут присутствовать пробелы вокруг оператора и чисел.

Напишите регулярное выражение, которое найдёт как всё арифметическое действие, так и (через группы) два операнда.

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

Оператор – это . Заметим, что дефис мы экранируем. Нам нужно число, затем оператор, затем число, и необязательные пробелы между ними. Чтобы получить результат в требуемом формате, добавим к группам, поиск по которым нам не интересен (отдельно дробные части), а операнды наоборот заключим в скобки. В итоге:

Кроссворды из регулярных выражений

Такие кроссворды вы можете найти у нас.

Удачи и помните — не всегда задачу стоит решать именно с помощью регулярных выражений («У программиста была проблема, которую он начал решать регэкспами. Теперь у него две проблемы»). Иногда лучше, например, написать развёрнутый автомат конечных состояний.

Задачи и их разборы с javascript.ru; в статье использованы комиксы xkcd.

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

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

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

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

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

Для проверки работоспособности, попробуй применить программу к тексту из поля «Наименование заказа» на
странице (осторожно, спойлер!)

или

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

Подсказки для глупеньких:

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

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

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

Модификаторы указываются либо в скобках, например так: (?Ui), либо после закрывающего символа ‘/pattern/Ui’.

Модификатор Описание
i Выполняет поиск без учета регистра. Например «/a/i» ищет и a, и A.
m Выполняет многострочный поиск (шаблоны, которые ищут начало или конец строки, будут соответствовать началу или концу каждой строки)
u Обеспечивает правильное сопоставление шаблонов в кодировке UTF-8 (для поиска русского текста например).
U Инвертирует «жадность» (по умолчанию жадный, т.е. пытается захватить как можно большую строку, подходящую по условию).
s Если используется, то символ точка (.) соответствует и переводу строки. Иначе она ему не соответствует.
x Игнорировать пробелы. В этом случае пробелы нужно экранировать обратным слэшем \.

При использовании модификаторов, можно использовать знак минус для отключения модификатора. Например: (?m-i) — включаем многострочный поиск и отключаем регистронезависимый.

Бекслеши

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

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

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

  • Чтобы написать в регулярке , мы пишем в коде
  • Чтобы написать в регулярке , мы удваиваем каждый
    бекслеш и пишем
  • Чтобы написать в регулярке бекслеш и цифру (),
    бекслеш надо удвоить:

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

Функции регулярных выражений

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

Функция Определение
preg_match() Эта функция ищет конкретный образец в некоторой строке. Он возвращает 1 (true), если шаблон существует, и 0 (false) в противном случае.
preg_match_all() Эта функция ищет все вхождения шаблона в строке. Она возвращает количество найденных совпадений с шаблоном в строке, или 0 — если вхождений нет. Функция удобна для поиска и замены.
ereg_replace() Эта функция ищет определенный шаблон строки и возвращает новую строку, в которой совпадающие шаблоны были заменены другой строкой.
eregi_replace() Функция ведет себя как ereg_replace() при условии, что поиск шаблона не чувствителен к регистру.
preg_replace() Эта функция ведет себя как функция ereg_replace() при условии, что регулярные выражения могут использоваться как в шаблоне так и в строках замены.
preg_split() Функция ведет себя как функция PHP split(). Он разбивает строку на регулярные выражения в качестве параметров.
preg_grep() Эта функция ищет все элементы, которые соответствуют шаблону регулярного выражения, и возвращает выходной массив.
preg_quote() Эта функция принимает строку и кавычки перед каждым символом, который соответствует регулярному выражению.
ereg() Эта функция ищет строку, заданную шаблоном, и возвращает TRUE, если она найдена, иначе возвращает FALSE.
eregi() Эта функция ведет себя как функция ereg() при условии, что поиск не чувствителен к регистру.

Примечание:

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

Функция preg_match()

Функция выполняет проверку на соответствие регулярному выражению.

Пример. Поиск подстроки «php» в строке без учета регистра:

Попробуй сам

Результат выполнения кода:

Вхождение найдено.

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

Примечание: Не используйте функцию preg_match(), если необходимо проверить наличие подстроки в заданной строке. Для этого используйте strpos() или strstr(), т.к. они выполнят эту задачу гораздо быстрее.

Функция preg_match_all()

Функция выполняет глобальный поиск шаблона в строке.

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

Попробуй сам

Результат выполнения кода:

3

Функция preg_replace()

Функция выполняет поиск и замену по регулярному выражению.

В следующем функция выполняет поиск в строке совпадений с шаблоном pattern и заменяет их на replacement:

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

Глобальный поиск

А не обратили ли вы внимание на букву g после закрывающего слеша в паттерне?

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

Видите? Теперь только одно совпадение осталось.

В PHP при этом для поиска как бы с модификатором g используется функция preg_match_all(), а без этого модификатора — preg_match().

Жадность

Есть ещё один довольно популярный модификатор, который позволяет сделать поиск либо жадным, либо нежадным. Жадный поиск захватывает максимально возможную подстроку. Давайте рассмотрим вот такой пример:

Паттерн «к.+к» — буква к, затем любые символы в количестве от одного до бесконечности, и снова буква к.

Видите, какую строку захватило? А если бы нам хотелось остановиться на первой букве «к»? Тогда нам просто нужно было бы сделать поиск нежадным. Для этого используется модификатор «U»

В PHP он при этом указывается после закрывающего слеша паттерна:

Вообще, все остальные модификаторы как и «U» указываются после слеша, это только для модификатора g пришлось сделать две разные функции.

Итак, давайте сформулируем основные тезисы по жадности и нежадности:

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

PHP regex anchors

Anchors match positions of characters inside a given text.

In the next example, we look if a string is located at
the beginning of a sentence.

anchors.php

<?php

$sentence1 = "Everywhere I look I see Jane";
$sentence2 = "Jane is the best thing that happened to me";

if (preg_match("/^Jane/", $sentence1)) {
    echo "Jane is at the beginning of the \$sentence1\n";
} else {
    echo "Jane is not at the beginning of the \$sentence1\n";
}

if (preg_match("/^Jane/", $sentence2)) {
    echo "Jane is at the beginning of the \$sentence2\n";
} else {
    echo "Jane is not at the beginning of the \$sentence2\n";
}

We have two sentences. The pattern is . The pattern
checks if the ‘Jane’ string located at the beginning of the text.

$ php anchors.php 
Jane is not at the beginning of the $sentence1
Jane is at the beginning of the $sentence2
php> echo preg_match("#Jane$#", "I love Jane");
1
php> echo preg_match("#Jane$#", "Jane does not love me");
0

The pattern matches a string in which the word
Jane is at the end.

str.replace(str|regexp, str|func)

Это универсальный метод поиска-и-замены, один из самых полезных. Этакий швейцарский армейский нож для поиска и замены в строке.

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

Хотя есть подводный камень.

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

Вы можете видеть это в приведённом выше примере: только первый заменяется на .

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

Второй аргумент – строка замены. Мы можем использовать специальные символы в нем:

Спецсимволы Действие в строке замены
вставляет
вставляет всё найденное совпадение
вставляет часть строки до совпадения
вставляет часть строки после совпадения
если это 1-2 значное число, то вставляет содержимое n-й скобки
вставляет содержимое скобки с указанным именем

Например:

Для ситуаций, которые требуют «умных» замен, вторым аргументом может быть функция.

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

Функция вызывается с аргументами :

  1. – найденное совпадение,
  2. – содержимое скобок (см. главу Скобочные группы).
  3. – позиция, на которой найдено совпадение,
  4. – исходная строка,
  5. – объект с содержимым именованных скобок (см. главу Скобочные группы).

Если скобок в регулярном выражении нет, то будет только 3 аргумента: .

Например, переведём выбранные совпадения в верхний регистр:

Заменим каждое совпадение на его позицию в строке:

В примере ниже две скобки, поэтому функция замены вызывается с 5-ю аргументами: первый – всё совпадение, затем два аргумента содержимое скобок, затем (в примере не используются) индекс совпадения и исходная строка:

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

Или, если мы используем именованные группы, то объект с ними всегда идёт последним, так что можно получить его так:

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

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

  • На вход скрипта дан введенный пользователем номер телефона в
    виде 8-911-404-44-11 или +7(812)6786767 (в начале 8 или +7, потом идут 10 цифр и, возможно, какие-то символы).
    То есть, как и в прошлой задаче, человек вводит номер как хочет.
    Надо проверить номер на правильность и привести любой номер к единому формату 89114044411
    (то есть, заменить +7 на 8 и выкинуть весь мусор вроде пробелов, скобок и минусов, кроме цифр)
  • Автозамена. Напиши скрипт, заменяющий определенное слово на другое (например, слово
    «дурак» на «хороший человек» в фразе «ты дурак»). Скрипт должен не пропускать слово,
    если оно написано буквами в разном регистре (ДуРАк), с заменой русских букв
    на похожие английские (а -> a), или через пробелы («ты — д у р а к»)
  • Дан текст, содержащий в себе email’ы (адреса почты вроде you+me@some.domain-domain.com ). Напиши
    скрипт, выводящий все email, встречающиеся в этом тексте
  • «Grammar Nazi». Напиши скрипт, проверяющий текст на наличие злостных ошибок:
    • нет пробела после запятой, точки с запятой, восклицательного знака,
      вопросительного знака, двоеточия
    • «жи» или «ши» написано с буквой ы
    • в тексте есть слово «координально» или «сдесь», «зделал», «зделаю», «зделан»
    • в тексте есть слова «а» или «но» без запятой перед ними.
    • (можешь добавить еще несколько правил, если хорошо знаешь русский язык)

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

  • Если ты сделал задачу про Grammar Nazi, сделай скрипт, которы вместо сообщения об ошибках будет
    молча их исправлять.

Специальные символы квантификаторов

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

  • ? ({0,1}) — символ повторяется 0 или 1 раз

  • * ({0,}) — символ повторяется от 0 раз и более

  • + ({1,}) — символ повторяется от 1 и более раз

Давайте разбираться. Начнем со знака вопроса. Допустим у нас есть строка colour color и мы хотим найти либо colour, либо color. Мы можем написать так: colou?r.

Что произошло? Мы указали, что идет последовательность символов colo, потом написали u? (тоже самое, что и u{0,1}). Это значит, что символ u повторяется 0 или 1 раз (то есть либо его нет вовсе (он не повторяется, то есть повторяется 0 раз), либо он есть, но только один (повторяется один раз)). Ну а потом указали, что после должен идти символ r. Поэтому colour соответствует, так как буква u повторяется 1 раз, а color — так как u вообще отсутствует (повторяется 0 раз). Видите, все просто 🙂

Давайте изменим строку и напишем что-то по типу colouuuuur color. И допустим мы хотим указать, что u должен либо не быть, либо быть сколько угодно раз. Для этого мы можем написать colou*r.

То есть либо u у нас нет, либо повторяется много раз.

Символ + работает почти также, за исключением того, что символ должен повторяться минимум 1 раз. То есть в данном случае слово color не будет соответствовать, так как там u не присутствует (то есть повторяется 0 раз, а у нас символ должен повторяться минимум 1 раз)

PHP regex quantifiers

A quantifier after a token or a group specifies how often that
preceding element is allowed to occur.

 ?     - 0 or 1 match
 *     - 0 or more
 +     - 1 or more
 {n}   - exactly n
 {n,}  - n or more
 {,n}  - n or less (??)
 {n,m} - range n to m

The above is a list of common quantifiers.

The question mark indicates there is zero or one of
the preceding element.

zeroorone.php

<?php

$words = ;
$pattern = "/colou?r/";

foreach ($words as $word) {
    if (preg_match($pattern, $word)) {
        echo "$word matches the pattern\n";
    } else {
        echo "$word does not match the pattern\n";
    }
}

We have four nine in the array.

$pattern = "/colou?r/";

Color is used in American English, colour in British English.
This pattern matches both cases.

$ php zeroorone.php 
color matches the pattern
colour matches the pattern
comic does not match the pattern
colourful matches the pattern
colored matches the pattern
cosmos does not match the pattern
coloseum does not match the pattern
coloured matches the pattern
colourful matches the pattern

This is the output of the script.

The metacharacter matches the preceding element
zero or more times.

zeroormore.php

<?php

$words = ;

$pattern = "/.*even/";

foreach ($words as $word) {
    
    if (preg_match($pattern, $word)) {
        echo "$word matches the pattern\n";
    } else {
        echo "$word does not match the pattern\n";
    }
}

In the above script, we have added the metacharacter.
The combination means, zero, one or more single characters.

$ php zeroormore.php 
Seven matches the pattern
even matches the pattern
Maven does not match the pattern
Amen does not match the pattern
Leven matches the pattern

Now the pattern matches three words: Seven, even and Leven.

php> print_r(preg_grep("#o{2}#", ));
Array
(
     => gool
     => root
     => foot
)

The pattern matches strings that contain exactly
two ‘o’ characters.

php> print_r(preg_grep("#^\d{2,4}$#", ));
Array
(
     => 12
     => 123
     => 1234
)

We have this pattern. The is a character
set; it stands for digits. The pattern matches numbers that have 2, 3, or 4 digits.

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

Якорь — это такой специальный символ, который обозначает позицию чего-либо. К примеру, нам нужно найти конец строки и проверить, что в конце стоит точка. Для этого есть якорь «$». Использование выглядит следующим образом:

Паттерн «.$» — точка в конце строки. Всё просто.

Также нам часто приходится говорить о начале строки. Для этого есть якорь «^».

Несмотря на то, что в строке есть два слова «кукушки», под шаблон попало только первое, так как оно находится в начале строки.

Давайте вернёмся к примеру с телефонами. В прошлый раз мы использовали шаблон «/+7{10}/». Однако, если строка содержит больше цифр в конце, то она просто отбросит лишнее.

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

А корректный попадает:

Многострочный режим

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

Это происходит потому, что всё это поле считается одной строкой. Чтобы для каждой строки были свои якоря ^ и $ нужно включить многострочный режим с помощью модификатора «m».

PHP regex dot metacharacter

The (dot) metacharacter stands for any single character in the text.

single.php

<?php

$words = ;
$pattern = "/.even/";

foreach ($words as $word) {

    if (preg_match($pattern, $word)) {
        echo "$word matches the pattern\n";
    } else {
        echo "$word does not match the pattern\n";
    }
}

In the array, we have five words.

$pattern = "/.even/";

Here we define the search pattern. The pattern is a string. The regular expression
is placed within delimiters. The delimiters are mandatory.
In our case, we use forward slashes as delimiters. Note that we
can use different delimiters if we want. The dot character stands for any single character.

if (preg_match($pattern, $word)) {
    echo "$word matches the pattern\n";
} else {
    echo "$word does not match the pattern\n";
}

We test all five words if they match with the pattern.

$ php single.php 
Seven matches the pattern
even does not match the pattern
Maven does not match the pattern
Amen does not match the pattern
Leven matches the pattern

The Seven and Leven words match our search pattern.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Adblock
detector