Regular expressions one word

Регулярные выражения (их еще называют 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-канале

RegexBuddy—Better than a regular expression tutorial!

The metacharacter b is an anchor like the caret and the dollar sign. It matches at a position that is called a “word boundary”. This match is zero-length.

There are three different positions that qualify as word boundaries:

  • Before the first character in the string, if the first character is a word character.
  • After the last character in the string, if the last character is a word character.
  • Between two characters in the string, where one is a word character and the other is not a word character.

Simply put: b allows you to perform a “whole words only” search using a regular expression in the form of bwordb. A “word character” is a character that can be used to form words. All characters that are not “word characters” are “non-word characters”.

Exactly which characters are word characters depends on the regex flavor you’re working with. In most flavors, characters that are matched by the short-hand character class w are the characters that are treated as word characters by word boundaries. Java is an exception. Java supports Unicode for b but not for w.

Most flavors, except the ones discussed below, have only one metacharacter that matches both before a word and after a word. This is because any position between characters can never be both at the start and at the end of a word. Using only one operator makes things easier for you.

Since digits are considered to be word characters, b4b can be used to match a 4 that is not part of a larger number. This regex does not match 44 sheets of a4. So saying “b matches before and after an alphanumeric sequence” is more exact than saying “before and after a word”.

B is the negated version of b. B matches at every position where b does not. Effectively, B matches at any position between two word characters as well as at any position between two non-word characters.

Looking Inside The Regex Engine

Let’s see what happens when we apply the regex bisb to the string This island is beautiful. The engine starts with the first token b at the first character T. Since this token is zero-length, the position before the character is inspected. b matches here, because the T is a word character and the character before it is the void before the start of the string. The engine continues with the next token: the literal i. The engine does not advance to the next character in the string, because the previous regex token was zero-length. i does not match T, so the engine retries the first token at the next character position.

b cannot match at the position between the T and the h. It cannot match between the h and the i either, and neither between the i and the s.

The next character in the string is a space. b matches here because the space is not a word character, and the preceding character is. Again, the engine continues with the i which does not match with the space.

Advancing a character and restarting with the first regex token, b matches between the space and the second i in the string. Continuing, the regex engine finds that i matches i and s matches s. Now, the engine tries to match the second b at the position before the l. This fails because this position is between two word characters. The engine reverts to the start of the regex and advances one character to the s in island. Again, the b fails to match and continues to do so until the second space is reached. It matches there, but matching the i fails.

But b matches at the position before the third i in the string. The engine continues, and finds that i matches i and s matches s. The last token in the regex, b, also matches at the position before the third space in the string because the space is not a word character, and the character before it is.

The engine has successfully matched the word is in our string, skipping the two earlier occurrences of the characters i and s. If we had used the regular expression is, it would have matched the is in This.

Tcl Word Boundaries

Word boundaries, as described above, are supported by most regular expression flavors. Notable exceptions are the POSIX and XML Schema flavors, which don’t support word boundaries at all. Tcl uses a different syntax.

In Tcl, b matches a backspace character, just like x08 in most regex flavors (including Tcl’s). B matches a single backslash character in Tcl, just like \ in all other regex flavors (and Tcl too).

Tcl uses the letter “y” instead of the letter “b” to match word boundaries. y matches at any word boundary position, while Y matches at any position that is not a word boundary. These Tcl regex tokens match exactly the same as b and B in Perl-style regex flavors. They don’t discriminate between the start and the end of a word.

Tcl has two more word boundary tokens that do discriminate between the start and end of a word. m matches only at the start of a word. That is, it matches at any position that has a non-word character to the left of it, and a word character to the right of it. It also matches at the start of the string if the first character in the string is a word character. M matches only at the end of a word. It matches at any position that has a word character to the left of it, and a non-word character to the right of it. It also matches at the end of the string if the last character in the string is a word character.

The only regex engine that supports Tcl-style word boundaries (besides Tcl itself) is the JGsoft engine. In PowerGREP and EditPad Pro, b and B are Perl-style word boundaries, while y, Y, m and M are Tcl-style word boundaries.

In most situations, the lack of m and M tokens is not a problem. ywordy finds “whole words only” occurrences of “word” just like mwordM would. Mwordm could never match anywhere, since M never matches at a position followed by a word character, and m never at a position preceded by one. If your regular expression needs to match characters before or after y, you can easily specify in the regex whether these characters should be word characters or non-word characters. If you want to match any word, yw+y gives the same result as m.+M. Using w instead of the dot automatically restricts the first y to the start of a word, and the second y to the end of a word. Note that y.+y would not work. This regex matches each word, and also each sequence of non-word characters between the words in your subject string. That said, if your flavor supports m and M, the regex engine could apply mw+M slightly faster than yw+y, depending on its internal optimizations.

If your regex flavor supports lookahead and lookbehind, you can use (?<!w)(?=w) to emulate Tcl’s m and (?<=w)(?!w) to emulate M. Though quite a bit more verbose, these lookaround constructs match exactly the same as Tcl’s word boundaries.

If your flavor has lookahead but not lookbehind, and also has Perl-style word boundaries, you can use b(?=w) to emulate Tcl’s m and b(?!w) to emulate M. b matches at the start or end of a word, and the lookahead checks if the next character is part of a word or not. If it is we’re at the start of a word. Otherwise, we’re at the end of a word.

GNU Word Boundaries

The GNU extensions to POSIX regular expressions add support for the b and B word boundaries, as described above. GNU also uses its own syntax for start-of-word and end-of-word boundaries. < matches at the start of a word, like Tcl’s m. > matches at the end of a word, like Tcl’s M.

Boost also treats < and > as word boundaries when using the ECMAScript, extended, egrep, or awk grammar.

POSIX Word Boundaries

The POSIX standard defines [[:<:]] as a start-of-word boundary, and [[:>:]] as an end-of-word boundary. Though the syntax is borrowed from POSIX bracket expressions, these tokens are word boundaries that have nothing to do with and cannot be used inside character classes. Tcl and GNU also support POSIX word boundaries. PCRE supports POSIX word boundaries starting with version 8.34. Boost supports them in all its grammars.

Regular expressions are a powerful tool for searching text as well as for searching and replacing text. With the help of regular expressions and suitable text editors such as the TextConverter, the editing of texts can be made much easier. However, in order to be able to work with regular expressions, you need the knowledge of some basics that are summarized in this tutorial.

This article is divided into the following sections to which you can jump directly:

  • What is a regular expression?
  • Basics
  • Repetitions
  • Character Classes
  • Grouping and Backreferences
  • Modifiers
  • Unicode
  • Examples for using regular expressions
  • Regular Expression Service
  • Software for Regular Expressions

What is a regular expression?

The usage of regular expressions corresponds to a search which is more general than a normal search for a simple text. For example, if you are searching for «A» in a text, you can find all occurrences of «A». But what can we do if we want to find any of the uppercase characters? You can search for «A», «B», «C» and so on. Or you can use regular expressions. The short regular Expression [A-Z] is the same as searching for each of the single characters from A to Z. You can do that as precise as you want. You can search for an arbitrary date, an arbitrary e-mail address or whatever you want. How to do that, you can read in this summary.

You can use regular expressions for example in editors for texts or text files like the Text Converter, with which it is possible to delete or replace texts with regular expressions or to use the texts found by regular expressions to use it in another context or position, so that it is possible to change the format of a date or to rewrite each e-mail address to a link. A normal search had to know all possible e-mail addresses for that, what is impossible.

Basics

In this table you can find the most important conventions and characters as well as their meanings. The 15 characters [ ] ( ) { } | ? + — * ^ $ and . are meta characters and have a special meaning within regular expressions, which will be explained in the following sections. If you want to use one of these characters as this character in a regular expression, you can use a in front of the character to escape the character from its meaning as meta character. Regularly, all of the other characters can be used in a regular expression as such.

Regular Expression Meaning and Example
a The regular expression «a» also matches «a». As long as the character is no meta character with another meaning, it can be used in the regular expression directly.

Example: abc defg abcdefgbafcgbde 0123456789
[abc] The square brackets [ and ] can be used to define a character group. The example finds one of the characters a, b or c.

Example: abc defg abcdefgbafcgbde 0123456789
[a-f] The hyphen — can be used to define a range of characters. The example finds one of the characters a, b, c, d, e or f. Also in this example, the regular expression only matches one character.

Example: abc defg abcdefgbafcgbde 0123456789
[0-9] The hyphen can also be used to define a range of numbers. The example finds the numbers 0 to 9.

Example: abc defg abcdefgbafcgbde 0123456789
[A-Z0-9abc] Within a character group, ranges and single characters can be used. In the example, the group consists of the upper case letters A to Z, the digits 0 to 9 as well as the lower case letters a, b and c.

Example: abc defg abcdefgbafcgbde 0123456789 -&
[^a] With the meta character ^ at the beginning of a character group, the character group is negated. That means, the example will match any character but not an a.

Example: abc defg abcdefgbafcgbde 0123456789
^a If the meta character ^ is not included into a character group, it stands for the beginning of a string or a line. The example would match all lines or strings beginning with a.

Example: abc defg abcdefgbafcgbde 0123456789
a$ Like the meta character ^ stands for the beginning of a string or a line, the character $ stands for its end. The example would match all strings or lines ending with an a.

Example: abc defg abcdefgbafcgbde 0123456789a
^abc$ Here the meta characters ^ and $ are used together. This example would match all strings or lines which are equal to «abc».

Example 1: abc

Example 2: abc abc
b Stands for the position at the begin or end of a word.
B Stands for a position which is not at the begin or end of a word.
babcb Finds the single word «abc», but not the string «abc», when it is surrounded by other characters.

Example: abc abcde abc deabcde deabc abc
babc Finds all words beginning with «abc».

Example: abc abcde abc deabcde deabc abc
abcb Finds all words ending with «abc».

Example: abc abcde abc deabcde deabc abc
BabcB Finds all words containing «abc», but not beginning or ending with «abc».

Example: abc abcde abc deabcde deabc abc
abcB Finds all words containing «abc», but not ending with «abc».

Example: abc abcde abc deabcde deabc abc
ABC|abc The character | stands for an alternative. This regex will find «ABC» and «abc».

Example: abcde ABCDE fgabcde FGABCDE
[a-f] If there is a in front of a meta character, the meaning of this meta character is escaped. In this case, the meta character does not represent a range, but is used as own character. So, the example matches the characters a, f and -. In other words, with the character   it is possible to add meta characters to character groups.

Example: abcdefg abcdefgh
1+1=2 This regular expression matches the string 1+1=2. Again, the meta character + is escaped with .

Example: 1+1=2 1+1=2
[-af] Whenever a meta character is within a character group at a position with no meaning, it is used as normal character. The example matches the characters -, a and f.

Example: abcdefg abcdefgh
[af-] The same applies to a — at the end of a group.

Example: abcdefg abcdefgh
ab[cd] In this example, one character from a character group is combined with «ab». So, the example matches the strings «abc» and «abd».

Example: abcdef abdef cdabcd cdabdc
. The dot stands for an arbitray character. Every character will be found with this regular expression. It depends on the modifier, whether line breaks are included.

Example: abc defg abcdefgbafcgbde 0123456789 -&
.ab Matches any string with three characters from which the last two characters are a and b, for example aab, eab, %ab, :ab and so on.

Example: abc dfgabc &ab fgxab
[^a]ab Matches the same strings as the regular expression «.ab» except the string «aab».

Example: aab cab dd dab fg &ab

If you want to test a regular expression introduced on this page, you can use the software Text Converter for this. Just open an arbitrary text file in this tool and click on Search and Replace in the most right column. Here you can activate regular expressions in the search box. After that you can type in a regular expression to test how it works.

Repetitions

To describe situations in which there are repetitions of characters or whole character classes, you can use some of the following meta characters.

Regular Expression Meaning and Example
ab{2} The preceding element has to appear exactly for two times. This example would only match abb. The a is not added with a bracket to a class together with the b, because of that the repeating expression has only affect to the b and not to the a.

Example: a ab abb babb abbb abbbb babbbbbbbbbbd
ab{2,3} The preceding element has to appear at least two times and not more than three times. This regular expression would match abb, abbb, but not ab or abbbb.

Example: a ab abb babb abbb abbbb babbbbbbbbbbd
ab{2,} The preceding element has to appear at least two times. The example would match abb, abbb, abbbb and so on.

Example: a ab abb babb abbb abbbb babbbbbbbbbbd
ab{,3} The preceding element has to appear not more than three times. The example would match abbb and abbbb but not abbbbb.

Example: a ab abb babb abbb abbbb babbbbbbbbbbd
ab? The question mark indicates that the preceding element is optional. That means, the preceding element can appear, but it must not appear. The example would match a and ab. The question mark is the same as {0,1}.

Example: a ab abb babb abbb abbbb babbbbbbbbbbd
ab+ The plus indicates that the preceding element has to appear at least one times, but also more than one time. The example would match ab, abb, abbb and so on but not a. The plus is the same as the experssion {1,}.

Example: a ab abb babb abbb abbbb babbbbbbbbbbd
ab* The character * indicates that the preceding element has to appear zero or more times. It is the same as the expression {0,}.

Example: a ab abb babb abbb abbbb babbbbbbbbbbd
[ab]+ This example finds strings as a, b, ab, ba, abb, ababa and so on. It does not mean, that exactly the same preceding character has to be repeated. It means that characters from the group has to be repeated. If you would like to find repetitions of the same character, you have to use backreferences. The regular expression would be ([ab])1+ and will be explained below.

Example: a ab abb babb abbb abbbb babbbbbbbbbbd
a[bc]+d This expression matches strings like abd, acd, abcd, acbd, abccbd, acbcbbcd and so on.

Example: abcd aad acbd fg abbccbbcd fg abd fg acd
[0-9]{2,3} Also in this example there is no need to have the same numbers repeatet. The expression matches all numbers with two or three digits, hence the numbers from 10 to 999. Strings like 1,2 will not be found by this expression.

Example: 1,4 10 89 ab3a ab42a 234

Character Classes

Behind creating own groups of characters with square brackets, there are also some predefined character classes. With this classes, regular expressions become shorter and clearer.

Regular Expression Meaning and Example
d This expression (Digit) stands for a digit, it is the same as [0-9].

Example: abc defg abcdefgbafcgbde 0123456789 -&
D This expression stands for any character that is not a digit. It is the same as [^0-9] or [^d].

Example: abc defg abcdefgbafcgbde 0123456789 -&
w This expression (Word) stands for a letter, a digit or a underscore. It is the same as [A-Za-z0-9_].

Example: abc defg abcdefgbafcgbde 0123456789 -&
W This expression stands for any character which is no digit, letter or underscore. It is the same as [^w] or [^A-Za-z0-9_].

Example: abc defg abcdefgbafcgbde 0123456789 -&
s This expression stands for whitespace (Space), for example line breaks, tabs, spaces and so on.
S This expression stands for any character which is not a whitespace character, it is the same as [^s].

Grouping and Backreferences

With round brackets, you can group some characters, for example to apply an operator on the whole group. Furthermore, with round brackets you can create back references. That means, the characters found in this brackets will be stored, so that you can re-use them in the same regex or even in the replace regex, when searching and replacing with regular expressions. The examples show some of the possibilities, which you should test in a program like the Text Converter to get a feeling for this expressions.

Regular Expression Meaning and Example
(ab)+ The whole group «ab» is repeated, one ore more repetitions of «ab» will be found.

Example: abcde ababcde ababababa
ab(cd|ef|gh)i Within the brackets, there are some alternatives. This example will match the strings «abcdi», «abefi» and «abghi» but no other strings.

Example: abcdifg abcdefghi abghi
([ab])1+ Here the backreference 1 is used. Each bracket creates such a reference, the 1 corresponds to the first bracket in the expression. The expression means, that the letter found in the group [ab] has to be repeated one ore more times after the group. Hence, this expression matches «aa», «bb», «aaa», «bbb» and so on.

Example: aaaacd efbbbbbbbbghab
([ab])x1x1 The reference can also be used more than one time. This expression matches «axaxa» and «bxbxb».

Example: axaxa axax bxbxb axbxa
([ab])x(c)x1x2 In this expression, two references 1 and 2 are used, which correspond to the first and second bracket. The strings «axcxaxc» and «bxcxbxc» will match this expression.

Example: axcxaxc axax bxcxbxc axbxa
([ab])x(c)x2x2 It is not necessary to use each of the references resulting from brackets in the expression. Here only the second reference is used.

Example: axcxcxc axax bxcxcxc axbxa
(d+.)(d+.) References can not only be used within a single regular expression. In the Text Converter, you can search for a string with a regex and replace this string by using references like $1, $2 and so on. If you type the example in the search field and you replace this by $2$1, the found date will be turned around. Please note, that you have to activate regular expressions for the search and replace boxes under the boxes.

Example: «11.04.» will replaced by «04.11.»
babsb([aoeiu][a-z]+)b With this regular expression you can find all single words «a» followed by another word beginning with a, o, e, i or u. In English, it is not allowed to write an «a» in front of a word beginning with a vowel. You can use the regular expression «an $1» in the Text Converter to correct this error.

Example: «a idea» will be replaced by «an idea»

Modifiers

The behavior of regular expressions can be changed with so called modifiers. If you want to change this modifiers in the Text Converter in general, you can go to the menu «Settings > Settings regarding regular Expressions (RegEx)», where all of the modifiers can be changed. But it is also possible to change modifiers within regular expressions or to apply modifiers only on a part of the regex. How that works, you learn in the second part of this section. The following modifiers can be adjusted in the Text Converter:

  • Modifier i (Case Insensitive): If this modifier is active, it will be searched independent from upper and lower case characters. That means, the regex [a-z] matches either only lower case letters (only a to z — modifier i is not active) or both, lower and upper case letters (a to z as well as A to Z — modifier i is active).
  • Modifier m (Multi Line): If this modifier is active, the whole file will be treated as multiple lines. That means ^ and $ match the beginning and the end of the whole file. If the modifier is not active, ^ and $ will match the beginning and the end of a line.
  • Modifier s (Single Line): Treat a string as a single line. If this modifier is active, the dot . matches all characters including spaces. If this modifier is not active, that will not be the case.
  • Modifier g (Greedy Mode): This modifier changes all of the following operators like + and *. If this modifier is active, all operators will behave normal. If this modifier is not active, the regular expressions will be applied non greedy. That means the + works as +?, the * works as *? and so on.
  • Modifier x (Extended Syntax): If this modifier is active, you can use whitespace (for example spaces) in your regular expressions and add comments (after a # in a line all other characters will not be used for the regular expression). With this, the regular expression will be more readable, but you have to escape all spaces with whenever it is not used in a character group.

If a modifier should only be used for one regular expression or even only for a part of a regex, you can use the following methods to change the modifiers. The modifiers mentioned above are named by their letters, that means the letters i, m, s, g or x have to be used.

Regular Expression Meaning and Example
(?i)[abc] In this example you can see how you can activate a modifier. In this example the modifier i for case insensitivity is activated.
Example: abcdef ABCDEF
(?i)[a](?-i)[cd] By using (?-i) a modifier is deactivated. In the example, first the modifier i is activated, the letters a and A will be found. After that the modifier i is deactivated, c and d have to be lower case to match this expression.

Example: ac Ac AC ad Ad AD
((?i)[a])[cd] With brackets you can reach the same results. Of course, i has to be deactivated generally in this example.
Example: ac Ac AC ad Ad AD
(?ig-msx)[abc] If you want to change more than one modifier at the same time, you can also do that within one expression. Other possibilities are (?ims) to activate some modifiers or (?-ims) to deactivate a number of modifiers.

Unicode

Often, there is the question whether and how you can use Unicode characters within regular expressions, for example Chinese characters or letters from the Cyrillic or Greek alphabet. Originally, regular expressions were only used for ANSI characters and lots of programs utilizing regular expressions are still only supporting the range of ANSI characters. However, this does not apply to the Text Converter. In this software you can use arbitrary Unicode characters in the same way, you are using ANSI characters. The following examples show how this works and how you can use Unicode characters.

Regular Expression Meaning and Example
[Д-И] In the Text Converter you can use Unicode characters in the same way you are using ANSI and ASCII characters. The example uses the range Д to И from the Cyrillic alphabet in a character group.

Example: АБВГДЕЖЗИКЛ
Arbitrary special characters you can use like this example. Here is the character for infinity.

Example:
x{221E} Alternatively, you can also use the Unicode HEX code for a character. This code is 221E for the infinity symbol and it is used like the regular expression in the example.

Example:
x41 Also in the ASCII range, you can use the HEX code instead of the character. This makes sense especially when noting tabs or other characters that can not be written directly. In the example the hexadecimal code for A (code 41) is shown. A table with all of the HEX codes, you can get in this ASCII table.

Example: ABC ABCABC
[x{0001}-x{221E}] Characters defined with the HEX code in this way can be used as any other character in the syntax of the regular expressions. In the example a group of characters is defined, which range includes all characters up to the code of the infinity symbol. This includes Latin, Greek and Cyrillic but not Japanese characters.

Example: ABCGHJΔΨΩБВГДЕЖカモヤモ

Examples

With the knowledge written on this page, you can write arbitrary regular expressions on your own, by combining the rules in your own way. As an example, the following regular expression will be analyzed. This regular expression can be used to find an arbitrary E-Mail address from any text:

b[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}b

As you can see, the regular expression is rounded by b. That means, the E-Mail address should be a single word and not rounded by other characters in the text. The structure of an E-Mail address is name@domain.ending. As you can see in the regular expression, there is a character group before the @ in which the characters should be repeated at least one time (+). This is the name part of the E-Mail address. After the @, there are two other character groups. One for the domain and one for the ending. These groups are divided by an dot. Because a dot is a special character within regular expressions, it has to be escaped by using . The character group of the domain can consists of an arbitrary number of characters, but at least one character (+) and the character group of the ending has to consist of at least 2 and at most 4 characters. This is indicated by {2,4} behind the group.

With the regular expression in the example, you can find e-mails in texts. But how can you work with this regular expression? For example, in a program like the Text Converter, you can use the expression to search and replace texts. Simply go to the action «Replace Text» and activate regular expressions under the box for the search or replace term. If you use our example, you can enter a text which will replace the e-mail address. For example, you can use the following regular expressions:

Search Term: (b[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}b)
Replace with: <a href=»mailto:$1″>$1</a>

With this, the found e-mail address will be rewritten as link. As you can see, the expression in the search box must be enclosed with brackets. Only if there are brackets around the expression, you can re-use the string in the replace box. The brackets can enclose arbitrary parts of the search term. This makes several other things possible. For example, we can try the following combination:

Search Term: .*(b[a-zA-Z0-9._+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4}b).*
Replace with: $1

With this, you are searching for an e-mail address with arbitrary characters (.*) around it. So, the whole text including the address will be found. But it will only replaced with the string found by the term in brackets, that is the e-mail. So, you can extract an e-mail address out of a text with this regular expression.

If you are using more than one bracket in your search term, you can use them with $1, $2, $3 and so on. Here is an example:

Search Term: (.*)search(.*)
Replace with: $1replace$2

In this example, we are searching for the text in front and behind the string «search». All of this will be replaced with the text in front of and behind «search» and the word «replace» will be written between these texts. This regular expression does something, that can be carried out much more easier. The expression only replaces the word «search» with «replace». But if you modify the expression a little bit, you can search for different writings of a word or you can create much more complex search terms.

Regular Expression Service

Do you need help creating a regular expression? We are here to help you. Just write us via our contact form. The price of our service depends on the complexity of your project. Just give us a brief description of what you are planning to do. Of course, your request will be non-binding.

Software for Regular Expressions

If you would like to use the regular expression introduced on this page to work on text files, you can use the software Text Converter for this task. The Text Converter makes it possible to search in text files according to regular expressions, you can replace regular expressions with other texts or other expressions, you can delete parts of the text with the help of regular expressions or you can split files at the position of a regular expression to save the files as single files. Of course, with this program it is also possible to use matched parts in another context or order (back references).

Another program is the Easy MP3 Player, with which it is possible to search your music collection with regular expressions, so that you can transform very specified searches.

Here is a list of programs in which you can use regular expressions:

  • Text Converter (Searching, replacing and deleting text in an entire text, in lines, in CSV cells and in XML files with the help of regular expressions, back references and file splitting on regular expressions)
  • Easy MP3 Player (Search your music collection with regular expressions)
  • Clipboard Saver (Search and replace clipboard contents with regular expressions)
  • File Renamer (find and replace as well as delete text in file names with regular expressions)
  • Word Creator (counting characters of a text that correspond to a regular expression)

Important Note

This text about Regular Expression was written by Stefan Trost and it is not allowed to use this text (even in parts) in another context without a permission of Stefan Trost.

A Regular Expression – or regex for short– is a syntax that allows you to match strings with specific patterns. Think of it as a suped-up text search shortcut, but a regular expression adds the ability to use quantifiers, pattern collections, special characters, and capture groups to create extremely advanced search patterns.
Regex can be used any time you need to query string-based data, such as:

  • Analyzing command line output
  • Parsing user input
  • Examining server or program logs
  • Handling text files with a consistent syntax, like a CSV
  • Reading configuration files
  • Searching and refactoring code

While doing all of these is theoretically possible without regex, when regexes hit the scene they act as a superpower for doing all of these tasks.

In this guide we’ll cover:

  • What does a regex look like?
  • How to read and write a regex
    • What’s a “quantifier”?
    • What’s a “pattern collection”?
    • What’s a “regex token”?
  • How to use a regex
  • What’s a “regex flag“?
  • What’s a “regex group”?

What does a regex look like?

In its simplest form, a regex in usage might look something like this:

We’re using a regular expression /Test/ to look for the word “Test”

This screenshot is of the regex101 website. All future screenshots will utilize this website for visual reference.

In the “Test” example the letters test formed the search pattern, same as a simple search.
These regexes are not always so simple, however. Here’s a regex that matches 3 numbers, followed by a “-“, followed by 3 numbers, followed by another “-“, finally ended by 4 numbers.

You know, like a phone number:

^(?:d{3}-){2}d{4}$

The phone number “555-555-5555” will match with the regex above, but “555-abc-5555” will not

This regex may look complicated, but two things to keep in mind:

  1. We’ll teach you how to read and write these in this article
  2. This is a fairly complex way of writing this regex.

In fact, most regexes can be written in multiple ways, just like other forms of programming. For example, the above can be rewritten into a longer but slightly more readable version:

^[0-9]{3}-[0-9]{3}-[0-9]{4}$

Most languages provide a built-in method for searching and replacing strings using regex. However, each language may have a different set of syntaxes based on what the language dictates.

In this article, we’ll focus on the ECMAScript variant of Regex, which is used in JavaScript and shares a lot of commonalities with other languages’ implementations of regex as well.

How to read (and write) regexes

Quantifiers

Regex quantifiers check to see how many times you should search for a character.

Here is a list of all quantifiers:

  • a|b– Match either “a” or “b
  • ? – Zero or one
  • + – one or more
  • * – zero or more
  • {N} – Exactly N number of times (where N is a number)
  •  {N,} – N or more number of times (where N is a number)
  • {N,M} – Between N and M number of times (where N and M are numbers and N < M)
  • *? – Zero or more, but stop after first match

For example, the following regex:

Hello|Goodbye

Matches both the string “Hello” and “Goodbye”.

Meanwhile:

Hey?

Will track “y” zero to one time, so will match up with “He” and “Hey”.

Alternatively:

Hello{1,3}

Will match “Hello”, “Helloo”, “Hellooo”, but not “Helloooo”, as it is looking for the letter “o” between 1 and 3 times.

These can even be combined with one another:

He?llo{2}

Here we’re looking for strings with zero-to-one instances of “e” and the letter “o” times 2, so this will match “Helloo” and “Hlloo”.

Greedy matching

One of the regex quantifiers we touched on in the previous list was the + symbol. This symbol matches one or more characters. This means that:

Hi+

Will match everything from “Hi” to “Hiiiiiiiiiiiiiiii”. This is because all quantifiers are considered “greedy” by default.

However, if you change it to be “lazy” using a question mark symbol (?) to the following, the behavior changes.

Hi+?

Now, the i matcher will try to match as few times as possible. Since the +icon means “one or more”, it will only match one “i”. This means that if we input the string “Hiiiiiiiiiii”, only “Hi” will be matched.

While this isn’t particularly useful on its own, when combined with broader matches like the the . symbol, it becomes extremely important as we’ll cover in the next section. The .symbol is used in regex to find “any character”.

Now if you use:

H.*llo

You can match everything from “Hillo” to “Hello” to “Hellollollo”.

>We’re using a regex /H.*llo/ to look for the words “Hillo”, “Hello”, and “Helloollo”

However, what if you want to only match “Hello” from the final example?

Well, simply make the search lazy with a ?  and it’ll work as we want:

H.*?llo

We’re using a regex /H.*?llo/ to look for the words “Hillo”, “Hello”, and partially match the “Hello” in “Helloollo”

Pattern collections

Pattern collections allow you to search for a collection of characters to match against. For example, using the following regex:

My favorite vowel is [aeiou]Code language: CSS (css)

You could match the following strings:

My favorite vowel is a
My favorite vowel is e
My favorite vowel is i
My favorite vowel is o
My favorite vowel is u

But nothing else.

Here’s a list of the most common pattern collections:

  • [A-Z]– Match any uppercase character from “A” to “Z”
  • [a-z]– Match any lowercase character from “a” to “z”
  • [0-9] – Match any number
  • [asdf]– Match any character that’s either “a”, “s”, “d”, or “f”
  • [^asdf]– Match any character that’s not any of the following: “a”, “s”, “d”, or “f”

You can even combine these together:

  • [0-9A-Z]– Match any character that’s either a number or a capital letter from “A” to “Z”
  • [^a-z] – Match any non-lowercase letter

General tokens

Not every character is so easily identifiable. While keys like “a” to “z” make sense to match using regex, what about the newline character?

The “newline” character is the character that you input whenever you press “Enter” to add a new line.

  • . – Any character
  • n – Newline character
  • t – Tab character
  • s– Any whitespace character (including t, n and a few others)
  • S – Any non-whitespace character
  • w– Any word character (Uppercase and lowercase Latin alphabet, numbers 0-9, and _)
  • W– Any non-word character (the inverse of the w token)
  • b– Word boundary: The boundaries between w and W, but matches in-between characters
  • B– Non-word boundary: The inverse of b
  • ^ – The start of a line
  • $ – The end of a line 
  • – The literal character “”

So if you wanted to remove every character that starts a new word you could use something like the following regex:

s.

And replace the results with an empty string. Doing this, the following:

Hello world how are you

Becomes:

Helloorldowreou

We’re using a regex /s./ to look for the whitespaces alongside the following character in the string “Hello world how are you”

Combining with collections

These tokens aren’t just useful on their own, though! Let’s say that we want to remove any uppercase letter or whitespace character. Sure, we could write

[A-Z]|s

But we can actually merge these together and place our s token into the collection:

[A-Zs]Code language: JSON / JSON with Comments (json)

We’re using a regex /[A-Zs]/ to look for uppercase letters and whitespaces in the string “Hello World how are you”

Word boundaries

In our list of tokens, we mentioned b to match word boundaries. I thought I’d take a second to explain how it acts a bit differently from others.

Given a string like “This is a string”, you might expect the whitespace characters to be matched – however, this isn’t the case. Instead, it matches between the letters and the whitespace:

We’re using a word boundary regex /b/ to look for the in-between spaces in characters

This can be tricky to get your head around, but it’s unusual to simply match against a word boundary. Instead, you might have something like the following to match full words:

bw+b

We’re using a regex /bw+b/ to look for full words. In the string “This is a string” we match “this”, “is”, “a”, and “string”

You can interpret that regex statement like this:

“A word boundary. Then, one or more ‘word’ characters. Finally, another word boundary”.

Start and end line

Two more tokens that we touched on are ^ and $. These mark off the start of a line and end of a line, respectively.

So, if you want to find the first word, you might do something like this:

^w+

To match one or more “word” characters, but only immediately after the line starts. Remember, a “word” character is any character that’s an uppercase or lowercase Latin alphabet letters, numbers 0-9, and_.

The regex /^w+/ matches the first word in the string. In “This is a string” we match “This”

Likewise, if you want to find the last word your regex might look something like this:

w+$

You can use /w+$/ to match the last word in the string. In “This is a string” we match “string”

However, just because these tokens typically end a line doesn’t mean that they can’t have characters after them.

For example, what if we wanted to find every whitespace character between newlines to act as a basic JavaScript minifier? 

Well, we can say “Find all whitespace characters after the end of a line” using the following regex:

$s+

We can use /$s+/ to find all whitespace between the end of a string and the start of the next string.

Character escaping

While tokens are super helpful, they can introduce some complexity when trying to match strings that actually contain tokens. For example, say you have the following string in a blog post:

"The newline character is 'n'"Code language: JSON / JSON with Comments (json)

Or want to find every instance of this blog post’s usage of the “n” string. Well, you can escape characters using. This means that your regex might look something like this:

\n

How to use a regex

Regular expressions aren’t simply useful for finding strings, however. You’re also able to use them in other methods to help modify or otherwise work with strings.

While many languages have similar methods, let’s use JavaScript as an example.

Creating and searching using regex

First, let’s look at how regex strings are constructed. 

In JavaScript (along with many other languages), we place our regex inside of // blocks. The regex searching for a lowercase letter looks like this:

/[a-z]/

This syntax then generates a RegExp object which we can use with built-in methods, like exec, to match against strings.

/[a-z]/.exec("a"); // Returns ["a"]
/[a-z]/.exec("0"); // Returns nullCode language: JavaScript (javascript)

We can then use this truthiness to determine if a regex matched, like we’re doing in line #3 of this example:

We can also alternatively call a RegExp constructor with the string we want to convert into a regex:

const regex = new RegExp("[a-z]"); // Same as /[a-z]/Code language: JavaScript (javascript)

Replacing strings with regex

You can also use a regex to search and replace a file’s contents as well. Say you wanted to replace any greeting with a message of “goodbye”. While you could do something like this:

function youSayHelloISayGoodbye(str) {
  str = str.replace("Hello", "Goodbye");
  str = str.replace("Hi", "Goodbye");
  str = str.replace("Hey", "Goodbye");  str = str.replace("hello", "Goodbye");
  str = str.replace("hi", "Goodbye");
  str = str.replace("hey", "Goodbye");
  return str;
}Code language: JavaScript (javascript)

There’s an easier alternative, using a regex:

function youSayHelloISayGoodbye(str) {
  str = str.replace(/[Hh]ello|[Hh]i|[Hh]ey/, "Goodbye");
  return str;
}Code language: JavaScript (javascript)

However, something you might notice is that if you run youSayHelloISayGoodbyewith “Hello, Hi there”: it won’t match more than a single input:

If the regex /[Hh]ello|[Hh]i|[Hh]ey/ is used on the string “Hello, Hi there”, it will only match “Hello” by default.

Here, we should expect to see both “Hello” and “Hi” matched, but we don’t.

This is because we need to utilize a Regex “flag” to match more than once.

Flags

A regex flag is a modifier to an existing regex. These flags are always appended after the last forward slash in a regex definition. 

Here’s a shortlist of some of the flags available to you.

  • g – Global, match more than once
  • m – Force $ and ^ to match each newline individually
  • i – Make the regex case insensitive

This means that we could rewrite the following regex:

/[Hh]ello|[Hh]i|[Hh]ey/

To use the case insensitive flag instead:

/Hello|Hi|Hey/i

With this flag, this regex will now match:

Hello
HEY
Hi
HeLLo

Or any other case-modified variant.

Global regex flag with string replacing

As we mentioned before, if you do a regex replace without any flags it will only replace the first result:

let str = "Hello, hi there!";
str = str.replace(/[Hh]ello|[Hh]i|[Hh]ey/, "Goodbye");
console.log(str); // Will output "Goodbye, hi there"Code language: JavaScript (javascript)

However, if you pass the global flag, you’ll match every instance of the greetings matched by the regex:

let str = "Hello, hi there!";
str = str.replace(/[Hh]ello|[Hh]i|[Hh]ey/g, "Goodbye");
console.log(str); // Will output "Goodbye, hi there"Code language: JavaScript (javascript)

A note about JavaScript’s global flag

When using a global JavaScript regex, you might run into some strange behavior when running the exec command more than once.

In particular, if you run exec with a global regex, it will return null every other time:

If we assign a regex to a variable then run `exec` on said variable, it will find the results properly the first and third time, but return `null` the second time

This is because, as MDN explains:

JavaScript RegExp objects are stateful when they have the global or sticky flags set… They store a lastIndex from the previous match. Using this internally, exec() can be used to iterate over multiple matches in a string of text…

The exec command attempts to start looking through the lastIndex moving forward. Because lastIndex is set to the length of the string, it will attempt to match "" – an empty string – against your regex until it is reset by another exec command again. While this feature can be useful in specific niche circumstances, it’s often confusing for new users.

To solve this problem, we can simply assign lastIndex to 0 before running each exec command:

If we run `regex.lastIndex = 0` in between each `regex.exec`, then every single `exec` runs as intended

Groups

When searching with a regex, it can be helpful to search for more than one matched item at a time. This is where “groups” come into play. Groups allow you to search for more than a single item at a time.

Here, we can see matching against both Testing 123  and Tests 123without duplicating the “123” matcher in the regex.

/(Testing|tests) 123/ig

With the regex /(Testing|tests) 123/ig we can match “Testing 123” and “Tests 123”

Groups are defined by parentheses; there are two different types of groups–capture groups and non-capturing groups:

  • (...) – Group matching any three characters
  • (?:...) – Non-capturing group matching any three characters

The difference between these two typically comes up in the conversation when “replace” is part of the equation. 

For example, using the regex above, we can use the following JavaScript to replace the text with “Testing 234” and “tests 234”:

const regex = /(Testing|tests) 123/ig;

let str = `
Testing 123
Tests 123
`;

str = str.replace(regex, '$1 234');
console.log(str); // Testing 234nTests 234"Code language: JavaScript (javascript)

We’re using $1 to refer to the first capture group, (Testing|tests). We can also match more than a single group, like both (Testing|tests) and (123):

const regex = /(Testing|tests) (123)/ig;

let str = `
Testing 123
Tests 123
`;

str = str.replace(regex, '$1 #$2');
console.log(str); // Testing #123nTests #123"Code language: JavaScript (javascript)

However, this is only true for capture groups. If we change:

/(Testing|tests) (123)/ig

To become:

/(?:Testing|tests) (123)/ig;

Then there is only one captured group – (123) – and instead, the same code from above will output something different:

const regex = /(?:Testing|tests) (123)/ig;

let str = `
Testing 123
Tests 123
`;

str = str.replace(regex, '$1');
console.log(str); // "123n123"Code language: JavaScript (javascript)

Named capture groups

While capture groups are awesome, it can easily get confusing when there are more than a few capture groups. The difference between $3 and $5 isn’t always obvious at a glance.

To help solve for this problem, regexes have a concept called “named capture groups”

  • (?<name>...)– Named capture group called “name” matching any three characters

You can use them in a regex like so to create a group called “num” that matches three numbers:

/Testing (?<num>d{3})/Code language: HTML, XML (xml)

Then, you can use it in a replacement like so:

const regex = /Testing (?<num>d{3})/
let str = "Testing 123";
str = str.replace(regex, "Hello $<num>")
console.log(str); // "Hello 123"Code language: JavaScript (javascript)

Named back reference

Sometimes it can be useful to reference a named capture group inside of a query itself. This is where “back references” can come into play.

  • k<name>Reference named capture group “name” in a search query

Say you want to match:

Hello there James. James, how are you doing?

But not:

Hello there James. Frank, how are you doing?

While you could write a regex that repeats the word “James” like the following:

/.*James. James,.*/

A better alternative might look something like this:

/.*(?<name>James). k<name>,.*/Code language: HTML, XML (xml)

Now, instead of having two names hardcoded, you only have one.

Lookahead and lookbehind groups

Lookahead and behind groups are extremely powerful and often misunderstood.

There are four different types of lookahead and behinds:

  • (?!) – negative lookahead
  • (?=) – positive lookahead
  • (?<=) – positive lookbehind
  • (?<!) – negative lookbehind

Lookahead works like it sounds like: It either looks to see that something is after the lookahead group or is not after the lookahead group, depending on if it’s positive or negative.

As such, using the negative lookahead like so:

/B(?!A)/

Will allow you to match BC but not BA.

With the regex /B(?!A)/ we can match “B” in “BC” but not in “BA”

You can even combine these with ^ and $ tokens to try to match full strings. For example, the following regex will match any string that does not start with “Test”

/^(?!Test).*$/gm

/^(?!Test).*$/gm lets us match “Hello” and “Other”, but not “Testing 123” and “Tests 123”

Likewise, we can switch this to a positive lookahead to enforce that our string muststart with “Test”

/^(?=Test).*$/gm

Inversing our previous item – /^(?=Test).*$/gm lets us match “Testing 123” and “Tests 123”, but not “Hello” and “Other”

Putting it all together

Regexes are extremely powerful and can be used in a myriad of string manipulations. Knowing them can help you refactor codebases, script quick language changes, and more!

Let’s go back to our initial phone number regex and try to understand it again:

^(?:d{3}-){2}d{4}$

Remember that this regex is looking to match phone numbers such as:

555-555-5555

Here this regex is:

  • Using ^ and $ to define the start and end of a regex line.
  • Using a non-capturing group to find three digits then a dash
    • Repeating this group twice, to match 555-555-
  • Finding the last 4 digits of the phone number

Hopefully, this article has been a helpful introduction to regexes for you. If you’d like to see quick definitions of useful regexes, check out our cheat sheet.

Понравилась статья? Поделить с друзьями:
  • Regular expressions not word
  • Regular expressions in excel
  • Regular expressions find word
  • Regular expressions exclude word
  • Regular expressions any word