Пост

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

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

Вместо введения

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

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

Все примеры можно проверять на сайте https://regex101.com/

Базовый вариант

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

По умолчанию мы ищем только одно вхождение при этом регистр букв важен.

Например:

  • регулярное выражение: a
  • строка поиска и результат: аааааааа

или

  • регулярное выражение: Алексей
  • строка поиска и результат: Привет, мое имя Алексей. Привет, мое имя Алексей

Поиск части слова

Или же поиск по части слова:

  • регулярное выражение: ве
  • строка поиска и результат: Привет

Разделитель

Само регулярное выражение состоит из строки-шаблона. В роли ограничителей например могут выступать символы:

1
2
3
4
5
6
/
~
@
;
%
#

Пример регулярного выражения с разделителем:

  • регулярное выражение: /строка/
  • строка поиска и результат: Тестовая строка

Пример с разделителем и модификатором

Теперь представим есть строка телефоны и на телефоне и не тот телефон, нужно найти все вхождения слова телефон:

  • регулярное выражение: /телефон/
  • строка поиска и результат: телефоны и на телефоне и не тот телефон

Но мы найдем только первое вхождение.

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

Модификаторы указываются после разделителя в конце:

  • регулярное выражение: /телефон/g
  • строка поиска и результат: телефоны и на телефоне и не тот телефон

Модификаторы или флаги модифицируют поведение регулярного выражения

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

Символ точка

. - это специальный символ, совпадение с любым одиночным символом кроме пробельного.

  • регулярное выражение: /ca./g
  • строка поиска и результат: cat car

То есть на месте . может быть любой символ.

Еще пример:

  • регулярное выражение: /file./g
  • строка поиска и результат: file1.xls file2.xls file3.xls na1.xls na2.xls

Тут мы ищем имена файлов file и в конце ставим точку как любой символ, в итоге нашли 3 файла

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

Точку можно применять в любом месте несколько раз.

Вот такой пример без глобального флага g:

  • регулярное выражение: /.../
  • строка поиска и результат: собака

То есть любые три символа как раз попали.

Если нужно найти саму точку в строке, символ . нужно экранировать.

  • регулярное выражение: /file.\.xls/g
  • строка поиска и результат: file1.xls file2.xls file3.xls na1.xls na2.xls

Вот теперь попала точка + мы добавили расширение .xls

Символ \ всегда обозначает начало экранирования символов, например сам \ экранируем так \\

Про экранирование

Чуть выше я писал, что специальные символы если они совпадают с обычными нужно экранировать.

Метасимвол - это символ, который имеет специальное назначение.

Например, это символы .\[]{}*

  • регулярное выражение: /\[1\]/g
  • строка поиска и результат: $test = [1]

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

Ну а теперь к символьным классам.

Символьные классы

Символьный класс - это комбинация символов определенного набора в квадратных скобках.

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

  • регулярное выражение: /телефон[еы]/g
  • строка поиска и результат: телефоны и на телефоне и не тот телефон

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

Квантификатор - это ограничитель, который контролирует количество возможных повторений символов или класса символов

О квантификаторах поговорим позже.

Для примера выше добавим его после символьного класса добавим квантификатор ?.

  • регулярное выражение: /телефон[еы]?/g
  • строка поиска и результат: телефоны и на телефоне и не тот телефон

Теперь нашли все возможные варианты.

Пример с поиском всех слов в строке

Еще пример, имеем последовательность рай сад май вай Кай Сад. Задача найти все слова. Просто перечислим все нужные нам буквы.

  • регулярное выражение: /[КСвмсрайд]/g
  • строка поиска и результат: рай сад май вай Кай Сад

Можно конечно указать нужные буквы так /[абвгдеёжзийклмнопрст]/, но так никто не делает, есть способ проще.

Диапазон символов

Укажем диапазоны символов:

  • [а-я]
  • [А-Я]
  • [a-z]
  • [A-Z]
  • [0-9] - что эквивалентно [0123456789]

Либо сразу все символы:

  • [a-zA-Zа-яА-Я0-9]

Избегайте ошибок указания диапазона значений меньше начала, например [9-1]

Символьные классы могут быть в любом месте выражения.

Пример с поиском всех слов

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

  • регулярное выражение: /[а-до-ры]/g
  • строка поиска и результат: договор договоры договора

Пример с поиском определенного символа

Но чаще мы ищем не все слова, а только определенные слова и символы в тексте.

Например, нужно найти в тексте символ , и предшествующие ему определенные буквы:

  • регулярное выражение: /[авх],/g
  • строка поиска и результат: это тема, которая привлекает внимание многих исследователей и специалистов в различных областях. Этот загадочный объект представляет интерес для астрономов, физиков, биологов и других ученых, но также сопряжен с рядом подводных камней и опасностей. Давайте ближе рассмотрим некоторые из них

Внутри могут быть литералы, которые меняют поведение символьного класса.

Отрицание

Иногда требуется найти символы кроме указанных в символьном классе. Делается это с помощью символа ^.

Например:

1
2
[^а-и] - любой символ кроме указанного диапазона
[^А-Яа-яa-zA-Y0-9] - символы кроме указанных диапазонов

Посмотрим на пример с отрицанием:

  • регулярное выражение: /[^А-Яа-яa-zA-Y0-9]/g
  • строка поиска и результат: sdfgdfgZ-!.78абвг,?

Мы нашли все кроме всех указанных диапазонов. Что не попало в отрицание поиска - это символы Z-!.,?

Еще пример:

  • регулярное выражение: /file[13].xls/g
  • строка поиска и результат: file1.xls file2.xls file3.xls na1.xls na2.xls

Нашли, только file1 и file3.

Символ ^ обозначает отрицание всех символов в наборе, а не только первого символа идущего за ним.

Поиск символа - (дефис)

Символ дефиса используется как указатель диапазона искомых символов [0-9]. Но как его найти в тексте?

Для этого его нужно указать в начале или в конце символьного класса, при этом его совсем не обязательно экранировать, например:

  • регулярное выражение: /[-0-9]/g
  • строка поиска и результат: -123454

Или в конце класса, результат от этого не поменяется:

  • регулярное выражение: /[0-9-]/g
  • строка поиска и результат: -123454

Поиск других символов

Другие символы - это не буквы и не цифры можно найти тем же образом, перечислив их.

  • регулярное выражение: /[,.!~><()=+]/g
  • строка поиска и результат: ,.!~><()=+

Предопределенные символьные классы

Помимо формы в квадратных скобках существует также краткая форма их записи:

1
2
3
4
5
6
\d - целое число [0-9]
\D - символ кроме целого числа [^0-9]
\s - Пробельный символ [\f\n\r\t\v ]
\S - Символ кроме пробельного [^\f\n\r\t\v ]
\w - Буква,цифра и нижнее подчеркивание [a-zA-Z0-9_]
\W - Любой символ кроме буквы,цифры и нижнего подчеркивания [^a-zA-Z0-9_]

Рассмотрим примеры.

Целое число

Найдем все числа и дефис в тексте:

  • регулярное выражение: /\d\d\d-/g
  • строка поиска и результат: номер 123-

Как видим выглядит уже лучше, чем мы бы писали так /[0-9][0-9][0-9]-/g. Результат тот же.

Все кроме чисел

Если нужно найти все кроме чисел пишем \D

  • регулярное выражение: /\D/g
  • строка поиска и результат: текст 78 вместе 9 с числами 123!
Пробельный символ

Далее нужно понять что такое пробельный символ

В пробельные символы входят

  • ` ` пробел
  • \r возврат каретки (CR)
  • \n перевод строки (LF)
  • \t табуляция
  • \v вертикальная табуляция
  • \f конец страницы

Для разных операционных систем окончания строк разные, например для windows это \r\n а для linux и macos это \n

  • регулярное выражение: /\s/g
  • строка поиска и результат: много пробеловеще и с переносом строки
Кроме пробельных символов

Обратная ситуация когда нам наоборот не нужны пробелы

  • регулярное выражение: /\S/g
  • строка поиска и результат: текст с пробелами 123?
Латинские буквы и цифры

Существует так же команда под которую подходят буквы, цифры и нижнее подчеркивание.

  • регулярное выражение: /\w/g
  • строка поиска и результат: Stay go home123 Русский текст
Латинские не буквы и цифры

И обратная ситуация, когда попадет все кроме букв и цифр

Пример тот же, что и выше

  • регулярное выражение: /\W/g
  • строка поиска и результат: Stay go home123 Русский текст

Кратко про символьные классы

  • Символьные классы [] позволяют перечислить символы, которые могут быть в данной позиции текста
  • Символ отрицания ^ внутри символьного класса [^0-9] и снаружи ^[0-9] имеют совершенно разные значения.
  • Краткие значения например \d, эквивалентны [0-9] и [0123456789]

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

Квантификатор - это специальный ограничитель, который указывает на возможное кол-во повторений символа или символов перед ним.

?

? - означает, что символ или символы перед ? могут быть ноль или один раз.

Простой пример:

  • регулярное выражение: /кто?/g
  • строка поиска и результат: ктоо
  • строка поиска и результат: кто
  • строка поиска и результат: кт

Здесь подразумевается, что буква o может быть либо 0 или 1 раз, но не больше.

Еще пример:

  • регулярное выражение: /текст[0-9]?/g
  • строка поиска и результат: текст
  • строка поиска и результат: текст8
  • строка поиска и результат: текст8678

Здесь уже у нас символьный класс [0-9] и мы проверяем его наличие или отсутствие.

? У нас значит ноль или один раз.

Еще пример, протокол http

  • регулярное выражение: /https?/g
  • строка поиска и результат: http https

Буква s может быть, а может и не быть.

+

+ - последовательность символов (или набора) должна повторятся один или более раз

К примеру

  • регулярное выражение: /текст[0-9]+/g
  • строка поиска и результат: текст
  • строка поиска и результат: текст7777777
  • строка поиска и результат: текст7777777,…7

Ключевое отличие + от ? что обязательно должен быть один раз или более.

Знак + как и другие в этом разделе является мета символом. Если нужно найти совпадение непосредственно со знаком + нужно его экранировать \+

Распространенный пример [\w.]+ дает возможность совпасть нескольким символам, а именно [a-zA-Z0-9.].

Обратите внимание, что регулярное выражение /\w.+/, дает получить любые символы от 1 и более раз.

*

* - символ может встречаться ноль или более раз.

Самый сильный квантификатор.

  • регулярное выражение: /текст[0-9]*/g
  • строка поиска и результат: текст
  • строка поиска и результат: текст77777

Мета символ * можно рассматривать, как нечто необязательное

Диапазон повторений

Мета символы +* обеспечивают совпадение с неограниченным количеством символов. Они не позволяют задать минимальное количество совпадений.

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

Сам интервал заключается в {}, эти символы сами являются мета символами и должны быть экранированы, чтобы использоваться как обычные символы

{0}

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

Примеры:

  • {1}
  • {4}
  • {16}

  • регулярное выражение: /[0-9]{5}/g
  • строка поиска и результат: 123456789

Символы будут повториться ровно 5 раз.

  • регулярное выражение: /h{13}/g
  • строка поиска и результат: hhhhhhhhhhhhhhhhhhhh

Как видим, мы ожидаем 13 символов.

{0,}

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

  • регулярное выражение: /[0-9]{5,}/g
  • строка поиска и результат: 1234
  • строка поиска и результат: 12345678910

В данных примерах символов должно быть или 5 или больше 5, но не меньше 5

{0,1}

Символы повторятся от и до раз. От минимального до максимального

  • регулярное выражение: /[0-9]{3,8}/g
  • строка поиска и результат: 12 123 12345678 123456789

Строго от 3 до 8 символов.

Квантификатор {0,1} на самом деле эквивалент ? Квантификатор {1,} на самом деле эквивалент + а Квантификатор {0,} на самом деле эквивалент *

Примеры

Первый

  • регулярное выражение: /\d\d\d\d\d\d\d\d\d/
  • строка поиска и результат: 123456789

Согласитесь решение не красивое, сократим его указав +

  • регулярное выражение: /\d+/
  • строка поиска и результат: 123456789

Вот теперь гораздо лучше

Второй

  • регулярное выражение: /\d+-\d+-\d+/g
  • строка поиска и результат: 123456789 123-1234-123

Вот теперь первый номер не попал в наше поле зрения. Поправим поставим ? после дефиса.

  • регулярное выражение: /\d+-?\d+-?\d+/g
  • строка поиска и результат: 123456789 123-1234-123

Третий

  • регулярное выражение: /\d+[- (]?\d+[- )]?\d+/g
  • строка поиска и результат: 123456789 123-1234-123 8(123)123123 8 345 123456

Здесь мы заменили дефисы на символьные классы

Четвертый

  • регулярное выражение: /\+?\d+[- (]?\d+[- )]?\d+/g
  • строка поиска и результат: 123456789 123-1234-123 8(123)123123 8 345 123456 +6 889 90000

Нашли еще телефон с + добавив его присутствие или отсутствие с помощью экранирования.

Пятый

У нас есть некий html

1
<p>Текст в параграфе1</p>     <p>Текст в параграфе2</p>

Нужно найти полностью тег p

  • регулярное выражение: /<[p]>.*?\/[p]>/g
  • строка поиска и результат: <p>Текст в параграфе1</p> <p>Текст в параграфе2</p>

Граница слова

Мета символ \b

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

К примеру:

  • регулярное выражение: /\bтест\b/gu
  • строка поиска и результат: тестовая строка тест еще тест, но много тестов

Заметим что слова тестовая и тестов мы не нашли, так как это не отдельные слова

Модификатор /u значит, что поиск будет идти по всей таблице unicode

Мета символ \b, просто обеспечивает совпадение с местоположением в промежутке между символами. Следовательно, должен быть использован, как до искомого слова, так и после него.

  • регулярное выражение: /\bтест/gu
  • строка поиска и результат: тестовая строка тест еще тест, но много тестов

В примере у нас совпали все слова начинающиеся на тест.

  • регулярное выражение: /тест\b/gu
  • строка поиска и результат: тестовая строка тест еще тест, но много тестов контест

А вот здесь обратная ситуация мы получили и сами слова тест и слова оканчивающееся на тест.

Если требуется точное совпадение со словом указываем /bслово/b

Мета символ \b обеспечивает совпадение не с символом, а с позицией

Исключение совпадений \B

Если нужно наоборот исключить совпадение на границе слова, используется мета символ \B

  • регулярное выражение: /\B-\B/gu
  • строка поиска и результат: - +-слово слово-

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

Граница символьных строк

Границы символьных строк нужны для сопоставления с шаблонами в начале или в конце целой строки.

Начало строки ^

Мета символ ^ имеет несколько значений. В символьных классах [] - это отрицание, если он указан за пределами набора символов - то это совпадение с началом символьной строки.

  • регулярное выражение: /^ма/g
  • строка поиска и результат: май или март

Как видно, совпало только слово с начала строки.

Конец строки $

Аналогичная ситуация и для конца строки.

  • регулярное выражение: /ма$/g
  • строка поиска и результат: май или гамма мама

Бесполезный шаблон.

Шаблон /^.*$/g является бесполезным, так как находит все подряд.

Еще задача найти все комментарии в коде.

  • регулярное выражение: /(?m)^\s*\/\/.*$/g
  • строка поиска и результат: // Комментарий А это не комментарий // Еще коммент

Модификатор m в начале говорит и том, что мета символы ^ и $ будут работать для каждой строки, а не всего выражения целиком.

Любой пробельный символ в начале строки может быть, а может и не быть ^\s*.

Модификатор (?m) поддерживается не всеми языками программирования, иногда его заменяет просто /m в конце регулярного выражения

В некоторых реализациях регулярных выражений используются мета символы \A и \Z как аналоги ^ и $

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

Модифицировать дефолтное поведение регулярных выражений можно с помощью флагов (модификаторов) они указываются после строки-шаблона.

Основные из них:

  • g ищет все совпадения, а не только первое вхождение
  • i регистронезависимый поиск
  • m мультистроковость поиска
  • u поддержка юникода

/g - глобальный поиск

Находит все совпадения в строке

  • регулярное выражение: /тест/g
  • строка поиска и результат: тестовая строка тест, и еще тест

/i - регистронезависимый поиск

Иногда нужно все совпадение вне зависимости от регистра.

  • регулярное выражение: /a/ig
  • строка поиска и результат: Aa

Группировка подвыражений

Чтобы дать понять, что данное выражение интерпретируется как единое целое, выражение заключают в скобки ().

() являются мета символами.

  • регулярное выражение: /([1-3][5-7])/g
  • строка поиска и результат: 16

В примере выше мы сгруппировали символьные классы в большую группу.

Более показательный пример. Напишем регулярное выражение для поиска IP адресов.

В простейшем виде, оно выглядит следующим образом:

  • регулярное выражение: /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/g
  • строка поиска и результат: 1.2.3.444

Это просто 4 группы из цифр в количестве от 1 до 3, разделенный точками.

Но выглядит не очень, много повторений, немного подкорректируем, обернем повторяющиеся часть в ():

  • регулярное выражение: /(\d{1,3}\.){3}\d{1,3}/g
  • строка поиска и результат: 1.2.3.444

Этот вариант по сравнению с верхним идентичен, но короче.

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

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

Логическое ИЛИ

Так же может понадобиться выбрать из нескольких значений:

  • регулярное выражение: /(19|20|30)/g
  • строка поиска и результат: 30 20 19 21 19

В примере выше, под шаблон попадает любое из 3 чисел, при этом это одна группа.

А теперь исходя из верхнего примера попробуем найти год,

  • регулярное выражение: /(19|20|30)\d{2}/g
  • строка поиска и результат: 1987

Под это выражение попадают любые четырех значные года.

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

Позиционный просмотр

Иногда бывает нужно извлечь только нужные части совпадающего выражения. Для этого воспользуемся позиционным просмотром.

В выражении нужно указать (?=) и далее шаблон после которого нужно прокрутить вперед.

  • регулярное выражение: /(?=:).+/g
  • строка поиска и результат: http://site.ru

Что тут получается, когда мы доходим до символа :, то что было до этого отрезается.

Однако, фактического совпадения со знаком : нет, метасимвол (?=) говорит механизму регулярных выражений обнаружить совпадение со знаком : и прокрутить выражение вперед.

Перепишем выражение по другому

  • регулярное выражение: /.+(:)/g
  • строка поиска и результат: http://site.ru

Совпала, часть, которая идет после символа :.

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

Любое подвыражение может быть превращено в выражение для просмотра вперед предварив его (?=)

Как составить регулярное выражение

Его следует разложить на части.

Например, адрес электронной почты.

Итак, из чего состоит регулярное выражение для адреса электронной почты.

1
prefix@domain.domain-zone

Электронная почта состоит из 5 частей:

  • prefix
  • @
  • domain
  • .
  • domain-zone

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

[\w\.-]+@[\w-]+\.[a-zа-я]{2,9}

То есть prefix @ domain . domain-zone. Все части отделил пробелами.

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

Регулярные выражения редко когда бывают верными и неверными. Чаще вес имеет степень строгости сопоставления с шаблоном.

Примеры

Рассмотрим решение реальных примеров.

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

Дата рождения

01-31.01-12.1900-2099

  • регулярное выражение: /^(?:0[1-9]|[12]\d|3[01])([\/.])(?:0[1-9]|1[012])\1(?:19|20)\d\d$/
  • строка поиска и результат: 19.12.1900

Разберем каждую часть шаблона подробнее

Группа день

1
2
3
4
5
6
7
8
^ начало строки
(
?:0[1-9] строка от 01-09
|
[12]\d если первый символ 1 или 2 то есть 10-29
|
3[01] частный случай 30-31 
)

В результате получаем любой день от 01 до 31

Группа месяц

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

1
2
3
4
5
(
?:0[1-9] от 01 до 09
|
1[012] 10-12
)

Здесь четкий месяц от 01-12

Группа год

Далее там идет ссылка на уже существующую группу \1, в данном случае это ([\/.])

1
2
3
4
5
6
(
?:19|20 обязательно одна из цифр 19 или 20
)
\d одна цифра
\d одна цифра
$ конец строки регулярного выражения

Получаем год от 1900 до 2099

Ip адрес

  • регулярное выражение: /([0-9]{1,3}[\.]){3}[0-9]{1,3}/gm
  • строка поиска и результат: 1.1.1.1
1
2
3
4
5
(
[0-9]{1,3} число от 0 до 999 для простоты не будем ограничивать
[\.] точка
){3} повторяем три такие группы
[0-9]{1,3} делаем последнюю группу символов
Авторский пост защищен лицензией CC BY 4.0 .