Как найти цифру с буквой регулярным выражением

Регулярные выражения (их еще называют regexp, или regex) — это механизм для поиска и замены текста. В строке, файле, нескольких файлах… Их используют разработчики в коде приложения, тестировщики в автотестах, да просто при работе в командной строке!

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

Например, на вход приходит дата рождения в формате ДД.ММ.ГГГГГ. Вам надо передать ее дальше, но уже в формате ГГГГ-ММ-ДД. Как это сделать с помощью простого поиска? Вы же не знаете заранее, какая именно дата будет.

А регулярное выражение позволяет задать шаблон «найди мне цифры в таком-то формате».

Для чего применяют регулярные выражения?

  1. Удалить все файлы, начинающиеся на test (чистим за собой тестовые данные)

  2. Найти все логи

  3. grep-нуть логи

  4. Найти все даты

А еще для замены — например, чтобы изменить формат всех дат в файле. Если дата одна, можно изменить вручную. А если их 200, проще написать регулярку и подменить автоматически. Тем более что регулярные выражения поддерживаются даже простым блокнотом (в Notepad++ они точно есть).

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

Содержание

  1. Где пощупать

  2. Поиск текста

  3. Поиск любого символа

  4. Поиск по набору символов

  5. Перечисление вариантов

  6. Метасимволы

  7. Спецсимволы

  8. Квантификаторы (количество повторений)

  9. Позиция внутри строки

  10. Использование ссылки назад

  11. Просмотр вперед и назад

  12. Замена

  13. Статьи и книги по теме

  14. Итого

Где пощупать

Любое регулярное выражение из статьи вы можете сразу пощупать. Так будет понятнее, о чем речь в статье — вставили пример из статьи, потом поигрались сами, делая шаг влево, шаг вправо. Где тренироваться:

  1. Notepad++ (установить Search Mode → Regular expression)

  2. Regex101 (мой фаворит в онлайн вариантах)

  3. Myregexp

  4. Regexr

Инструменты есть, теперь начнём

Поиск текста

Самый простой вариант регэкспа. Работает как простой поиск — ищет точно такую же строку, как вы ввели.

Текст: Море, море, океан

Regex: море

Найдет: Море, море, океан

Выделение курсивом не поможет моментально ухватить суть, что именно нашел regex, а выделить цветом в статье я не могу. Атрибут BACKGROUND-COLOR не сработал, поэтому я буду дублировать регулярки текстом (чтобы можно было скопировать себе) и рисунком, чтобы показать, что именно regex нашел:

Обратите внимание, нашлось именно «море», а не первое «Море». Регулярные выражения регистрозависимые!

Хотя, конечно, есть варианты. В JavaScript можно указать дополнительный флажок i, чтобы не учитывать регистр при поиске. В блокноте (notepad++) тоже есть галка «Match case». Но учтите, что это не функция по умолчанию. И всегда стоит проверить, регистрозависимая ваша реализация поиска, или нет.

А что будет, если у нас несколько вхождений искомого слова?

Текст: Море, море, море, океан

Regex: море

Найдет: Море, море, море, океан

По умолчанию большинство механизмов обработки регэкспа вернет только первое вхождение. В JavaScript есть флаг g (global), с ним можно получить массив, содержащий все вхождения.

А что, если у нас искомое слово не само по себе, это часть слова? Регулярное выражение найдет его:

Текст: Море, 55мореон, океан

Regex: море

Найдет: Море, 55мореон, океан

Это поведение по умолчанию. Для поиска это даже хорошо. Вот, допустим, я помню, что недавно в чате коллега рассказывала какую-то историю про интересный баг в игре. Что-то там связанное с кораблем… Но что именно? Уже не помню. Как найти?

Если поиск работает только по точному совпадению, мне придется перебирать все падежи для слова «корабль». А если он работает по включению, я просто не буду писать окончание, и все равно найду нужный текст:

Regex: корабл

Найдет:

На корабле

И тут корабль

У корабля

Это статический, заранее заданный текст. Но его можно найти и без регулярок. Регулярные выражения особенно хороши, когда мы не знаем точно, что мы ищем. Мы знаем часть слова, или шаблон.

Поиск любого символа

. — найдет любой символ (один).

Текст:

Аня

Ася

Оля

Аля

Валя

Regex: А.я

Результат:

Аня

Ася

Оля

Аля

Валя

Символ «.» заменяет 1 любой символ

Символ «.» заменяет 1 любой символ

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

А6я

А&я

А я

Учтите это при поиске! Точка очень удобный символ, но в то же время очень опасный — если используете ее, обязательно тестируйте получившееся регулярное выражение. Найдет ли оно то, что нужно? А лишнее не найдет?

Точку точка тоже найдет!

Regex: file.

Найдет:

file.txt

file1.txt

file2.xls

Но что, если нам надо найти именно точку? Скажем, мы хотим найти все файлы с расширением txt и пишем такой шаблон:

Regex: .txt

Результат:

file.txt

log.txt

file.png

1txt.doc

one_txt.jpg

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

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

Regex: .txt

Результат:

file.txt

log.txt

file.png

1txt.doc

one_txt.jpg

Также мы будем поступать со всеми спецсимволами. Хотим найти именно такой символ в тексте? Добавляем перед ним обратный слеш.

Правило поиска для точки:

. — любой символ

. — точка

Поиск по набору символов

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

Regex: А..а

Результат:

Анна

Алла

аоикА74арплт

Аркан

А^&а

Абба

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

Regex: А[нл][нл]а

Результат:

Анна

Алла

аоикА74арплт

Аркан

А^&а

Абба

Вот теперь результат уже лучше! Да, нам все еще может вернуться «Анла», но такие ошибки исправим чуть позже.

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

[нл] — только «н» и «л»

[а-я] — все русские буквы в нижнем регистре от «а» до «я» (кроме «ё»)

[А-Я]    — все заглавные русские буквы

[А-Яа-яЁё]  — все русские буквы

[a-z]  — латиница мелким шрифтом

[a-zA-Z]  — все английские буквы

[0-9]  — любая цифра

[В-Ю]   — буквы от «В» до «Ю» (да, диапазон — это не только от А до Я)

[А-ГО-Р]   — буквы от «А» до «Г» и от «О» до «Р»

Обратите внимание — если мы перечисляем возможные варианты, мы не ставим между ними разделителей! Ни пробел, ни запятую — ничего.

[абв] — только «а», «б» или «в»

[а б в] — «а», «б», «в», или пробел (что может привести к нежелательному результату)

[а, б, в] — «а», «б», «в», пробел или запятая

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

  • Символ до дефиса — начало диапазона

  • Символ после — конец

Один символ! Не два или десять, а один! Учтите это, если захотите написать что-то типа [1-31]. Нет, это не диапазон от 1 до 31, эта запись читается так:

  • Диапазон от 1 до 3

  • И число 1

Здесь отсутствие разделителей играет злую шутку с нашим сознанием. Ведь кажется, что мы написали диапазон от 1 до 31! Но нет. Поэтому, если вы пишете регулярные выражения, очень важно их тестировать. Не зря же мы тестировщики! Проверьте то, что написали! Особенно, если с помощью регулярного выражения вы пытаетесь что-то удалить =)) Как бы не удалили лишнее…

Указание диапазона вместо точки помогает отсеять заведомо плохие данные:

Regex: А.я или А[а-я]я

Результат для обоих:

Аня

Ася

Аля

Результат для «А.я»:

А6я

А&я

А я

^ внутри [] означает исключение:

[^0-9]  — любой символ, кроме цифр

[^ёЁ]  — любой символ, кроме буквы «ё»

[^а-в8]  — любой символ, кроме букв «а», «б», «в» и цифры 8

Например, мы хотим найти все txt файлы, кроме разбитых на кусочки — заканчивающихся на цифру:

Regex: [^0-9].txt

Результат:

file.txt

log.txt

file_1.txt

1.txt

Так как квадратные скобки являются спецсимволами, то их нельзя найти в тексте без экранирования:

Regex: fruits[0]

Найдет: fruits0

Не найдет: fruits[0]

Это регулярное выражение говорит «найди мне текст «fruits», а потом число 0». Квадратные скобки не экранированы — значит, внутри будет набор допустимых символов.

Если мы хотим найти именно 0-левой элемент массива фруктов, надо записать так:

Regex: fruits[0]

Найдет: fruits[0]

Не найдет: fruits0

А если мы хотим найти все элементы массива фруктов, мы внутри экранированных квадратных скобок ставим неэкранированные!

Regex: fruits[[0-9]]

Найдет:

fruits[0] = “апельсин”;

fruits[1] = “яблоко”;

fruits[2] = “лимон”;

Не найдет:

cat[0] = “чеширский кот”;

Конечно, «читать» такое регулярное выражение становится немного тяжело, столько разных символов написано…

Без паники! Если вы видите сложное регулярное выражение, то просто разберите его по частям. Помните про основу эффективного тайм-менеджмента? Слона надо есть по частям.

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

— Ууууууу, я это за день не закончу!

Проблема в том, что груз задачи мешает работать. Мы ведь понимаем, что это надолго. А большую задачу делать не хочется… Поэтому мы ее откладываем, беремся за задачи поменьше. В итоге да, день прошел, а мы не успели закончить.

А если не тратить время на размышления «сколько времени это у меня займет», а сосредоточиться на конкретной задаче (в данном случае — первом письме из стопки, потом втором…), то не успеете оглянуться, как уже всё разгребли!

Разберем по частям регулярное выражение — fruits[[0-9]]

Сначала идет просто текст — «fruits».

Потом обратный слеш. Ага, он что-то экранирует.

Что именно? Квадратную скобку. Значит, это просто квадратная скобка в моем тексте — «fruits[»

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

Нашли. Наш набор: [0-9]. То есть любое число. Но одно. Там не может быть 10, 11 или 325, потому что квадратные скобки без квантификатора (о них мы поговорим чуть позже) заменяют ровно один символ.

Пока получается: fruits[«любое однозназначное число»

Дальше снова обратный слеш. То есть следующий за ним спецсимвол будет просто символом в моем тексте.

А следующий символ — ]

Получается выражение: fruits[«любое однозназначное число»]

Наше выражение найдет значения массива фруктов! Не только нулевое, но и первое, и пятое… Вплоть до девятого:

Regex: fruits[[0-9]]

Найдет:

fruits[0] = “апельсин”;

fruits[1] = “яблоко”;

fruits[9] = “лимон”;

Не найдет:

fruits[10] = “банан”;

fruits[325] = “ абрикос ”;

Как найти вообще все значения массива, см дальше, в разделе «квантификаторы».

А пока давайте посмотрим, как с помощью диапазонов можно найти все даты.

Какой у даты шаблон? Мы рассмотрим ДД.ММ.ГГГГ:

  • 2 цифры дня

  • точка

  • 2 цифры месяца

  • точка

  • 4 цифры года

Запишем в виде регулярного выражения: [0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9].

Напомню, что мы не можем записать диапазон [1-31]. Потому что это будет значить не «диапазон от 1 до 31», а «диапазон от 1 до 3, плюс число 1». Поэтому пишем шаблон для каждой цифры отдельно.

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

Давайте его протестируем! Как насчет 8888 года или 99 месяца, а?

Regex: [0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]

Найдет:

01.01.1999

05.08.2015

Тоже найдет:

08.08.8888

99.99.2000

Попробуем ограничить:

  • День месяца может быть максимум 31 — первая цифра [0-3]

  • Максимальный месяц 12 — первая цифра [01]

  • Год или 19.., или 20.. — первая цифра [12], а вторая [09]

Вот, уже лучше, явно плохие данные регулярка отсекла. Надо признать, она отсечет довольно много тестовых данных, ведь обычно, когда хотят именно сломать, то фигачат именно «9999» год или «99» месяц…

Однако если мы присмотримся внимательнее к регулярному выражению, то сможем найти в нем дыры:

Regex: [0-3][0-9].[0-1][0-9].[12][09][0-9][0-9]

Не найдет:

08.08.8888

99.99.2000

Но найдет:

33.01.2000

01.19.1999

05.06.2999

Мы не можем с помощью одного диапазона указать допустимые значения. Или мы потеряем 31 число, или пропустим 39. И если мы хотим сделать проверку даты, одних диапазонов будет мало. Нужна возможность перечислить варианты, о которой мы сейчас и поговорим.

Перечисление вариантов

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

Regex: Оля|Олечка|Котик

Найдет:

Оля

Олечка

Котик

Не найдет:

Оленька

Котенка

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

Regex: А(н|л)я

Найдет:

Аня

Аля

Круглые скобки обозначают группу символов. В этой группе у нас или буква «н», или буква «л». Зачем нужны скобки? Показать, где начинается и заканчивается группа. Иначе вертикальная черта применится ко всем символам — мы будем искать или «Ан», или «ля»:

Regex: Ан|ля

Найдет:

Аня

Аля

Оля

Малюля

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

Эти 2 варианта вернут одно и то же:

  • А(н|л)я

  • А[нл]я

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

Давайте вернемся к задаче «проверить введенную пользователем дату с помощью регулярных выражений». Мы пробовали записать для дня диапазон [0-3][0-9], но он пропускает значения 33, 35, 39… Это нехорошо!

Тогда распишем ТЗ подробнее. Та-а-а-ак… Если первая цифра:

  • 0 — вторая может от 1 до 9 (даты 00 быть не может)

  • 1, 2 — вторая может от 0 до 9

  • 3 — вторая только 0 или 1

Составим регулярные выражения на каждый пункт:

  • 0[1-9]

  • [12][0-9]

  • 3[01]

А теперь осталось их соединить в одно выражение! Получаем: 0[1-9]|[12][0-9]|3[01]

По аналогии разбираем месяц и год. Но это остается вам для домашнего задания =)

Потом, когда распишем регулярки отдельно для дня, месяца и года, собираем все вместе:

(<день>).(<месяц>).(<год>)

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

[0-1][0-9].[12][09][0-9][0-9]

Подставим то, что написали для дня:

0[1-9]|[12][0-9]|3[01].[0-1][0-9].[12][09][0-9][0-9]

Как читается это выражение?

  • ИЛИ   0[1-9]

  • ИЛИ   [12][0-9]

  • ИЛИ    3[01].[0-1][0-9].[12][09][0-9][0-9]

Видите проблему? Число «19» будет считаться корректной датой. Система не знает, что перебор вариантов | закончился на точке после дня. Чтобы она это поняла, нужно взять перебор в скобки. Как в математике, разделяем слагаемые.

Так что запомните — если перебор идет в середине слова, его надо взять в круглые скобки!

Regex: А(нн|лл|лин|нтонин)а

Найдет:

Анна

Алла

Алина

Антонина

Без скобок:

Regex: Анн|лл|лин|нтонина

Найдет:

Анна

Алла

Аннушка

Кукулинка

Итого, если мы хотим указать допустимые значения:

  • Одного символа — используем []

  • Нескольких символов или целого слова — используем |

Метасимволы

Если мы хотим найти число, то пишем диапазон [0-9].

Если букву, то [а-яА-ЯёЁa-zA-Z].

А есть ли другой способ?

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

Символ

Эквивалент

Пояснение

d

[0-9]

Цифровой символ

D

[^0-9]

Нецифровой символ

s

[ fnrtv]

Пробельный символ

S

[^ fnrtv]

Непробельный символ

w

[[:word:]]

Буквенный или цифровой символ или знак подчёркивания

W

[^[:word:]]

Любой символ, кроме буквенного или цифрового символа или знака подчёркивания

.

Вообще любой символ

Это самые распространенные символы, которые вы будете использовать чаще всего. Но давайте разберемся с колонкой «эквивалент». Для d все понятно — это просто некие числа. А что такое «пробельные символы»? В них входят:

Символ

Пояснение

Пробел

r

Возврат каретки (Carriage return, CR)

n

Перевод строки (Line feed, LF)

t

Табуляция (Tab)

v

Вертикальная табуляция (vertical tab)

f

Конец страницы (Form feed)

[b]

Возврат на 1 символ (Backspace)

Из них вы чаще всего будете использовать сам пробел и перевод строки — выражение «rn». Напишем текст в несколько строк:

Первая строка

Вторая строка

Для регулярного выражения это:

Первая строкаrnВторая строка

А вот что такое backspace в тексте? Как его можно увидеть вообще? Это же если написать символ и стереть его. В итоге символа нет! Неужели стирание хранится где-то в памяти? Но тогда это было бы ужасно, мы бы вообще ничего не смогли найти — откуда нам знать, сколько раз текст исправляли и в каких местах там теперь есть невидимый символ [b]?

Выдыхаем — этот символ не найдет все места исправления текста. Просто символ backspace — это ASCII символ, который может появляться в тексте (ASCII code 8, или 10 в octal). Вы можете «создать» его, написать в консоли браузера (там используется JavaScript):

console.log("abcbbdef");

Результат команды:

adef

Мы написали «abc», а потом стерли «b» и «с». В итоге пользователь в консоли их не видит, но они есть. Потому что мы прямо в коде прописали символ удаления текста. Не просто удалили текст, а прописали этот символ. Вот такой символ регулярное выражение  [b] и найдет.

См также:

What’s the use of the [b] backspace regex? — подробнее об этом символе

Но обычно, когда мы вводим s, мы имеем в виду пробел, табуляцию, или перенос строки.

Ок, с этими эквивалентами разобрались. А что значит [[:word:]]? Это один из способов заменить диапазон. Чтобы запомнить проще было, написали значения на английском, объединив символы в классы. Какие есть классы:

Класс символов

Пояснение

[[:alnum:]]

Буквы или цифры: [а-яА-ЯёЁa-zA-Z0-9]

[[:alpha:]]

Только буквы: [а-яА-ЯёЁa-zA-Z]

[[:digit:]]

Только цифры: [0-9]

[[:graph:]]

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

[[:print:]]

Отображаемые символы и пробелы

[[:space:]]

Пробельные символы [ fnrtv]

[[:punct:]]

Знаки пунктуации: ! ” # $ % & ‘ ( ) * + , -. / : ; < = > ? @ [ ] ^ _ ` { | }

[[:word:]]

Буквенный или цифровой символ или знак подчёркивания: [а-яА-ЯёЁa-zA-Z0-9_]

Теперь мы можем переписать регулярку для проверки даты, которая выберет лишь даты формата ДД.ММ.ГГГГГ, отсеяв при этом все остальное:

[0-9][0-9].[0-9][0-9].[0-9][0-9][0-9][0-9]

dd.dd.dddd

Согласитесь, через метасимволы запись посимпатичнее будет =))

Спецсимволы

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

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

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

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

Regex: 2^2 = 4

Найдет: 2^2 = 4

Можно экранировать целую последовательность символов, заключив её между Q и E (но не во всех разновидностях).

Regex: Q{кто тут?}E

Найдет: {кто тут?}

Квантификаторы (количество повторений)

Усложняем задачу. Есть некий текст, нам нужно вычленить оттуда все email-адреса. Например:

  • test@mail.ru

  • olga31@gmail.com

  • pupsik_99@yandex.ru

Как составляется регулярное выражение? Нужно внимательно изучить данные, которые мы хотим получить на выходе, и составить по ним шаблон. В email два разделителя — собачка «@» и точка «.».

Запишем ТЗ для регулярного выражения:

  • Буквы / цифры / _

  • Потом @

  • Снова буквы / цифры / _

  • Точка

  • Буквы

Так, до собачки у нас явно идет метасимвол «w», туда попадет и просто текст (test), и цифры (olga31), и подчеркивание (pupsik_99). Но есть проблема — мы не знаем, сколько таких символов будет. Это при поиске даты все ясно — 2 цифры, 2 цифры, 4 цифры. А тут может быть как 2, так и 22 символа.

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

Символ «+» означает «одно или более повторений», это как раз то, что нам надо! Получаем: w+@

После собачки и снова идет w, и снова от одного повторения. Получаем: w+@w+.

После точки обычно идут именно символы, но для простоты можно снова написано w. И снова несколько символов ждем, не зная точно сколько. Итого получилось выражение, которое найдет нам email любой длины:

Regex: w+@w+.w+

Найдет:

test@mail.ru

olga31@gmail.com

pupsik_99_and_slonik_33_and_mikky_87_and_kotik_28@yandex.megatron

Какие есть квантификаторы, кроме знака «+»?

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

Число повторений

?

Ноль или одно

*

Ноль или более

+

Один или более

Символ * часто используют с точкой — когда нам неважно, какой идет текст до интересующей нас фразы, мы заменяем его на «.*» — любой символ ноль или более раз.

Regex: .*dd.dd.dddd.*

Найдет:

01.01.2000

Приходи на ДР 09.08.2015! Будет весело!

Но будьте осторожны! Если использовать «.*» повсеместно, можно получить много ложноположительных срабатываний:

Regex: .*@.*..*

Найдет:

test@mail.ru

olga31@gmail.com

pupsik_99@yandex.ru

Но также найдет:

@yandex.ru

test@.ru

test@mail.

Уж лучше w, и плюсик вместо звездочки.

А вот есть мы хотим найти все лог-файлы, которые нумеруются — log, log1, log2… log133, то * подойдет хорошо:

Regex: logd*.txt

Найдет:

log.txt

log1.txt

log2.txt

log3.txt

log33.txt

log133.txt

А знак вопроса (ноль или одно повторение) поможет нам найти людей с конкретной фамилией — причем всех, и мужчин, и женщин:

Regex: Назина?

Найдет:

Назин

Назина

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

Regex: (Хихи)*(Хаха)*

Найдет:

ХихиХаха

ХихиХихиХихи

Хихи

Хаха

ХихиХихиХахаХахаХаха

(пустота — да, её такая регулярка тоже найдет)

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

А что, если мне нужно определенное количество повторений? Скажем, я хочу записать регулярное выражение для даты. Пока мы знаем только вариант «перечислить нужный метасимвол нужное количество раз» — dd.dd.dddd.

Ну ладно 2-4 раза повторение идет, а если 10? А если повторить надо фразу? Так и писать ее 10 раз? Не слишком удобно. А использовать * нельзя:

Regex: d*.d*.d*

Найдет:

.0.1999

05.08.20155555555555555

03444.025555.200077777777777777

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

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

Число повторений

{n}

Ровно n раз

{m,n}

От m до n включительно

{m,}

Не менее m

{,n}

Не более n

Таким образом, для проверки даты можно использовать как перечисление d n раз, так и использование квантификатора:

dd.dd.dddd

d{2}.d{2}.d{4}

Обе записи будут валидны. Но вторая читается чуть проще — не надо самому считать повторения, просто смотрим на цифру.

Не забывайте — квантификатор применяется к последнему символу!

Regex: data{2}

Найдет: dataa

Не найдет: datadata

Или группе символов, если они взяты в круглые скобки:

Regex: (data){2}

Найдет: datadata

Не найдет: dataa

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

Regex: x{3}

Найдет: x{3}

Иногда квантификатор находит не совсем то, что нам нужно.

Regex: <.*>

Ожидание:

<req>
<query>Ан</query>
<gender>FEMALE</gender>

Реальность:

<req> <query>Ан</query> <gender>FEMALE</gender></req>

Мы хотим найти все теги HTML или XML по отдельности, а регулярное выражение возвращает целую строку, внутри которой есть несколько тегов.

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

Если мы понимаем, что нашли не то, что хотели, можно пойти двумя путями:

  1. Учитывать символы, не соответствующие желаемому образцу

  2. Определить квантификатор как нежадный (ленивый, англ. lazy) — большинство реализаций позволяют это сделать, добавив после него знак вопроса.

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

<[^>]*>

Оно ищет открывающий тег, внутри которого все, что угодно, кроме закрывающегося тега «>», и только потом тег закрывается. Так мы не даем захватить лишнее. Но учтите, использование ленивых квантификаторов может повлечь за собой обратную проблему — когда выражению соответствует слишком короткая, в частности, пустая строка.

Жадный

Ленивый

*

*?

+

+?

{n,}

{n,}?

Есть еще и сверхжадная квантификация, также именуемая ревнивой. Но о ней почитайте в википедии =)

Позиция внутри строки

По умолчанию регулярные выражения ищут «по включению».

Regex: арка

Найдет:

арка

чарка

аркан

баварка

знахарка

Это не всегда то, что нам нужно. Иногда мы хотим найти конкретное слово.

Если мы ищем не одно слово, а некую строку, проблема решается в помощью пробелов:

Regex: Товар №d+ добавлен в корзину в dd:dd

Найдет: Товар №555 добавлен в корзину в 15:30

Не найдет: Товарный чек №555 добавлен в корзину в 15:30

Или так:

Regex: .* арка .*

Найдет: Триумфальная арка была…

Не найдет: Знахарка сегодня…

А что, если у нас не пробел рядом с искомым словом? Это может быть знак препинания: «И вот перед нами арка.», или «…арка:».

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

Regex: bаркаb

Найдет:

арка

Не найдет:

чарка

аркан

баварка

знахарка

Можно ограничить только спереди — «найди все слова, которые начинаются на такое-то значение»:

Regex: bарка

Найдет:

арка

аркан

Не найдет:

чарка

баварка

знахарка

Можно ограничить только сзади —  «найди все слова, которые заканчиваются на такое-то значение»:

Regex: аркаb

Найдет:

арка

чарка

баварка

знахарка

Не найдет:

аркан

Если использовать метасимвол B, он найдем нам НЕ-границу слова:

Regex: BакрB

Найдет:

закройка

Не найдет:

акр

акрил

Если мы хотим найти конкретную фразу, а не слово, то используем следующие спецсимволы:

^ — начало текста (строки)

$ — конец текста (строки)

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

Regex: ^Я нашел!$

Найдет:

Я нашел!

Не найдет:

Смотри! Я нашел!

Я нашел! Посмотри!

Итого метасимволы, обозначающие позицию строки:

Символ

Значение

b

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

B

Не граница слова

^

начало текста (строки)

$

конец текста (строки)

Использование ссылки назад

Допустим, при тестировании приложения вы обнаружили забавный баг в тексте — дублирование предлога «на»: «Поздравляем! Вы прошли на на новый уровень». А потом решили проверить, есть ли в коде еще такие ошибки.

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

Regex: [ ]+(w+)[ ]+1

Текст: Поздравляем! Вы прошли на на новый уровень. Так что что улыбаемся и и машем.

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

[ ]+ → один или несколько пробелов, так мы ограничиваем слово. В принципе, тут можно заменить на метасимвол b.

(w+) → любой буквенный или цифровой символ, или знак подчеркивания. Квантификатор «+» означает, что символ должен идти минимум один раз. А то, что мы взяли все это выражение в круглые скобки, говорит о том, что это группа. Зачем она нужна, мы пока не знаем, ведь рядом с ней нет квантификатора. Значит, не для повторения. Но в любом случае, найденный символ или слово — это группа 1.

[ ]+ → снова один или несколько пробелов.

1 → повторение группы 1. Это и есть ссылка назад. Так она записывается в JavaScript-е.

Важно: синтаксис ссылок назад очень зависит от реализации регулярных выражений.

ЯП

Как обозначается ссылка назад

JavaScript

vi

Perl

$

PHP

$matches[1]

Java

Python

group[1]

C#

match.Groups[1]

Visual Basic .NET

match.Groups(1)

Для чего еще нужна ссылка назад? Например, можно проверить верстку HTML, правильно ли ее составили? Верно ли, что открывающийся тег равен закрывающемуся?

Напишите выражение, которое найдет правильно написанные теги:

<h2>Заголовок 2-ого уровня</h2>
<h3>Заголовок 3-ого уровня</h3>

Но не найдет ошибки:

<h2>Заголовок 2-ого уровня</h3>

Просмотр вперед и назад

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

Представление

Вид просмотра

Пример

Соответствие

(?=шаблон)

Позитивный просмотр вперёд

Блюдо(?=11)

Блюдо1

Блюдо11

Блюдо113

Блюдо511

(?!шаблон)

Негативный просмотр вперёд (с отрицанием)

Блюдо(?!11)

Блюдо1

Блюдо11

Блюдо113

Блюдо511

(?<=шаблон)

Позитивный просмотр назад

(?<=Ольга )Назина

Ольга Назина

Анна Назина

(?шаблон)

Негативный просмотр назад (с отрицанием)

(см ниже на рисунке)

Ольга Назина

Анна Назина

Замена

Важная функция регулярных выражений — не только найти текст, но и заменить его на другой текст! Простейший вариант замены — слово на слово:

RegEx: Ольга

Замена: Макар

Текст был: Привет, Ольга!

Текст стал: Привет, Макар!

Но что, если у нас в исходном тексте может быть любое имя? Вот что пользователь ввел, то и сохранилось. А нам надо на Макара теперь заменить. Как сделать такую замену? Через знак доллара. Давайте разберемся с ним подробнее.

Знак доллара в замене — обращение к группе в поиске. Ставим знак доллара и номер группы. Группа — это то, что мы взяли в круглые скобки. Нумерация у групп начинается с 1.

RegEx: (Оля) + Маша

Замена: $1

Текст был: Оля + Маша

Текст стал: Оля

Мы искали фразу «Оля + Маша» (круглые скобки не экранированы, значит, в искомом тексте их быть не должно, это просто группа). А замнили ее на первую группу — то, что написано в первых круглых скобках, то есть текст «Оля».

Это работает и когда искомый текст находится внутри другого:

RegEx: (Оля) + Маша

Замена: $1

Текст был: Привет, Оля + Маша!

Текст стал: Привет, Оля!

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

RegEx: (Оля) + (Маша)

Замена: $2 – $1

Текст был: Оля + Маша

Текст стал: Маша — Оля

Теперь вернемся к нашей задаче — есть строка приветствия «Привет, кто-то там!», где может быть написано любое имя (даже просто числа вместо имени). Мы это имя хотим заменить на «Макар».

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

RegEx: ^(Привет, ).*(!)$

Замена: $1Макар$2

Текст был (или или):

Привет, Ольга!

Привет, 777!

Текст стал:

Привет, Макар!

Давайте разберемся, как работает это регулярное выражение.

^ — начало строки.

Дальше скобка. Она не экранирована — значит, это группа. Группа 1. Поищем для нее закрывающую скобку и посмотрим, что входит в эту группу. Внутри группы текст «Привет, »

После группы идет выражение «.*» — ноль или больше повторений чего угодно. То есть вообще любой текст. Или пустота, она в регулярку тоже входит.

Потом снова открывающаяся скобка. Она не экранирована — ага, значит, это вторая группа. Что внутри? Внутри простой текст — «!».

И потом символ $ — конец строки.

Посмотрим, что у нас в замене.

$1 — значение группы 1. То есть текст «Привет, ».

Макар — просто текст. Обратите внимание, что мы или включает пробел после запятой в группу 1, или ставим его в замене после «$1», иначе на выходе получим «Привет,Макар».

$2 — значение группы 2, то есть текст «!»

Вот и всё!

А что, если нам надо переформатировать даты? Есть даты в формате ДД.ММ.ГГГГ, а нам нужно поменять формат на ГГГГ-ММ-ДД.

Регулярное выражение для поиска у нас уже есть — «d{2}.d{2}.d{4}». Осталось понять, как написать замену. Посмотрим внимательно на ТЗ:

ДД.ММ.ГГГГ

ГГГГ-ММ-ДД

По нему сразу понятно, что нам надо выделить три группы. Получается так: (d{2}).(d{2}).(d{4})

В результате у нас сначала идет год — это третья группа. Пишем: $3

Потом идет дефис, это просто текст: $3-

Потом идет месяц. Это вторая группа, то есть «$2». Получается: $3-$2

Потом снова дефис, просто текст: $3-$2-

И, наконец, день. Это первая группа, $1. Получается: $3-$2-$1

Вот и всё!

RegEx: (d{2}).(d{2}).(d{4})

Замена: $3-$2-$1

Текст был:

05.08.2015

01.01.1999

03.02.2000

Текст стал:

2015-08-05

1999-01-01

2000-02-03

Другой пример — я записываю в блокнот то, что успела сделать за цикл в 12 недель. Называется файлик «done», он очень мотивирует! Если просто вспоминать «что же я сделал?», вспоминается мало. А тут записал и любуешься списком.

Вот пример улучшалок по моему курсу для тестировщиков:

  1. Сделала сообщения для бота — чтобы при выкладке новых тем писал их в чат

  2. Фолкс — поправила статью «Расширенный поиск», убрала оттуда про пустой ввод при простом поиске, а то путал

  3. Обновила кусочек про эффект золушки (переписывала под ютуб)

И таких набирается штук 10-25. За один цикл. А за год сколько? Ух! Вроде небольшие улучшения, а набирается прилично.

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

Удаляю с помощью регулярного выражения:

RegEx: d+. (.*)

Замена: $1

Текст был:

1. Раз

2. Два

Текст стал:

Раз

Два

Можно было бы и вручную. Но для списка больше 5 элементов это дико скучно и уныло. А так нажал одну кнопочку в блокноте — и готово!

Так что регулярные выражения могут помочь даже при написании статьи =)

Статьи и книги по теме

Книги

Регулярные выражения 10 минут на урок. Бен Форта — Очень рекомендую! Прям шикарная книга, где все просто, доступно, понятно. Стоит 100 рублей, а пользы море.

Статьи

Вики — https://ru.wikipedia.org/wiki/Регулярные_выражения. Да, именно ее вы будете читать чаще всего. Я сама не помню наизусть все метасимволы. Поэтому, когда использую регулярки, гуглю их, википедия всегда в топе результатов. А сама статья хорошая, с табличками удобными.

Регулярные выражения для новичков — https://tproger.ru/articles/regexp-for-beginners/

Итого

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

  1. Найти все нужные файлы в папке.

  2. Grep-нуть логи — отсечь все лишнее и найти только ту информацию, которая вам сейчас интересна.

  3. Проверить по базе, нет ли явно некорректных записей — не остались ли тестовые данные в продакшене? Не присылает ли смежная система какую-то фигню вместо нормальных данных?

  4. Проверить данные чужой системы, если она выгружает их в файл.

  5. Выверить файлик текстов для сайта — нет ли там дублирования слов?

  6. Подправить текст для статьи.

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

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

Поэтому, если вы пишете регулярку, обязательно ее протестируйте! Особенно, если вы ее пишете в паре с командой rm (удаление файлов в linux). Сначала проверьте, правильно ли отрабатывает поиск, а потом уже удаляйте то, что нашли.

Регулярное выражение может не найти то, что вы ожидали. Или найти что-то лишнее. Особенно если у вас идет цепочка регулярок. Думаете, это так легко — правильно написать регулярку? Попробуйте тогда решить задачку от Егора или вот эти кроссворды =)

PS — больше полезных статей ищите в моем блоге по метке «полезное». А полезные видео — на моем youtube-канале

Шпаргалка по регулярным выражениям

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

  Аналог Пример Описание
? {0,1} a? одно или ноль вхождений «а»
+ {1,} a+ одно или более вхождений «а»
* {0,} a* ноль или более вхождений «а»

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

Символ «минус» (-) меред модификатором (за исключением U) создаёт его отрицание.

  Описание
g глобальный поиск (обрабатываются все совпадения с шаблоном поиска)
i игнорировать регистр
m многострочный поиск. Поясню: по умолчанию текст это одна строка, с модификатором есть отдельные строки, а значит ^– начало строки в тексте, $– конец строки в тексте.
s текст воспринимается как одна строка, спец символ «точка» (.) будет вкючать и перевод строки
u используется кодировка UTF-8
U инвертировать жадность
x игнорировать все неэкранированные пробельные и перечисленные в классе символы

Спецсимволы

  Аналог Описание
()   подмаска, вложенное выражение
[]   групповой символ
{a,b}   количество вхождений от «a» до «b»
|   логическое «или», в случае с односимвольными альтернативами используйте []
  экранирование спец символа
.   любой сивол, кроме перевода строки
d [0-9] десятичная цифра
D [^d] любой символ, кроме десятичной цифры
f   конец (разрыв) страницы
n   перевод строки
pL   буква в кодировке UTF-8 при использовании модификатора u
r   возврат каретки
s [ tvrnf] пробельный символ
S [^s] любой символ, кроме промельного
t   табуляция
w [0-9a-z_] любая цифра, буква или знак подчеркивания
W [^w] любой символ, кроме цифры, буквы или знака подчеркивания
v   вертикальная табуляция

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

  Пример Описание
^ [^da] отрицание, любой символ кроме «d» или «a»
[a-z] интервал, любой симво от «a» до «z»

Позиция внутри строки

  Пример Соответствие Описание
^ ^a aaa aaa начало строки
$ a$ aaa aaa конец строки
A Aa aaa aaa
aaa aaa
начало текста
z az aaa aaa
aaa aaa
конец текста
b ab
ba
aaa aaa
aaa aaa
граница слова, утверждение: предыдущий символ словесный, а следующий – нет, либо наоборот
B BaB aaa aaa отсутствие границы слова
G Ga aaa aaa Предыдущий успешный поиск, поиск остановился на 4-й позиции — там, где не нашлось a

Скачать в PDF, PNG.

Якоря

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

^[0-9]+

Здесь символ ^обозначает начало строки. Без него шаблон соответствовал бы любой строке, содержащей цифру.

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

Символьные классы в регулярных выражениях соответствуют сразу некоторому набору символов. Например, dсоответствует любой цифре от 0 до 9 включительно, wсоответствует буквам и цифрам, а
W— всем символам, кроме букв и цифр. Шаблон, идентифицирующий буквы, цифры и пробел, выглядит
так:

ws

POSIX

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

Утверждения

Поначалу практически у всех возникают трудности с пониманием утверждений, однако познакомившись с ними ближе, вы
будете использовать их довольно часто. Утверждения предоставляют способ сказать: «я хочу найти в этом документе
каждое слово, включающее букву “q”, за которой не следует “werty”».

[^s]*q(?!werty)[^s]*

Приведенный выше код начинается с поиска любых символов, кроме пробела ([^s]*), за которыми следует
q. Затем парсер достигает «смотрящего вперед» утверждения. Это автоматически делает предшествующий
элемент (символ, группу или символьный класс) условным — он будет соответствовать шаблону, только если
утверждение верно. В нашем случае, утверждение является отрицательным (?!), т. е. оно будет верным,
если то, что в нем ищется, не будет найдено.

Итак, парсер проверяет несколько следующих символов по предложенному шаблону (werty). Если они найдены,
то утверждение ложно, а значит символ qбудет «проигнорирован», т. е. не будет соответствовать шаблону.
Если же wertyне найдено, то утверждение верно, и с qвсе в порядке. Затем продолжается
поиск любых символов, кроме пробела ([^s]*).

Кванторы

Кванторы позволяют определить часть шаблона, которая должна повторяться несколько раз подряд. Например, если вы
хотите выяснить, содержит ли документ строку из от 10 до 20 (включительно) букв «a», то можно использовать этот
шаблон:

a{10,20}

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

".*"

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

<a href="helloworld.htm" title="Привет, Мир">Привет, Мир</a>

Приведенный выше шаблон найдет в этой строке вот такую подстроку:

"helloworld.htm" title="Привет, Мир"

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

".*?"

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

"helloworld.htm" "Привет, Мир"

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

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

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

Шаблон для нахождения точки таков:

.

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

Спецсимволы экранирования в регулярных выражениях

Выражение Соответствие
не соответствует ничему, только экранирует следующий за ним символ. Это нужно, если вы хотите ввести метасимволы !$()*+.<>?[]^{|}в качестве их буквальных значений.
Q не соответствует ничему, только экранирует все символы вплоть до E
E не соответствует ничему, только прекращает экранирование, начатое Q

Подстановка строк

Подстановка строк подробно описана в следующем параграфе «Группы и диапазоны», однако здесь следует упомянуть о
существовании «пассивных» групп. Это группы, игнорируемые при подстановке, что очень полезно, если вы хотите
использовать в шаблоне условие «или», но не хотите, чтобы эта группа принимала участие в подстановке.

Группы и диапазоны

Группы и диапазоны очень-очень полезны. Вероятно, проще будет начать с диапазонов. Они позволяют указать набор
подходящих символов. Например, чтобы проверить, содержит ли строка шестнадцатеричные цифры (от 0 до 9 и от A до F),
следует использовать такой диапазон:

[A-Fa-f0-9]

Чтобы проверить обратное, используйте отрицательный диапазон, который в нашем случае подходит под любой символ, кроме
цифр от 0 до 9 и букв от A до F:

[^A-Fa-f0-9]

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

Использовать «или» очень просто: следующий шаблон ищет «ab» или «bc»:

(ab|bc)

Если в регулярном выражении необходимо сослаться на какую-то из предшествующих групп, следует использовать
n, где вместо nподставить номер нужной группы. Вам может понадобиться шаблон,
соответствующий буквам «aaa» или «bbb», за которыми следует число, а затем те же три буквы. Такой шаблон реализуется
с помощью групп:

(aaa|bbb)[0-9]+1

Первая часть шаблона ищет «aaa» или «bbb», объединяя найденные буквы в группу. За этим следует поиск одной или более
цифр ([0-9]+), и наконец 1. Последняя часть шаблона ссылается на первую группу и ищет то
же самое. Она ищет совпадение с текстом, уже найденным первой частью шаблона, а не соответствующее ему. Таким
образом, «aaa123bbb» не будет удовлетворять вышеприведенному шаблону, так как 1будет искать «aaa»
после числа.

Одним из наиболее полезных инструментов в регулярных выражениях является подстановка строк. При замене текста можно
сослаться на найденную группу, используя $n. Скажем, вы хотите выделить в тексте все слова «wish»
жирным начертанием. Для этого вам следует использовать функцию замены по регулярному выражению, которая может
выглядеть так:

replace(pattern, replacement, subject)

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

([^A-Za-z0-9])(wish)([^A-Za-z0-9])

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

$1<b>$2</b>$3

Ею будет заменена вся найденная по шаблону строка. Мы начинаем замену с первого найденного символа (который не буква
и не цифра), отмечая его $1. Без этого мы бы просто удалили этот символ из текста. То же касается конца
подстановки ($3). В середину мы добавили HTML тег для жирного начертания (разумеется, вместо него вы
можете использовать CSS или <strong>), выделив им вторую группу, найденную по шаблону
($2).

Модификаторы шаблонов

Модификаторы шаблонов используются в нескольких языках, в частности, в Perl. Они позволяют изменить работу парсера.
Например, модификатор iзаставляет парсер игнорировать регистры.

Регулярные выражения в Perl обрамляются одним и тем же символом в начале и в конце. Это может быть любой символ (чаще
используется «/»), и выглядит все таким образом:

/pattern/

Модификаторы добавляются в конец этой строки, вот так:

/pattern/i

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

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

(

Шпаргалка представляет собой общее руководство по шаблонам регулярных выражений без учета специфики какого-либо языка.
Она представлена в виде таблицы, помещающейся на одном печатном листе формата A4.
Создана под лицензией Creative Commons на базе шпаргалки, автором которой является Dave Child.
Скачать в PDF, PNG.

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

Is there a regular expression which checks if a string contains only upper and lowercase letters, numbers, and underscores?

Peter Mortensen's user avatar

asked Dec 3, 2008 at 4:25

1

To match a string that contains only those characters (or an empty string), try

"^[a-zA-Z0-9_]*$"

This works for .NET regular expressions, and probably a lot of other languages as well.

Breaking it down:

^ : start of string
[ : beginning of character group
a-z : any lowercase letter
A-Z : any uppercase letter
0-9 : any digit
_ : underscore
] : end of character group
* : zero or more of the given characters
$ : end of string

If you don’t want to allow empty strings, use + instead of *.


As others have pointed out, some regex languages have a shorthand form for [a-zA-Z0-9_]. In the .NET regex language, you can turn on ECMAScript behavior and use w as a shorthand (yielding ^w*$ or ^w+$). Note that in other languages, and by default in .NET, w is somewhat broader, and will match other sorts of Unicode characters as well (thanks to Jan for pointing this out). So if you’re really intending to match only those characters, using the explicit (longer) form is probably best.

Peter Mortensen's user avatar

answered Dec 3, 2008 at 4:33

Charlie's user avatar

CharlieCharlie

44k4 gold badges43 silver badges68 bronze badges

13

There’s a lot of verbosity in here, and I’m deeply against it, so, my conclusive answer would be:

/^w+$/

w is equivalent to [A-Za-z0-9_], which is pretty much what you want (unless we introduce Unicode to the mix).

Using the + quantifier you’ll match one or more characters. If you want to accept an empty string too, use * instead.

Peter Mortensen's user avatar

answered Dec 5, 2008 at 5:25

kch's user avatar

kchkch

76.8k46 gold badges135 silver badges148 bronze badges

5

You want to check that each character matches your requirements, which is why we use:

[A-Za-z0-9_]

And you can even use the shorthand version:

w

Which is equivalent (in some regex flavors, so make sure you check before you use it). Then to indicate that the entire string must match, you use:

^

To indicate the string must start with that character, then use

$

To indicate the string must end with that character. Then use

w+ or w*

To indicate “1 or more”, or “0 or more”. Putting it all together, we have:

^w*$

answered Dec 3, 2008 at 5:08

Anton's user avatar

AntonAnton

1,3772 gold badges17 silver badges30 bronze badges

2

Although it’s more verbose than w, I personally appreciate the readability of the full POSIX character class names ( http://www.zytrax.com/tech/web/regex.htm#special ), so I’d say:

^[[:alnum:]_]+$

However, while the documentation at the above links states that w will “Match any character in the range 0 – 9, A – Z and a – z (equivalent of POSIX [:alnum:])”, I have not found this to be true. Not with grep -P anyway. You need to explicitly include the underscore if you use [:alnum:] but not if you use w. You can’t beat the following for short and sweet:

^w+$

Along with readability, using the POSIX character classes (http://www.regular-expressions.info/posixbrackets.html) means that your regex can work on non ASCII strings, which the range based regexes won’t do since they rely on the underlying ordering of the ASCII characters which may be different from other character sets and will therefore exclude some non-ASCII characters (letters such as œ) which you might want to capture.

answered Jun 9, 2012 at 22:53

Day Davis Waterbury's user avatar

Um…question: Does it need to have at least one character or no? Can it be an empty string?

^[A-Za-z0-9_]+$

Will do at least one upper or lower case alphanumeric or underscore. If it can be zero length, then just substitute the + for *:

^[A-Za-z0-9_]*$

If diacritics need to be included (such as cedilla – ç) then you would need to use the word character which does the same as the above, but includes the diacritic characters:

^w+$

Or

^w*$

Peter Mortensen's user avatar

answered Dec 3, 2008 at 4:31

BenAlabaster's user avatar

BenAlabasterBenAlabaster

38.9k21 gold badges109 silver badges151 bronze badges

5

Use

^([A-Za-z]|[0-9]|_)+$

…if you want to be explicit, or:

^w+$

…if you prefer concise (Perl syntax).

Peter Mortensen's user avatar

answered Dec 3, 2008 at 4:31

Drew Hall's user avatar

Drew HallDrew Hall

28.3k12 gold badges61 silver badges81 bronze badges

1

In computer science, an alphanumeric value often means the first character is not a number, but it is an alphabet or underscore. Thereafter the character can be 0-9, A-Z, a-z, or underscore (_).

Here is how you would do that:

Tested under PHP:

$regex = '/^[A-Za-z_][A-Za-zd_]*$/'

Or take

^[A-Za-z_][A-Za-zd_]*$

and place it in your development language.

Peter Mortensen's user avatar

answered Jan 31, 2012 at 13:38

Danuel O'Neal's user avatar

Use lookaheads to do the “at least one” stuff. Trust me, it’s much easier.

Here’s an example that would require 1-10 characters, containing at least one digit and one letter:

^(?=.*d)(?=.*[A-Za-z])[A-Za-z0-9]{1,10}$

Note: I could have used w, but then ECMA/Unicode considerations come into play, increasing the character coverage of the w “word character”.

Peter Mortensen's user avatar

answered Nov 12, 2010 at 18:20

boooloooo's user avatar

booolooooboooloooo

1591 silver badge3 bronze badges

1

This works for me. I found this in the O’Reilly’s “Mastering Regular Expressions”:

/^w+$/

Explanation:

  • ^ asserts position at start of the string
    • w+ matches any word character (equal to [a-zA-Z0-9_])
    • “+” Quantifier — Matches between one and unlimited times, as many times as possible, giving back as needed (greedy)
  • $ asserts position at the end of the string

Verify yourself:

const regex = /^w+$/;
const str = `nut_cracker_12`;
let m;

if ((m = regex.exec(str)) !== null) {
    // The result can be accessed through the `m`-variable.
    m.forEach((match, groupIndex) => {
        console.log(`Found match, group ${groupIndex}: ${match}`);
    });
}

Peter Mortensen's user avatar

answered Jun 8, 2020 at 13:33

MiKr13's user avatar

MiKr13MiKr13

1,24713 silver badges20 bronze badges

Try these multi-lingual extensions I have made for string.

IsAlphaNumeric – The string must contain at least one alpha (letter in Unicode range, specified in charSet) and at least one number (specified in numSet). Also, the string should consist only of alpha and numbers.

IsAlpha – The string should contain at least one alpha (in the language charSet specified) and consist only of alpha.

IsNumeric – The string should contain at least one number (in the language numSet specified) and consist only of numbers.

The charSet/numSet range for the desired language can be specified. The Unicode ranges are available on Unicode Chart.

API:

public static bool IsAlphaNumeric(this string stringToTest)
{
    // English
    const string charSet = "a-zA-Z";
    const string numSet = @"0-9";

    // Greek
    //const string charSet = @"u0388-u03EF";
    //const string numSet = @"0-9";

    // Bengali
    //const string charSet = @"u0985-u09E3";
    //const string numSet = @"u09E6-u09EF";

    // Hindi
    //const string charSet = @"u0905-u0963";
    //const string numSet = @"u0966-u096F";

    return Regex.Match(stringToTest, @"^(?=[" + numSet + @"]*?[" + charSet + @"]+)(?=[" + charSet + @"]*?[" + numSet + @"]+)[" + charSet + numSet +@"]+$").Success;
}

public static bool IsNumeric(this string stringToTest)
{
    //English
    const string numSet = @"0-9";

    //Hindi
    //const string numSet = @"u0966-u096F";

    return Regex.Match(stringToTest, @"^[" + numSet + @"]+$").Success;
}

public static bool IsAlpha(this string stringToTest)
{
    //English
    const string charSet = "a-zA-Z";

    return Regex.Match(stringToTest, @"^[" + charSet + @"]+$").Success;
}

Usage:

// English
string test = "AASD121asf";

// Greek
//string test = "Ϡϛβ123";

// Bengali
//string test = "শর৩৮";

// Hindi
//string test = @"क़लम३७ख़";

bool isAlphaNum = test.IsAlphaNumeric();

Peter Mortensen's user avatar

answered Jan 11, 2012 at 0:52

Shantanu's user avatar

ShantanuShantanu

1011 silver badge3 bronze badges

1

The following regex matches alphanumeric characters and underscore:

^[a-zA-Z0-9_]+$

For example, in Perl:

#!/usr/bin/perl -w

my $arg1 = $ARGV[0];

# Check that the string contains *only* one or more alphanumeric chars or underscores
if ($arg1 !~ /^[a-zA-Z0-9_]+$/) {
  print "Failed.n";
} else {
    print "Success.n";
}

Peter Mortensen's user avatar

answered Dec 3, 2008 at 4:31

Jay's user avatar

JayJay

41.6k14 gold badges65 silver badges83 bronze badges

8

This should work in most of the cases.

/^[d]*[a-z_][a-zd_]*$/gi

And by most I mean,

abcd       True
abcd12     True
ab12cd     True
12abcd     True

1234       False

Explanation

  1. ^ ... $ – match the pattern starting and ending with
  2. [d]* – match zero or more digits
  3. [a-z_] – match an alphabet or underscore
  4. [a-zd_]* – match an alphabet or digit or underscore
  5. /gi – match globally across the string and case-insensitive

Peter Mortensen's user avatar

answered Dec 24, 2019 at 6:06

Chinmaya Pati's user avatar

3

For those of you looking for unicode alphanumeric matching, you might want to do something like:

^[p{L} p{Nd}_]+$

Further reading is at Unicode Regular Expressions (Unicode Consortium) and at Unicode Regular Expressions (Regular-Expressions.info).

Peter Mortensen's user avatar

answered Apr 3, 2012 at 14:57

Agustin's user avatar

AgustinAgustin

1,25413 silver badges10 bronze badges

1

For me there was an issue in that I want to distinguish between alpha, numeric and alpha numeric, so to ensure an alphanumeric string contains at least one alpha and at least one numeric, I used :

^([a-zA-Z_]{1,}d{1,})+|(d{1,}[a-zA-Z_]{1,})+$

Alan Moore's user avatar

Alan Moore

73.5k12 gold badges100 silver badges156 bronze badges

answered Jun 24, 2010 at 9:25

mylesmckeown's user avatar

1

Here is the regex for what you want with a quantifier to specify at least 1 character and no more than 255 characters

[^a-zA-Z0-9 _]{1,255}

barbsan's user avatar

barbsan

3,40811 gold badges21 silver badges28 bronze badges

answered Dec 3, 2008 at 4:44

mson's user avatar

msonmson

7,7526 gold badges40 silver badges70 bronze badges

0

I believe you are not taking Latin and Unicode characters in your matches.

For example, if you need to take “ã” or “ü” chars, the use of “w” won’t work.

You can, alternatively, use this approach:

^[A-ZÀ-Ýa-zà-ý0-9_]+$

Peter Mortensen's user avatar

answered Feb 8, 2019 at 14:08

Marcio Martins's user avatar

^w*$ will work for the below combinations:

1
123
1av
pRo
av1

Peter Mortensen's user avatar

answered Nov 14, 2017 at 15:50

Mukund Thakkar's user avatar

1

For Java, only case insensitive alphanumeric and underscore are allowed.

  • ^ Matches the string starting with any characters

  • [a-zA-Z0-9_]+ Matches alpha-numeric character and underscore.

  • $ Matches the string ending with zero or more characters.

      public class RegExTest {
          public static void main(String[] args) {
              System.out.println("_C#".matches("^[a-zA-Z0-9_]+$"));
          }
      }
    

Peter Mortensen's user avatar

answered Mar 6, 2021 at 12:42

Sharad's user avatar

SharadSharad

3603 silver badges7 bronze badges

To check the entire string and not allow empty strings, try

^[A-Za-z0-9_]+$

answered Dec 3, 2008 at 4:33

David Norman's user avatar

David NormanDavid Norman

19.3k12 gold badges63 silver badges54 bronze badges

0

This works for me. You can try:

[\p{Alnum}_]

Peter Mortensen's user avatar

answered May 20, 2015 at 13:02

Saurabh's user avatar

SaurabhSaurabh

7,4374 gold badges45 silver badges46 bronze badges

1

Required Format

Allow these three:

  1. 0142171547295
  2. 014-2171547295
  3. 123abc

Don’t allow other formats:

validatePnrAndTicketNumber(){
    let alphaNumericRegex=/^[a-zA-Z0-9]*$/;
    let numericRegex=/^[0-9]*$/;
    let numericdashRegex=/^(([1-9]{3})-?([0-9]{10}))$/;
   this.currBookingRefValue = this.requestForm.controls["bookingReference"].value;
   if(this.currBookingRefValue.length == 14 && this.currBookingRefValue.match(numericdashRegex)){
     this.requestForm.controls["bookingReference"].setErrors({'pattern': false});
   }else if(this.currBookingRefValue.length ==6 && this.currBookingRefValue.match(alphaNumericRegex)){
    this.requestForm.controls["bookingReference"].setErrors({'pattern': false});
   }else if(this.currBookingRefValue.length ==13 && this.currBookingRefValue.match(numericRegex) ){
    this.requestForm.controls["bookingReference"].setErrors({'pattern': false});
   }else{
    this.requestForm.controls["bookingReference"].setErrors({'pattern': true});
   }
}
<input name="booking_reference" type="text" [class.input-not-empty]="bookingRef.value"
    class="glyph-input form-control floating-label-input" id="bookings_bookingReference"
    value="" maxlength="14" aria-required="true" role="textbox" #bookingRef
    formControlName="bookingReference" (focus)="resetMessageField()" (blur)="validatePnrAndTicketNumber()"/>

Peter Mortensen's user avatar

answered Oct 13, 2020 at 10:46

uma mahesh's user avatar

uma maheshuma mahesh

1131 silver badge4 bronze badges

  English Русский Deutsch Български Français Español

Вступление¶

Регулярные выражения – удобный способ описывать шаблоны текстов.

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

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

Символы¶

Простые совпадения¶

Серия символов соответствует этой серии символов во входной строке.

RegEx Находит
foobar foobar

Непечатные символы (escape-коды)¶

Для представления непечатаемого символа в регулярном выражении используется x с шестнадцатеричным кодом. Если код длиннее 2 цифр (более U+00FF), то он обрамляется в фигурные скобки.

RegEx Находит
xAB символ с 2-значным шестнадцатеричным кодом AB
x{AB20} символ с 1-4 значным шестнадцатеричным кодом AB20
foox20bar foo bar (обратите внимание на пробел в середине)

Существует ряд предопределенных escape-кодов для непечатных символов, как в языке C:

RegEx Находит
t tab (HT/TAB), тоже что x09
n символ новой строки (LF), то же что x0a
r возврат каретки (CR), тоже что x0d
f form feed (FF), то же что x0c
a звонок (BEL), тоже что x07
e escape (ESC), то же что x1b
cAcZ

chr(0) по chr(25).

Например cI соответствует табуляции.

Также поддерживаются буквы в нижнем регистре «a»…»z».

Эскейпинг¶

Для представления спецсимволов (.+*?|()[]{}^$), перед ними надо поставить . Чтобы вставить сам обратный слэш его надо удвоить.

RegEx Находит
^FooBarPtr ^FooBarPtr здесь ^ не означает начало строки
[a] [a] это не класс символов

Классы символов¶

Пользовательские классы¶

Символьный класс – это список символов внутри []. Класс соответствует любому одному символу, указанному в этом классе.

RegEx Находит
foob[aeiou]r foobar, foober и т. д., но не foobbr, foobcr и т. д.

Вы можете инвертировать класс – если первый символ после [ является ^, то класс соответствует любому символу, кроме символов, перечисленных в классе.

RegEx Находит
foob[^aeiou]r foobbr, foobcr и т. д., но не foobar, foober и т. д.

Внутри списка символ - используется для указания диапазона, так что a-z представляет все символы между a и z включительно.

Если вы хотите, чтобы - сам был членом класса, поместите его в начало или конец списка или предварите его обратной косой чертой (escape).

Если вы хотите буквально использовать символ ] поместите его в начало списка или escape обратной косой чертой.

RegEx Находит
[-az] a, z и -
[az-] a, z и -
[А-z] a, z и -
[a-z] символы от a до z
[n-x0D] символы от #10 до #13

Разделители¶

Разделители строк¶

Метасимвол Находит
. любой символ в строке, может включать разделители строк
^ совпадение нулевой длины в начале строки
$ совпадение нулевой длины в конце строки
A совпадение нулевой длины в начале строки
z совпадение нулевой длины в конце строки
Z похож на z но совпадает перед разделителем строки, а не сразу после него, как z

Примеры:

RegEx Находит
^foobar foobar только если он находится в начале строки
foobar$ foobar, только если он в конце строки
^foobar$ foobar только если это единственная строка в строке
foob.r foobar, foobbr, foob1r и так далее

Метасимвол ^ совпадает с точкой начала строки (нулевой длины). $ – в конце строки. Если включен modifier /m , они совпадают с началами или концами строк внутри текста.

Обратите внимание, что в последовательности x0Dx0A нет пустой строки.

Примечание

TRegExpr

Если вы используете Unicode версию, то ^/$ также соответствует x2028, x2029, x0B, x0C или x85.

Метасимвол A совпадает с точкой нулевой длины в начале строки, z – в конце (после символов завершения строки). Модификатор modifier /m на них не влияет. Z тоже самое что z но совпадает с точкой перед символами завершения строки (LF and CR LF).

Метасимвол . по умолчанию соответствует любому символу, но если вы выключите modifier /s, то . не будет совпадать с разделителями строк внутри строки.

Обратите внимание, что выражение ^.*$ не соответствует точке между x0Dx0A, потому что это неразрывный разделитель строк. Но оно соответствует пустой строке в последовательности x0Ax0D, поэтому из-за неправильного порядка кодов он не воспринимается как разделитель строк и считается просто двумя символами.

Примечание

TRegExpr

Многострочная обработка может быть настроена с помощью свойств LineSeparators и LinePairedSeparator.

Таким образом, вы можете использовать разделители стиля Unix n или стиль DOS / Windows rn или смешивать их вместе (как описано выше по умолчанию).

Если вы предпочитаете математически правильное описание, вы можете найти его на сайте www.unicode.org.

Разделители слов¶

RegEx Находит
b разделитель слов
B разделитель с не-словом

Граница слова b – это точка между двумя символами, у которой w с одной стороны от нее и W с другой стороны (в любом порядке).

Повторы¶

Повтор¶

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

RegEx Находит
{n} ровно n раз
{n,} по крайней мере n раз
{n,m} по крайней мере n, но не более чем m раз
* ноль или более, аналогично {0,}
+ один или несколько, похожие на {1,}
? ноль или единица, похожая на {0,1}

То есть цифры в фигурных скобках {n,m} определяются минимальное n и максимальное m количество повторов (совпадений во входном тексте).

{n} эквивалентно {n,n} и означает точно n раз. {n,} совпадает n или более раз.

Теоретически значение n и m не ограничены (можно использовать максимальное значение для 32-х битного числа).

RegEx Находит
foob.*r foobar, foobalkjdflkj9r и foobr
foob.+r foobar, foobalkjdflkj9r, но не foobr
foob.?r foobar, foobbr и foobr, но не foobalkj9r
fooba{2}r foobaar
fooba{2}r foobaar, foobaaar, foobaaaar и т. д.
fooba{2,3}r foobaar, или foobaaar, но не foobaaaar
(foobar){8,10} 8, 9 или 10 экземпляров foobar (() это Группа)

Жадность¶

Повторы в жадном режиме захватывают как можно больше из входного текста, в не жадном режиме – как можно меньше.

По умолчанию все повторы являются жадными. Используйте ? Чтобы сделать любой повтор не жадным.

Для строки abbbbc:

RegEx Находит
b+ bbbb
Ь+? b
b*? пустую строку
b{2,3}? bb
b{2,3} bbb

Вы можете переключить все повторы в не жадный режим (modifier /g, ниже мы используем in-line модификатор change).

RegEx Находит
(?-g)Ь+ b

Сверхжадные повторы (Possessive Quantifier)¶

Синтаксис: a++, a*+, a?+, a{2,4}+. В настоящее время реализован только для простых групп и не будет работать для сложны, как например (foo|bar){3,5}+.

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

Альтернативы¶

Выражения в списке альтернатив разделяются |.

Таким образом, fee|fie|foe будет соответствовать любому из fee, fie или foe (также как и f(e|i|o)e).

Первое выражение включает в себя все от последнего разделителя шаблона ((, [ или начало шаблона) до первого |, а последнее выражение содержит все от последнего | к следующему разделителю шаблона.

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

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

Например, регулярное выражение foo|foot в строке barefoot будет соответствовать foo – первое же совпадение.

Также помните, что | в квадратных скобках воспринимается просто как символ, поэтому, если вы напишите [fee|fie|foe], это тоже самое что [feio|].

RegEx Находит
foo(bar|foo) foobar или foofoo

Группы (подвыражения)¶

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

Подвыражения нумеруются слева направо по открывающим их скобкам (включая вложенные группы (подвыражения). У первой группы номер 1. У выражения в целом – 0.

Например, для входной строки foobar регулярное выражение (foo(bar)) найдет:

Группы (подвыражения) значение
0 foobar
1 foobar
2 bar

Ссылки на группы (Backreferences)¶

Метасимволы от 1 до 9 интерпретируются как ссылки на группы. Они соответствуют ранее найденной группе с соответствующим индексом..

RegEx Находит
(.)1+ aaaa и cc
(.+)1+ также abab и 123123

(['"]?)(d+)1 соответствует "13" (в двойных кавычках) или '4' (в одинарных кавычках) или 77 (без кавычек) и т. д.

Именованные группы (подвыражения) и ссылки на них¶

Чтобы присвоить имя группе используйте (?P<name>expr) или (?'name'expr).

Имя группы должно начинаться с буквы или _, далее следуют буквы, цифры или _. Именованные и не именованные группы имеют общую нумерацию от 1 до 9.

Чтобы сослаться на именованную группу используйте (?P=name). Или, как и для не именованных, цифры от 1 до 9.

RegEx Находит
(?P<qq>['"])w+(?P=qq) "word" и 'word'

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

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

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

Примечание

TRegExpr

Для изменения модификаторов используйте ModifierStr или соответствующие TRegExpr свойства Модификатор *.

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

i, без учета регистра¶

Регистро-независимые сравнения. Использует установленные в вашей системе языковые настройки, см. также InvertCase.

m, многострочные строки¶

Обрабатывать строку как несколько строк. Таким образом, ^ и $ соответствуют началу или концу любой строки в любом месте строки.

Смотрите также Разделители строк.

s, одиночные строки¶

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

Смотрите также Разделители строк, которые обычно не совпадают.

г, жадность¶

Примечание

Специфичный для TRegExpr модификатор.

Отключив его Off, вы переключите повторитель в не-жадный режим.

Итак, если модификатор /g имеет значение Off, то + работает как +?, * как *? и так далее.

По умолчанию этот модификатор имеет значение Выкл.

x, расширенный синтаксис¶

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

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

Также символ # отделяет комментарии.

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

(
(abc) # комментарий 1
#
(efg) # комментарий 2
)

Это также означает, что если вам нужно вставить пробел или символ # в шаблон (вне класса символов, где они не затрагиваются /x), вам придется либо эскейпить их, либо кодировать, используя шестнадцатеричный код.

г, русские диапазоны¶

Примечание

Специфичный для TRegExpr модификатор.

В русской таблице ASCII символы ё / Ё размещаются отдельно от других.

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

С этим модификатором вместо [а-яА-ЯёЁ] вы можете написать [а-Я], если вам нужны все русские символы.

Когда модификатор включен:

RegEx Находит
а-я символы от а до я и ё
А-Я символы от А до Я и Ё
а-Я все русские символы

Модификатор по умолчанию установлен на Вкл.

Проверки или заглядывания вперед и назад (Assertions)¶

Заглядывание вперед (lookahead assertion) foo(?=bar) совпадает «foo» только перед «bar», при этом сама строка «bar» не войдет в найденный текст.

Отрицательное заглядывание вперед (negative lookahead assertion): foo(?!bar) совпадает «foo» только если после этой строки не следует «bar».

Ретроспективная проверка (lookbehind assertion): (?<=foo)bar совпадает «bar» только после «foo», при этом сама строка «foo» не войдет в найденный текст.

Отрицательное заглядывание назад (negative lookbehind assertion): foo(?!bar) совпадает «bar» только если перед этой строкой нет «foo».

Ограничения:

  • Скобки для заглядываний вперед должны быть в самом начале выражения. Не поддерживаются заглядывания внутри альтернатив (|) или групп.
  • Для заглядывания назад (?<!foo)bar, выражение «foo» должно быть фиксированной длины. Допустимы повторы только с фиксированным числом {n} или {n,n}. Разрешено использование классов символов и точки. Не разрешены группы и альтернативы.
  • Для остальных трех типов заглядываний, выражение в скобках может быть сколь угодно сложным.

Не захватываемые группы (подвыражения)¶

Синтаксис: (?:subexpression).

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

RegEx Находит
(https?|ftp)://([^/rn]+) в https://sorokin.engineer захватит подвыражения https и sorokin.engineer
(?:https?|ftp)://([^/rn]+) в https://sorokin.engineer захватит только sorokin.engineer

Атомарные группы¶

Синтаксис: (?>expr|expr|...).

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

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

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

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

И если оно находится внутри подвыражения, оно будет влиять только на это подвыражение, а именно на ту часть подвыражения, которая следует за оператором. Таким образом, в ((?i)Saint)-Petersburg это влияет только на подвыражение ((?i)Saint), поэтому оно будет соответствовать saint-Petersburg, но не saint-petersburg.

RegEx Находит
(?i)Saint-Petersburg Saint-petersburg и Saint-Petersburg
(?i)Saint-(?-i)Petersburg Saint-Petersburg, но не Saint-petersburg
(?i)(Saint-)?Petersburg Saint-petersburg и saint-petersburg
((?i)Saint-)?Petersburg saint-Petersburg, но не saint-petersburg

Рекурсия¶

Синтаксис (?R), синоним (?0).

Выражение a(?R)?z совпадает с одним или более символом «a» за которым следует точно такое же число символов «z».

Основное назначение рекурсии – сбалансировать обрамление вложенного текста. Общий вид b(?:m|(?R))*e где «b» это то что начинает обрамляемый текст, «m» это собственно текст, и «e» это то, что завершает обрамление.

Если же обрамляемый текст также может встречаться без обрамления то выражение будет b(?R)*e|m.

Вызовы подвыражений¶

Нумерованные группы (подвыражения) обозначают (?1)(?90) (максимальное число групп определяется константой в TRegExpr).

Синтаксис для именованных групп : (?P>name). Поддерживается также Perl вариант синтаксиса: (?&name).

Это похоже на рекурсию, но повторяет только указанную группу (подвыражение).

Unicode категории (category)¶

В стандарте Unicode есть именованные категории символов (Unicode category). Категория обозначается одной буквой, и еще одна добавляется, чтобы указать подкатегорию. Например «L» это буква в любом регистре, «Lu» – буквы в верхнем регистре, «Ll» – в нижнем.

  • Cc – Control
  • Cf – Формат
  • Co – Частное использование
  • Cs – Заменитель (Surrrogate)
  • Ll – Буква нижнего регистра
  • Lm – Буква-модификатор
  • Lo – Прочие буквы
  • Lt – Titlecase Letter
  • Lu – Буква в верхнем регистре
  • Mc – Разделитель
  • Me – Закрывающий знак (Enclosing Mark)
  • Mn – Несамостоятельный символ, как умляут над буквой (Nonspacing Mark)
  • Nd – Десятичная цифра
  • Nl – Буквенная цифра – например, китайская, римская, руническая и т.д. (Letter Number)
  • No – Другие цифры
  • Pc – Connector Punctuation
  • Pd – Dash Punctuation
  • Pe – Close Punctuation
  • Pf – Final Punctuation
  • Pi – Initial Punctuation
  • Po – Other Punctuation
  • Ps – Open Punctuation
  • Sc – Currency Symbol
  • Sk – Modifier Symbol
  • Sm – Математический символ
  • So – Прочие символы
  • Zl – Разделитель строк
  • Zp – Разделитель параграфов
  • Zs – Space Separator

Meta-character p denotes one Unicode char of specified category. Syntax: pL and p{L} for 1-letter name, p{Lu} for 2-letter names.

Метасимвол P это символ не из Unicode категории (category).

These meta-characters are supported within character classes too.

#статьи

  • 5 окт 2022

  • 0

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

Иллюстрация: Оля Ежак для SKillbox Media

Иван Стуков

Журналист, изучает Python. Любит разбираться в мелочах, общаться с людьми и понимать их.

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

Содержание:

  • Что такое регулярные выражения
  • Синтаксис регулярок
  • Как ведётся поиск
  • Квантификаторы и логическое ИЛИ при группировке
  • Регулярные выражения в Python: модуль re и Match-объекты
  • Жадный и ленивый пропуск
  • Примеры и задачи

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

Но тут вас поджидает препятствие: все даты разбросаны по нескольким главам учебника по десятку страниц каждая. Читать полкниги в поисках нужных вам крупиц информации — такое себе удовольствие. Тем более когда каждая минута на счету.

К счастью, вы — человек неглупый (не зря же пошли в IT), тренированный и быстро соображающий. Поэтому моментально замечаете основные закономерности:

  • даты обозначаются цифрами: арабскими, если это год и месяц, и римскими, если век;
  • учебник — по истории позднего Средневековья и Нового времени, поэтому все даты, написанные арабскими цифрами, — четырёхсимвольные;
  • после римских цифр всегда идёт слово «век».

Теперь у вас есть шаблон нужной информации. Остаётся лишь пролистать страницу за страницей и записать даты в смартфон (или себе на подкорку). Вуаля: пятёрка за четверть у вас в дневнике, а премия от родителей за отличную учёбу — в кармане.

По такому же принципу работают и регулярные выражения: они ведут поиск фрагментов текста по определённому шаблону. Если фрагмент совпадает с шаблоном — с ним можно работать.

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

(?:d{4})|(?:[IVX]+ век)

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

С помощью regex можно искать как вполне конкретные выражения (например, слово «век» — последовательность букв «в», «е» и «к»), так и что-то более общее (например, любую букву или цифру).

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

Символ Что означает Пример использования шаблона Пример вывода
. Любой символ, кроме новой строки (n) H.llo, .orld

20.. год

Hello, world; Hallo, 2orld

2022 год, 2010 год

[…] Любой символ из указанных в скобках. Символы можно задавать как перечислением, так и указывая диапазон через дефис [abc123]

[A-Z]

[A-Za-z0-9]

[А-ЯЁа-яё]

а; 1

B; T

A; s; 1

А; ё

[^…] Любой символ, кроме указанных в скобках [^A-Za-z] з, 4
^ Начало строки ^Добрый день, 0
$ Конец строки До свидания!$ 0
| Логическое ИЛИ. Регулярное выражение будет искать один из нескольких вариантов [0-9]|[IVXLCDM] — регулярное выражение будет находить совпадение, если цифра является либо арабской, либо римской 5; V
Экранирование. Помогает регулярным выражениям ориентироваться, является ли следующий за символ обычным или специальным AdwZ — экранирование превращает буквы алфавита в спецсимволы.

[.] — экранирование превращает спецсимволы в обычные

0

Важное замечание 1. Регулярные выражения зависимы от регистра, то есть «А» и «а» при поиске будут считаться разными символами.

Важное замечание 2. Буквы «Ё» и «ё» не входят в диапазон «А — Я» и «а — я». Так что, задавая русский алфавит, их нужно выписывать отдельно.

На экранировании остановимся подробнее. По умолчанию символы .^$*+? {}[]|() являются спецсимволами — то есть они выполняют определённые функции. Чтобы сделать спецсимволы обычными, их нужно экранировать .

Таким образом, . будет обозначать любой символ, а . — знак точки. Чтобы написать обратный слеш, его тоже нужно экранировать, то есть в регулярных выражениях он будет выглядеть так: \.

Обратная ситуация с некоторыми алфавитными символами. По умолчанию они считаются просто буквами, но при экранировании начинают играть роль спецсимволов.

Символ Что означает
d Любая цифра. То же самое, что [0-9]
D Любой символ, кроме цифры. То же самое, что [^0-9]
w Любая буква, цифра и нижнее подчёркивание
W Любой символ, кроме буквы, цифры и нижнего подчёркивания
s Любой пробельный символ (пробел, новая строка, табуляция, возврат каретки и тому подобное)
S Любой символ, кроме пробельного
A Начало строки. То же самое, что ^
Z Конец строки. То же самое, что $
b Начало или конец слова
B Середина слова
n, t, r Стандартные строковые обозначения: новая строка, табуляция, возврат каретки

Важное замечание. A, Z, b и B указывают не на конкретный символ, а на положение других символов относительно друг друга. Можно сказать, что они указывают на пространство между символами.

Например, регулярное выражение b[А-ЯЁаяё]b будет искать только те буквы, которые отделены друг от друга пробелами или знаками препинания.

Часто при записи регулярного выражения какая-то часть шаблона должна повторяться определённое количество раз. Число вхождений в синтаксисе regex задают с помощью квантификаторов. Они всегда помещаются после той части шаблона, которую нужно повторить.

Символ Что означает Примеры шаблона Примеры вывода
{} Указывает количество вхождений, можно задавать единичным числом или диапазоном d{4} — цифра, четыре подряд

d{1,4} — цифра, от одного до четырёх раз подряд

d{2,} — цифра, от двух раз подряд

d{,4} — цифра, от 0 до 4 раз подряд

1243, 1876

1, 12, 176, 1589

22, 456, 988888

5, 15, 987, 1234

? От нуля до одного вхождения. То же самое, что {0,1} d? 0
* От нуля вхождений. То же самое, что {0,} d* 0
+ От одного вхождения. То же самое, что {1,} d+ 0

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

(?:d{4})|(?:[IVX]+ век)

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

  • d{4}цифра, четыре подряд
  • | — логическое ИЛИ
  • [IVX]+ вексимвол I, V или X, одно или более вхождений, пробел, слово «век»

Попрактиковаться в составлении регулярных выражений можно на сайте regex101.com. А мы разберём основные приёмы их использования и решим несколько задач.

Уточним ещё несколько терминов regex.

Регулярные выражения — это инструмент для работы со строками, которые и являются основной их единицей.

Строка представляет собой как само регулярное выражение, так и текст, по которому ведётся поиск.

Найденные в тексте совпадения с шаблоном называются подстроками. Например, у нас есть регулярное выражение м. (буква «м», затем любой символ) и текст «Мама мыла раму». Применяя регулярное выражение к тексту, мы найдём подстроки «ма», «мы» и «му». Подстроку «Ма» наше выражение пропустит из-за разницы в регистре.

Есть и более мелкая единица, чем подстрока, — группа. Она представляет собой часть подстроки, которую мы попросили выделить специально. Группы выделяются круглыми скобками (…).

Возьмём ту же строку «Мама мыла раму» и применим к ней следующее регулярное выражение:

(w)(w{3})

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

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

Подстрока Группа 1 Группа 2
Мама М ама
мыла м ыла
раму р аму

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

Группам можно давать имена с помощью такой формы: (? P<name>…)

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

?P<first_letter>w)(?P<rest_letters>w{3})

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

w{4}

Немного изменим текст, по которому ищем совпадения: «Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке».

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

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

Подсказка, если не можете решить задачу

Используйте символ b.

Важное замечание. При написании regex нужно помнить, что они ищут только непересекающиеся подстроки. Под шаблон w{4} в слове «работает» подходят не только подстроки «рабо» и «тает», но и «абот», «бота», «отае». Их регулярное выражение не находит, потому что тогда бы эти подстроки пересеклись с другими — а в regex так нельзя.

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

(?:d{4})|(?:[IVX]+ век)

С помощью скобок мы сказали: выдайте совпадение, если в тексте присутствует хотя бы один из двух вариантов — либо год, либо век.

Важное замечание.? : в начале группы означает, что мы просим regex не запоминать эту группу. Если все группы открываются символами? :, то регулярные выражения вернут только подстроку и ни одной группы.

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

Также к группам удобно применять квантификаторы. Например, имена многих дроидов в «Звёздных войнах» построены по принципу: буква — цифра — буква — цифра.

Вот так это выглядит без групп:

[A-Z]d[A-Z]d

И вот так с ними:

(?:[A-Z]d){2}

Особенно полезно использовать незапоминаемые группы со сложными шаблонами.

Чтобы работать с регулярными выражениями в Python, необходимо импортировать модуль re:

import re

Это даёт доступ к нескольким функциям. Вот их краткое описание.

Функция Что делает Если находит совпадение Если не находит совпадение
re.match (pattern, string) Ищет pattern в начале строки string Возвращает Match-объект Возвращает None
re.search (pattern, string) Ищет pattern по всей строке string Возвращает Match-объект с первым совпадением, остальные не находит Возвращает None
re.finditer (pattern, string) Ищет pattern по всей строке string Возвращает итератор, содержащий Match-объекты для каждого найденного совпадения Возвращает пустой итератор
re.findall (pattern, string) Ищет pattern по всей строке string Возвращает список со всеми найденными совпадениями Возвращает None
re.split (pattern, string, [maxsplit=0]) Разделяет строку string по подстрокам, соответствующим pattern Возвращает список строк, на которые разделила исходную строку Возвращает список строк, единственный элемент которого — неразделённая исходная строка
re.sub (pattern, repl, string) Заменяет в строке string все pattern на repl Возвращает строку в изменённом виде Возвращает строку в исходном виде
re.compile (pattern) Собирает регулярное выражение в объект для будущего использования в других re-функциях Ничего не ищет, всегда возвращает Pattern-объект 0

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

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

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

r'...'

Перечислим самые популярные из них.

Находит совпадение только в том случае, если соответствующая шаблону подстрока находится в начале строки, по которой ведётся поиск:

print (re.match (r'Мама', 'Мама мыла раму'))
>>> <re.Match object; span=(0, 4), match='Мама'>

print (re.match (r'мыла', 'Мама мыла раму'))
>>> None

Как видим, поиск по шаблону «Мама» нашёл совпадение и вернул Match-объект. Слово же «мыла», хотя и есть в строке, находится не в начале. Поэтому регулярное выражение ничего не находит и возвращается None.

Ищет совпадения по всему тексту:

print (re.search (r'Мама', 'Мама мыла раму'))
>>> <re.Match object; span=(0, 4), match='Мама'>

print (re.search (r'мыла', 'Мама мыла раму'))
>>> <re.Match object; span=(5, 9), match='мыла'>

При этом re.search возвращает только первое совпадение, даже если в строке, по которой ведётся поиск, их больше. Проверим это:

print (re.search (r'мыла', 'Мама мыла раму, а потом ещё раз мыла, потому что не домыла'))
>>> <re.Match object; span=(5, 9), match='мыла'>

Возвращает итератор с объектами, к которым можно обратиться через цикл:

results = re.finditer (r'мыла', 'Мама мыла раму, а потом ещё раз мыла, потому что не домыла')
print (results)
>>> <callable_iterator object at 0x000001C4CDE446D0>

for match in results:
    print (match)
>>> <re.Match object; span=(5, 9), match='мыла'>
>>> <re.Match object; span=(32, 36), match='мыла'>
>>> <re.Match object; span=(54, 58), match='мыла'>

Эта функция очень полезна, если вы хотите получить Match-объект для каждого совпадения.

В Match-объектах хранится много всего интересного. Посмотрим внимательнее на объект с подстрокой «Мама», который нашла функция re.match:

<re.Match object; span=(0, 4), match='Мама'>

span — это индекс начала и конца найденной подстроки в тексте, по которому мы искали совпадение. Обратите внимание, что второй индекс не включается в подстроку.

match — это собственно найденная подстрока. Если подстрока длинная, то она будет отображаться не целиком.

Это, конечно же, не всё, что можно получить от Match-объекта. Рассмотрим ещё несколько методов.

Возвращает найденную подстроку, если ему не передавать аргумент или передать аргумент 0. То же самое делает обращение к объекту по индексу 0:

match = re.match (r'Мама', 'Мама мыла раму')

print (match.group())
>>> Мама

print (match.group(0))
>>> Мама

print (match[0])
>>> Мама

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

match = re.match (r'(М)(ама)', 'Мама мыла раму')

print (match.group(1))
print (match.group(2))
>>> М
>>> ама

print (match[1])
print (match[2])
>>> М
>>> ама

#Методом group также можно получить кортеж из нужных групп.
print (match.group(1,2))
>>> ('М', 'ама')

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

match = re.match (r'(?P<first_letter>М)(?P<rest_letters>ама)', 'Мама мыла раму')

print (match.group('first_letter'))
print (match.group('rest_letters'))
>>> М
>>> ама

Если одна и та же группа соответствует шаблону несколько раз, то в группу запишется только последнее совпадение:

#Помещаем в группу один буквенный символ, при этом шаблон представляет собой четыре таких символа.
match = re.match (r'(w){4}', 'Мама мыла раму')

print (match.group(0))
>>> Мама
print (match.group(1))
>>> а

Возвращает кортеж с группами:

match = re.match (r'(М)(ама)', 'Мама мыла раму')

print (match.groups())
>>> ('М', 'ама')

Возвращает кортеж с индексом начала и конца подстроки в исходном тексте. Если мы хотим получить только первый индекс, можно использовать метод start, только последний — end:

match = re.search (r'мыла', 'Мама мыла раму')

print (match.span())
>>> (5, 9)
print (match.start())
>>> 5
print (match.end())
>>> 9

Возвращает просто список совпадений. Никаких Match-объектов, к которым нужно дополнительно обращаться:

#В этом примере в качестве регулярного выражения мы используем правильный ответ на задание 0.
match_list = re.findall (r'bw{4}b', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.')
print (match_list)
>>> ['Мама', 'мыла', 'раму', 'папа']

Функция ведёт себя по-другому, если в регулярном выражении есть деление на группы. Тогда функция возвращает список кортежей с группами:

match_list = re.findall (r'b(w{1})(w{3})b', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.')
print (match_list)
>>> [('М', 'ама'), ('м', 'ыла'), ('р', 'аму'), ('п', 'апа')]

Аналог метода str.split. Делит исходную строку по шаблону, а сам шаблон исключает из результата:

#Поделим строку по запятой и пробелу после неё.
split_string = re.split (r', ', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.')
print (split_string)
>>> ['Мама мыла раму', 'а папа был на пилораме', 'потому что работает на лесопилке.']

re.split также имеет дополнительный аргумент maxsplit — это максимальное количество частей, на которые функция может поделить строку. По умолчанию maxsplit равен нулю, то есть не устанавливает никаких ограничений:

#Приравняем аргумент maxsplit к единице.
split_string = re.split (r', ', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.', maxsplit=1)
print (split_string)
>>> ['Мама мыла раму', 'а папа был на пилораме, потому что работает на лесопилке.']

Если в re.split мы указываем группы, то они попадают в список строк в качестве отдельных элементов. Для наглядности поделим исходную строку на слог «па»:

#Помещаем буквы «п» и «а» в одну группу.
split_string = re.split (r'(па)', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.')
print (split_string)
>>> ['Мама мыла раму, а ', 'па', '', 'па', ' был на пилораме, потому что работает на лесопилке.']

#Помещаем буквы «п» и «а» в разные группы.
split_string = re.split (r'(п)(а)', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.')
print (split_string)
>>> ['Мама мыла раму, а ', 'п', 'а', '', 'п', 'а', ' был на пилораме, потому что работает на лесопилке.']

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

new_string = re.sub (r'Мама', 'Дочка', 'Мама мыла раму, а папа был на пилораме, потому что работает на лесопилке.')
print (new_string)
>>> Дочка мыла раму, а папа был на пилораме, потому что работает на лесопилке.

Дополнительные возможности у функции появляются при применении групп. В качестве аргумента замены ему можно передать не строку, а ссылку на номер группы в виде n. Тогда он подставит на нужное место соответствующую группу из шаблона. Это очень удобно, когда нужно поменять местами структурные элементы в тексте:

new_string = re.sub (r'(w+) (w+) (w+),', r'2 3 1 –', 'Бендер Остап Ибрагимович, директор ООО "Рога и копыта"')
print (new_string)
>>> Остап Ибрагимович Бендер — директор ООО "Рога и копыта"

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

pattern = re.compile (r'Мама')

print (pattern.search ('Мама мыла раму'))
>>> <re.Match object; span=(0, 4), match='Мама'>

print (pattern.sub ('Дочка', 'Мама мыла раму'))
>>> Дочка мыла раму

Нередко в регулярных выражениях нужно учесть сразу много вариантов и опций, из-за чего их структура усложняется. А regex даже простые и короткие читать нелегко, что уж говорить о длинных.

Чтобы хоть как-то облегчить чтение регулярок, в Python r-строки можно делить точно так же, как и обычные. Возьмём наше выражение для поиска дат по учебнику истории:

re.findall (r'(?:d{4})|(?:[IVX]+ век)', text)

Его же можно написать вот в таком виде:

re.findall (r'(?:d{4})'
            r'|'
            r'(?:[IVX]+ век)', text)

Часто при написании регулярных выражений приходится использовать квантификаторы, охватывающие диапазон значений. Например, d{1,4}. Как регулярные выражения решают, сколько цифр им захватить, одну или четыре? Это определяется пропуском квантификаторов.

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

В некоторых случаях это может стать проблемой. Например, возьмём часть оглавления поэмы Венедикта Ерофеева «Москва — Петушки», записанную в одну строку:

Фрязево — 61-й километр……….64 61-й километр — 65-й километр…68 65-й километр — Павлово-Посад…71 Павлово-Посад — Назарьево……..73 Назарьево — Дрезна……………77 Дрезна — 85-й километр………..80

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

  • Каждый пункт начинается с буквы или цифры (для этого используем шаблон w).
  • Он может содержать внутри себя любой набор символов: буквы, цифры, знаки препинания (для этого используем шаблон .+).
  • Он заканчивается на точку, после которой следует от одной до трёх цифр (для этого используем шаблон .d{1,3}).

Посмотрим в конструкторе, как работает наше выражение:

Скриншот: Skillbox Media

Что же произошло? Почему найдено только одно совпадение, причем за него посчитали весь текст сразу? Всё дело в жадности квантификатора +, который старается захватить максимально возможное количество подходящих символов.

В итоге шаблон w находит совпадение с буквой «Ф» в начале текста, шаблон .d{1,3} находит совпадение с «.80» в конце текста, а всё, что между ними, покрывается шаблоном .+.

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

Если нет, то функция будет искать следующее совпадение с .. А если да, то . закончит свою работу и следующие символы строки будут сравниваться со следующей частью регулярного выражения: .d{1,3}.

Чтобы объявить квантификатор ленивым, после него надо поставить символ ?. Сделаем ленивым квантификатор + в нашем регулярном выражении для поиска строк в оглавлении:

Скриншот: Skillbox Media

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

content = 'Фрязево — 61-й километр..........64 61-й километр — 65-й километр....68 65-й километр — Павлово-Посад....71 Павлово-Посад — Назарьево........73 Назарьево — Дрезна...............77 Дрезна — 85-й километр...........80'

strings = re.findall (r'w.+?.d{1,3}', content)
for string in strings:
    print (string)

#Результат на экране.
>>> Фрязево — 61-й километр..........64
>>> 61-й километр — 65-й километр....68
>>> 65-й километр — Павлово-Посад....71
>>> Павлово-Посад — Назарьево........73
>>> Назарьево — Дрезна...............77
>>> Дрезна — 85-й километр...........80

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

При обнародовании судебных решений из них извлекают персональные данные участников процесса — фамилии, имена и отчества. Каждое слово в Ф. И. О. начинается с заглавной буквы, при этом фамилия может быть двойная.

Напишите программу, которая заменит в тексте Ф. И. О. подсудимого на N.

Подсудимая Эверт-Колокольцева Елизавета Александровна в судебном заседании вину инкриминируемого правонарушения признала в полном объёме и суду показала, что 14 сентября 1876 года, будучи в состоянии алкогольного опьянения от безысходности, в связи с состоянием здоровья позвонила со своего стационарного телефона в полицию, сообщив о том, что у неё в квартире якобы заложена бомба. После чего приехали сотрудники полиции, скорая и пожарные, которым она сообщила, что бомба — это она.

«Подсудимая N в судебном заседании» и далее по тексту.

Подсказка

Используйте незапоминаемую опциональную группу вида (? : …)? , чтобы обозначить вторую часть фамилии после дефиса.

Решение

#Сначала кладём в переменную string текст строки, по которой ведём поиск.
print (re.sub (r'[А-ЯЁ]w*'
          r'(?:-[А-ЯЁ]w*)?'
          r'(?: [А-ЯЁ]w*){2}', 'N', string))

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

Перед названием улицы может быть написано «Улица», «улица», «Ул.» или «ул.», перед номером дома — «дом» или «д.», перед номером квартиры — «квартира» или «кв.». Также номер дома и номер квартиры могут быть разделены дефисом без пробелов.

Дан текст, в нём нужно найти все адреса и вывести их в виде «Пушкина 32-135».

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

Добрый день!

Сегодня на выезды потребуется отправить трёх-четырёх специалистов, остальных держите в офисе. Некоторые заявки пришли на конкретных людей, но можно вызвать и других, смотрите по ситуации, как лучше их отправить, чтобы всех объездить сегодня.

Петрову П. П. попросили выехать по адресам ул. Культуры 78 кв. 6, улица Мира дом 12Б квартира 144. Смирнова С. С. просят подъехать только по адресу: Восьмого Марта 106-19. Без предпочтений по специалистам пришли запросы с адресов: улица Свободы 54 6, Улица Шишкина дом 9 кв. 15, ул. Лермонтова 18 кв. 93.

Все адреса скопированы из заявок, корректность подтверждена.

Культуры 78-6

Мира 12Б-144

Восьмого Марта 106-19

Свободы 54-6

Шишкина 9-15

Лермонтова 18-93

Подсказка

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

Решение

#Сначала кладём в переменную string текст строки, по которой ведём поиск.
pattern = re.compile (r'(?:[Уу]л(?:.|ица) )?'
                      r'((?:[А-ЯЁ]w+)(?: [А-ЯЁ]w+)*)'
                      r' (?:дом |д. )?'
                      r'(d+w?)'
                      r'[ -](?:квартира |кв. )?'
                      r'(d+)')

addresses = pattern.findall (text)
for address in addresses:
    print (f'{address[0]} {address[1]}-{address[2]}')

Структура этого регулярного выражения довольно сложная. Чтобы в нём разобраться, посмотрите на схему. Прямоугольники обозначают обязательные элементы, овалы — опциональные. Развилки символизируют разные варианты, которые допускает наш шаблон. Красным цветом очерчены группы, которые мы запоминаем.

Инфографика: Майя Мальгина для Skillbox Media

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

Даны несколько предложений. Программа должна проверить, встречаются ли в каждом из них слова на одинаковую букву. Если таких нет, она печатает: «Метод Довлатова соблюдён». А если есть: «Вы расстроили Сергея Донатовича».

Важно. Чтобы регулярные выражения не рассматривали заглавные и прописные буквы как разные символы, передайте re-функции дополнительный аргумент flags=re.I или flags=re.IGNORECASE.

Здесь все слова начинаются с разных букв.

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

А здесь совсем интересно: символ «а» однобуквенный.

Метод Довлатова соблюдён

Вы расстроили Сергея Донатовича

Вы расстроили Сергея Донатовича

Подсказка

Чтобы указать на начало слова, используйте символ b.

Чтобы в каждом совпадении regex не старалось захватить максимум, используйте ленивый пропуск.

Чтобы найти повторяющийся символ, используйте ссылку на группу в виде 1.

Решение

#Сначала кладём в переменную string текст строки, по которой ведём поиск.
pattern = r'b(w)w*.*?b1'

match = re.search (pattern, string, flags=re.I)
if match is None:
    print ('Метод Довлатова соблюдён')
else:
    print ('Вы расстроили Сергея Донатовича')

Вернёмся к регулярному выражению, которое ищет даты в учебнике истории: (? :d{4})|(? : [IVX]+ век).

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

Чтобы не получать лишние результаты, обратим внимание на то, как именно могут быть записаны годы. Есть несколько вариантов записи: 1400 год, 1400 г., 1400–1500 годы, 1400–1500 гг., (1400), (1400–1500).

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

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

Началом Реформации принято считать 31 октября 1517 г. — день, когда Мартин Лютер (1483–1546) прибил к дверям виттенбергской Замковой церкви свои «95 тезисов», в которых выступил против злоупотреблений Католической церкви. Реформация охватила практически всю Европу и продолжалась в течение всего XVI века и первой половины XVII века. Одно из самых известных и кровавых событий Реформации — Варфоломеевская ночь во Франции, произошедшая в ночь на 24 августа 1572 года.

Точное число жертв так и не удалось установить достоверно. Погибли по меньшей мере 2000 гугенотов в Париже и 3000 — в провинциях. Герцог де Сюлли, сам едва избежавший смерти во время резни, говорил о 70 000 жертв. Для Парижа единственным точным числом остаётся 1100 погибших во время Варфоломеевской ночи.

Этому событию предшествовали три других, произошедшие в 1570–1572 годах: Сен-Жерменский мирный договор (1570), свадьба гугенота Генриха Наваррского и Маргариты Валуа (1572) и неудавшееся покушение на убийство адмирала Колиньи (1572).

[‘1517 г.’, ‘(1483–1546)’, ‘XVI век’, ‘XVII век’, ‘1572 год’, ‘1570–1572 годах’, ‘(1570)’, ‘(1572)’, ‘(1572)’]

Решение

#Сначала кладём в переменную string текст строки, по которой ведём поиск.
pattern = re.compile (r'(?:(d{4}(?:-d{4})?))'
                      r'|'
                      r'(?:'
                          r'(?:d{4}-)?d{4} '
                          r'(?:'
                              r'(?:год(?:ы|ах|ов)?)'
                              r'|'
                              r'(?:гг?.)'
                          r')'
                      r')'
                      r'|'
                      r'(?:[IVX]+ век)')

print (pattern.findall (string))

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

Инфографика: Майя Мальгина для Skillbox Media

Научитесь: Профессия Python-разработчик
Узнать больше

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