Регулярные выражения (их еще называют regexp, или regex) — это механизм для поиска и замены текста. В строке, файле, нескольких файлах… Их используют разработчики в коде приложения, тестировщики в автотестах, да просто при работе в командной строке!
Чем это лучше простого поиска? Тем, что позволяет задать шаблон.
Например, на вход приходит дата рождения в формате ДД.ММ.ГГГГГ. Вам надо передать ее дальше, но уже в формате ГГГГ-ММ-ДД. Как это сделать с помощью простого поиска? Вы же не знаете заранее, какая именно дата будет.
А регулярное выражение позволяет задать шаблон «найди мне цифры в таком-то формате».
Для чего применяют регулярные выражения?
-
Удалить все файлы, начинающиеся на test (чистим за собой тестовые данные)
-
Найти все логи
-
grep-нуть логи
-
Найти все даты
-
…
А еще для замены — например, чтобы изменить формат всех дат в файле. Если дата одна, можно изменить вручную. А если их 200, проще написать регулярку и подменить автоматически. Тем более что регулярные выражения поддерживаются даже простым блокнотом (в Notepad++ они точно есть).
В этой статье я расскажу о том, как применять регулярные выражения для поиска и замены. Разберем все основные варианты.
Содержание
-
Где пощупать
-
Поиск текста
-
Поиск любого символа
-
Поиск по набору символов
-
Перечисление вариантов
-
Метасимволы
-
Спецсимволы
-
Квантификаторы (количество повторений)
-
Позиция внутри строки
-
Использование ссылки назад
-
Просмотр вперед и назад
-
Замена
-
Статьи и книги по теме
-
Итого
Где пощупать
Любое регулярное выражение из статьи вы можете сразу пощупать. Так будет понятнее, о чем речь в статье — вставили пример из статьи, потом поигрались сами, делая шаг влево, шаг вправо. Где тренироваться:
-
Notepad++ (установить Search Mode → Regular expression)
-
Regex101 (мой фаворит в онлайн вариантах)
-
Myregexp
-
Regexr
Инструменты есть, теперь начнём
Поиск текста
Самый простой вариант регэкспа. Работает как простой поиск — ищет точно такую же строку, как вы ввели.
Текст: Море, море, океан
Regex: море
Найдет: Море, море, океан
Выделение курсивом не поможет моментально ухватить суть, что именно нашел regex, а выделить цветом в статье я не могу. Атрибут BACKGROUND-COLOR не сработал, поэтому я буду дублировать регулярки текстом (чтобы можно было скопировать себе) и рисунком, чтобы показать, что именно regex нашел:
Обратите внимание, нашлось именно «море», а не первое «Море». Регулярные выражения регистрозависимые!
Хотя, конечно, есть варианты. В JavaScript можно указать дополнительный флажок i, чтобы не учитывать регистр при поиске. В блокноте (notepad++) тоже есть галка «Match case». Но учтите, что это не функция по умолчанию. И всегда стоит проверить, регистрозависимая ваша реализация поиска, или нет.
А что будет, если у нас несколько вхождений искомого слова?
Текст: Море, море, море, океан
Regex: море
Найдет: Море, море, море, океан
По умолчанию большинство механизмов обработки регэкспа вернет только первое вхождение. В JavaScript есть флаг g (global), с ним можно получить массив, содержащий все вхождения.
А что, если у нас искомое слово не само по себе, это часть слова? Регулярное выражение найдет его:
Текст: Море, 55мореон, океан
Regex: море
Найдет: Море, 55мореон, океан
Это поведение по умолчанию. Для поиска это даже хорошо. Вот, допустим, я помню, что недавно в чате коллега рассказывала какую-то историю про интересный баг в игре. Что-то там связанное с кораблем… Но что именно? Уже не помню. Как найти?
Если поиск работает только по точному совпадению, мне придется перебирать все падежи для слова «корабль». А если он работает по включению, я просто не буду писать окончание, и все равно найду нужный текст:
Regex: корабл
Найдет:
На корабле
И тут корабль
У корабля
Это статический, заранее заданный текст. Но его можно найти и без регулярок. Регулярные выражения особенно хороши, когда мы не знаем точно, что мы ищем. Мы знаем часть слова, или шаблон.
Поиск любого символа
. — найдет любой символ (один).
Текст:
Аня
Ася
Оля
Аля
Валя
Regex: А.я
Результат:
Аня
Ася
ОляАля
Валя
Точка найдет вообще любой символ, включая цифры, спецсисимволы, даже пробелы. Так что кроме нормальных имен, мы найдем и такие значения:
А6я
А&я
А я
Учтите это при поиске! Точка очень удобный символ, но в то же время очень опасный — если используете ее, обязательно тестируйте получившееся регулярное выражение. Найдет ли оно то, что нужно? А лишнее не найдет?
Точку точка тоже найдет!
Regex: file.
Найдет:
file.txt
file1.txt
file2.xls
Но что, если нам надо найти именно точку? Скажем, мы хотим найти все файлы с расширением txt и пишем такой шаблон:
Regex: .txt
Результат:
file.txt
log.txt
file.png1txt.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 по отдельности, а регулярное выражение возвращает целую строку, внутри которой есть несколько тегов.
Напомню, что в разных реализациях регулярные выражения могут работать немного по разному. Это одно из отличий — в некоторых реализациях квантификаторам соответствует максимально длинная строка из возможных. Такие квантификаторы называют жадными.
Если мы понимаем, что нашли не то, что хотели, можно пойти двумя путями:
-
Учитывать символы, не соответствующие желаемому образцу
-
Определить квантификатор как нежадный (ленивый, англ. 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) |
Блюдо11 Блюдо113
|
(?!шаблон) |
Негативный просмотр вперёд (с отрицанием) |
Блюдо(?!11) |
Блюдо1
Блюдо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», он очень мотивирует! Если просто вспоминать «что же я сделал?», вспоминается мало. А тут записал и любуешься списком.
Вот пример улучшалок по моему курсу для тестировщиков:
-
Сделала сообщения для бота — чтобы при выкладке новых тем писал их в чат
-
Фолкс — поправила статью «Расширенный поиск», убрала оттуда про пустой ввод при простом поиске, а то путал
-
Обновила кусочек про эффект золушки (переписывала под ютуб)
И таких набирается штук 10-25. За один цикл. А за год сколько? Ух! Вроде небольшие улучшения, а набирается прилично.
Так вот, когда цикл заканчивается, я пишу в блог о своих успехах. Чтобы вставить список в блог, мне надо удалить нумерацию — тогда я сделаю ее силами блоггера и это будет смотреться симпатичнее.
Удаляю с помощью регулярного выражения:
RegEx: d+. (.*)
Замена: $1
Текст был:
1. Раз
2. Два
Текст стал:
Раз
Два
Можно было бы и вручную. Но для списка больше 5 элементов это дико скучно и уныло. А так нажал одну кнопочку в блокноте — и готово!
Так что регулярные выражения могут помочь даже при написании статьи =)
Статьи и книги по теме
Книги
Регулярные выражения 10 минут на урок. Бен Форта — Очень рекомендую! Прям шикарная книга, где все просто, доступно, понятно. Стоит 100 рублей, а пользы море.
Статьи
Вики — https://ru.wikipedia.org/wiki/Регулярные_выражения. Да, именно ее вы будете читать чаще всего. Я сама не помню наизусть все метасимволы. Поэтому, когда использую регулярки, гуглю их, википедия всегда в топе результатов. А сама статья хорошая, с табличками удобными.
Регулярные выражения для новичков — https://tproger.ru/articles/regexp-for-beginners/
Итого
Регулярные выражения — очень полезная вещь для тестировщика. Применений у них много, даже если вы не автоматизатор и не спешите им стать:
-
Найти все нужные файлы в папке.
-
Grep-нуть логи — отсечь все лишнее и найти только ту информацию, которая вам сейчас интересна.
-
Проверить по базе, нет ли явно некорректных записей — не остались ли тестовые данные в продакшене? Не присылает ли смежная система какую-то фигню вместо нормальных данных?
-
Проверить данные чужой системы, если она выгружает их в файл.
-
Выверить файлик текстов для сайта — нет ли там дублирования слов?
-
Подправить текст для статьи.
-
…
Если вы знаете, что в коде вашей программы есть регулярное выражение, вы можете его протестировать. Вы также можете использовать регулярки внутри ваших автотестов. Хотя тут стоит быть осторожным.
Не забывайте о шутке: «У разработчика была одна проблема и он стал решать ее с помощью регулярных выражений. Теперь у него две проблемы». Бывает и так, безусловно. Как и с любым другим кодом.
Поэтому, если вы пишете регулярку, обязательно ее протестируйте! Особенно, если вы ее пишете в паре с командой rm (удаление файлов в linux). Сначала проверьте, правильно ли отрабатывает поиск, а потом уже удаляйте то, что нашли.
Регулярное выражение может не найти то, что вы ожидали. Или найти что-то лишнее. Особенно если у вас идет цепочка регулярок. Думаете, это так легко — правильно написать регулярку? Попробуйте тогда решить задачку от Егора или вот эти кроссворды =)
PS — больше полезных статей ищите в моем блоге по метке «полезное». А полезные видео — на моем youtube-канале
The following examples illustrate the use and construction of simple regular expressions. Each example includes the type of text to match, one or more regular expressions that match that text, and notes that explain the use of the special characters and formatting.
- Match Exact Phrase Only
- Match Word or Phrase in a List
- Match Word with Different Spellings or Special Characters
- Match Any Email Address from a Specific Domain
- Match Any IP Address in a Range
- Match an Alphanumeric Format
Important: We support RE2 Syntax only, which differs slightly from PCRE. Regular expressions are case-sensitive by default.
Note: Examples shown below can be useful as starting points for more complex regular expressions. However, for matching a single word, we suggest that you use the Content compliance or Objectionable content settings.
Match Exact Phrase Only | |
---|---|
Usage example | Match the phrase stock tips. |
Regex examples | Example 1: (W|^)stockstips(W|$)
Example 2:(W|^)stocks{0,3}tips(W|$) Example 3: (W|^)stocks{0,3}tip(s){0,1}(W|$) |
Notes |
|
Match Word or Phrase in a List | |
---|---|
Usage example | Match any word or phrase in the following list:
|
Regex example | (?i)(W|^)(baloney|darn|drat|fooey|goshsdarnit|heck)(W|$) |
Notes |
|
Match Word with Different Spellings or Special Characters | |
---|---|
Usage example | Match the word viagra and some of the obfuscations that spammers use, such as:
|
Regex example | v[i!1][a@]gr[a@] |
Notes |
|
Match Any Email Address from a Specific Domain | |
---|---|
Usage example | Match any email address from the domains yahoo.com, hotmail.com, and gmail.com. |
Regex example | (W|^)[w.-]{0,25}@(yahoo|hotmail|gmail).com(W|$) |
Notes |
|
Match Any IP Address in a Range | |
---|---|
Usage example | Match any IP address within the range 192.168.1.0 to 192.168.1.255. |
Regex examples | Example 1: 192.168.1. Example 2: 192.168.1.d{1,3} |
Notes |
|
Match an Alphanumeric Format | |
---|---|
Usage example | Match the purchase order numbers for your company. This number has various possible formats, such as:
|
Regex example | (W|^)po[#-]{0,1}s{0,1}d{2}[s-]{0,1}d{4}(W|$) |
Notes |
|
Was this helpful?
How can we improve it?
Regex or regular expression is a pattern-matching tool. It allows you to search text in an advanced manner.
Regex is like CTRL+F on steroids.
For example, to find out all the emails or phone numbers from text, regex can get the job done.
The downside of the regex is it takes a while to memorize all the commands. One could say it takes 20 minutes to learn, but forever to master.
In this guide, you learn the basics of regex.
We are going to use the regex online playground in regexr.com. This is a super useful platform where you can easily practice your regex skills with useful examples.
Make sure to write down each regular expression you see in this guide to truly learn what you are doing.
Regex Tutorial
To make it as beneficial as possible, this tutorial is example-heavy. This means some of the regex concepts are introduced as part of the examples. Make sure you read everything!
Anyway, let’s get started with regex.
Regex and Flags
A regular expression (or regex) starts with forward slash (/) and ends with a forward slash.
The pattern matching happens in between the forward slashes.
For instance, let’s find the word “loud” in the text document.
As you can see, this works like CTRL + F.
Next, pay attention to the letter “g” in the above regex /loud/g.
The letter “g” means that the global flag is activated. In other words, you are treating the piece of example text as one long line of text.
Most of the time you are going to use the “g” flag only.
But it is good to understand there are other flags as well.
In the regexr online editor, you can find all the possible flags in the top right corner.
Now that you understand what regex is and what is the global flag, let’s see an example.
Let’s search for “at” in the piece of text:
As you can see, our regular expression found three matches of “at”.
Now, if you disable the “g” flag, it is only going to match the first occurrence of “at”.
Anyway, let’s switch the global flag back on.
So far using regex has been like using the good old CTRL+F.
However, the true power of the regular expressions shows up when we search for patterns instead of specific words.
To do this, we need to learn about the regex special characters that make pattern matching possible
Let’s start with the + charater.
The + Operator – Match One or More
Let’s search for character “s” in the example text.
This matches all “s” letters there are.
But what if you want to search for multiple “s” characters in a row?
In this case, you can use the + character after the letter “s”. This matches all the following “s” letters after the first one.
As a result, it now matches the double “s” in the text in addition to the singular “s”.
In short, the + operator matches one or more same characters in a row.
Next, let’s take a look at how optional matching works.
The ? Operator – Match Optional Characters
Optional matching is characterized by the question mark operator (?).
Optional matching means to match something that might follow.
For example, to match all letters “s” and every “s” followed by “t”, you can specify the letter “t” as an optional match using the question mark.
This matches:
- Each singular “s”
- Each combination of “st”.
Next up, let’s take a look at a special character that combines the + and ? characters.
The * Operator – Match Any Optional Characters
The star operator (*) means “match zero or more”.
Essentially, it is the combination of the + and the ? operators.
For example, let’s match with each letter “o” and any amount of letter “s” that follow.
This matches:
- All the singular “o” letters.
- All occurrences of “os”.
- All occurrences of “oss”.
As a matter of fact, this would match with “ossssssss” with any number of “s” letters as long as they are preceded by an “o”.
Next, let’s take a look at the wild card character.
The . Operator – Match Anything Except a New Line
In regex, the period is a special character that matches any singular character.
It acts as the wildcard.
The only character the period does not match is a line break.
For example, let’s match any character that comes before “at” in the text.
But how about matching with a dot then? The period (.) is a reserved special character, so it cannot be used.
This is where escaping is used.
The Operator – Escape a Special Character
If you are familiar with programming, you know what escaping means.
If not, escaping means to “invalidate” a reserved keyword or operator using a special character in front of its name.
As you saw in the previous example, the period character acts as a wildcard in regex. This means you cannot use it to match a dot in the text.
As you can see, /./g matches with each letter (and space) in the text, so it is not much of a help.
This is where escaping is useful.
In regex, you can escape any reserved character using a backslash ().
Anything followed by a backslash is going to be converted into a normal text character.
To match dots using regex, escape the period character with (.).
Now it matches all the dots in the text.
Let’s play with the example. To match any character that comes before a dot, add a period before the escaped period:
Now you understand how to match and escape characters in regex. Let’s move on to matching word characters using other special characters.
Match Different Types of Characters
You just learned how to use a backslash to escape a character.
However, the backslash has another important use case. Combining a backslash with some particular character forms an operator that can be used to match useful things.
As an example, an important special character in regex is w.
This matches all the word characters, that is, letters and digits but leaves out spaces.
For example, let’s match all the letters and digits in the text:
Another commonly used special operator is the space character s that matches any type of white space there is in the text.
For example, let’s match all the spaces in the text.
Of course, you can also match numeric characters only.
This happens via the d operator.
For instance, let’s match all digits in the text:
This matches with “2” and “0”.
These are the very basic special character operators there are in regex.
Next, you are going to learn how to invert these special characters.
Invert Special Characters
To invert a special character in regex, capitalize it.
- w matches any word character –> W matches with any non-word character
- s matches with any white space character –> S matches with any non-whitespace character.
- d matches with any digit –> D matches any non-digit character.
Examples:
Next, let’s take a look at how to match words with a specific length.
{} – Match Specific Length
Let’s say you want to capture all the words that are longer than 2 characters long.
Now, you cannot use + or * with the w character as it does not make sense.
Instead, use the curly braces {} by specifying how many characters to match.
There are three ways to use {}:
- {n}. Match n consecutive characters.
- {n,}. Match n character or more.
- {n,m}. Match between n and m in length.
Let’s examples of each.
Example 1. Match all sets of characters that are exactly 3 in length:
Example 2. Match consecutive strings that are longer than 3 characters:
Example 3. Match any set of characters that are between 3 and 5 characters in length:
Now that you know how to deal with quantities in regex, let’s talk about grouping.
[] – Groups and Ranges
In regex, you can use character grouping. This means you match with any character in the group.
One way to group characters is by using square brackets [].
For example, let’s match with any two letters where the last letter is “s” and the first letter is either “a” or “o”.
A really handy feature of using square brackets is you can specify a range. This lets you match any letter in the specified range.
To specify a range, use the dash with the following syntax. For example, [a-z] matches any letter from a to z.
For example, let’s match with any two-letter word that ends in “s” and starts with any character from a to z.
One thing you sometimes may want to do is to combine ranges.
This is also possible in regex.
For example, to find any two letters that end with “s” and start with any lowercase or uppercase letter, you can do:
/[a-zA-Z]s/g
Or if you want to match with any two letters that end with “s” and start with a number between 0 and 9, you can do:
/[0-9]s/g
Awesome.
Next, let’s take a look at another way to group characters in regex.
() Capturing Groups
In regex, capturing groups is a way to treat multiple characters as a single unit.
To create a capturing group, place the characters inside of the parenthesis.
For example, let’s match with words “The” or “the”, where the first letter is either lowercase t or uppercase T.
But why parenthesis? Let’s see what happens without them:
Now it matches with either any single character “t” or the word “The”.
This is the power of the capturing group. It treats the characters inside the parenthesis as a single unit.
Let’s see another example where we find any words that are 2-3 letters long and each letter in the word is either a,s,e,d.
As the last example of capturing, let’s match any words that repeat “os” two or three times in a row.
Here the “os” is not matched in the words “explosion” and “across”. This is because the “os” occurs only a single time. However, the “osososos” at the end has 3 x “os” so it gets matched.
Next up, let’s take a look at yet another special character, caret (^).
The ^ operator – Match the Beginning of a Line
The caret (^) character in regex means match with the beginning of the new line.
For example, let’s match with the letter “T” at the beginning of a text chapter.
Now, let’s see what happens when we try to match with the letter “N” at the beginning of the next line.
No matches!
But why is that? There is an “N” at the beginning of the second line.
This happens because our flag is set to “g” or “global”. We are treating the whole piece of text as a single line of text.
If you want to change this, you need to set the multiline flag in addition to the global flag.
Now the match is also made at the beginning of the second line.
However, it is easier to deal with the text as a single chunk of text, so we are going to disable the multiline flag for the rest of the guide.
Now that you know how the caret operator works in regex, let’s take a look at the next special character, the dollar sign ($).
$ End of Statement
To match the end of a statement with regex, use the dollar sign ($).
For instance, let’s match with a dot that ends the text chapter.
As you can see, this only matches the dot at the end of the second line. As mentioned before, this happens because we treat the text as a single line of text.
Awesome! Now you have learned most of the special characters you are ever going to use in regex.
Next, let’s take a look at how to really benefit from regex by learning about important concepts of lookahead and lookbehind.
Lookbehinds
In regex, a lookbehind means to match something preceded by something.
There are two types of lookbehinds:
- Positive lookbehind
- Negative lookbehind
Let’s take a look at what these do.
The (?<=) Operator – Positive Lookbehind
A positive look behind is specified by defining a group that starts with a question mark, followed by a less than sign and an equal sign, and then a set of characters.
- (?<=)
Here < means we are going to perform a look behind, and = means it is positive.
A positive lookbehind matches everything before the main expression without including it in the result.
For example, let’s match the first characters after “os” in the text.
This positive look behind does not include “os” in the matches. Instead, it checks if the matches are preceded by “os” before showing them.
This is super useful.
The (?<!) Operator – Negative Lookbehind
Another type of look behind is the negative look behind. This is basically the opposite of the positive lookbehind.
To create a negative look behind, create a group with a question mark followed by a less-than sign and an exclamation point.
- (?<!)
Here < means look behind and ! makes it negative.
As an example, let’s perform the exact same search as we did in the positive lookbehind, but let’s make it negative:
As you can see, the negative lookbehind matches everything except the first character after the word “os”. This is the exact opposite of the positive lookahead.
Now that you know what the lookbehinds do, let’s move on to very similar concepts, that is, lookaheads.
Lookaheads
In regex, a lookahead is similar to lookbehind.
A lookahead matches everything after the main expression without including it in the result.
To perform a lookahead, all you need to do is remove the less-than sign.
- (?=) is a positive lookahead.
- (?!) is a negative lookahead.
The (?=) Operator – Positive Lookahead
For example, let’s match with any singular character followed by “es” or “os”.
And as you guessed, a negative lookahead matches the exact opposite of what a positive lookahead does.
The (?!) Operator – Positive Lookahead
For example, let’s match everything except for the single characters that occur before “os” or “es”
Now you have all the tools to understand a slightly more advanced example using regex. Also, you are going to learn a bunch of important things at the same, so keep on reading!
Find and Replace Phone Numbers Using Regex
Let’s say we have a text document that has differently formatted phone numbers.
Our task is to find those numbers and replace them by formatting them all in the same way.
The number that belongs to Alice is simple. Just 10 digits in a row.
The number that belongs to Bob is a bit trickier because you need to group the regex into 5 parts:
- A group of three digits
- Dash
- A group of three digits
- Dash
- A group of four digits.
Now it matches Bob’s number.
But our goal was to match all the numbers at the same time. Now Alice’s number is no longer found.
To fix this, we need to restructure the regex again. Instead of assuming there is always a dash between the first two groups of numbers, let’s assume it is optional. As you now know, this can be done using the question mark.
Good job.
Next up, there can also be numbers separated by space, such as Charlie’s number.
To take this into account, we must assume that the separator is either a white space or a dash. This can be done using a group with square brackets [] by placing a dash and a white space into it.
Now also Charlie’s number is matched by our regular expression.
Then there are those numbers where the first three digits are isolated by parenthesis and where the last two groups are separated by a dash.
To find these numbers, we need to add an optional parenthesis in front of the first three digits. But as you recall, parenthesis is a special character in regex, so you need to escape them using the backslash .
Awesome, now David’s number is also found.
Last but not least, a phone number might be formatted such that the country-specific number is in front of the number with a + sign.
To take this into account, we need to add an optional group of a + sign followed by a digit between 0-9.
Now our regex finds every phone number there is on the list!
Next, let’s replace each number with a number such that each number is formatted in the same way.
Before we can do this, we need to capture each set of numbers by creating capturing groups for them. As you learned before, this happens by placing each set of digits into a set of parenthesis.
If you inspect the Details section of the editor, you can see that now each set of numbers in a phone number is grouped in the capture groups.
For example, let’s click the first phone number match and see the Details:
As you can see, the first phone number is grouped into three capture groups 3,4, and 5.
As another example, let’s click Eric’s number to see the details:
Here you can see that the number is split into groups 1, 2, 3, 4, and 5.
However, there is one problem.
The number +7 occurs twice, in group 1 and group 2.
This is not what we want.
It happens because the regex catches both the +7 with a space and without a space. Thus the 2 groups.
To get rid of this, you can specify the expression that captures the number with space as a non-capturing group.
To do this, use the ?: operator in front of the group:
Now Eric’s number (and all the other numbers too) is nicely split into 4 groups.
Finally, we can use these four capture groups to replace the matched numbers with numbers that are formatted in the same way.
In regex, you can refer to each capture group with $n, where n is the number of the group.
To format the numbers, let’s open up the replace tab in the editor.
Let’s say we want to replace all the numbers with a number that is formatted like this:
+7 123-900-4343
And if there is no +7 in front of the number, then we leave it as:
123-900-4343
To do this, replace each phone number by referencing their capture group in the Replace section of the editor:
$1$2-$3-$4
Amazing! Now all the numbers are replaced in the resulting piece of text and follow the same format.
This concludes our regex tutorial.
Conclusion
Today you learned how to use regex.
In short, regex is a commonly supported tool to match patterns in text documents.
You can use it to find and replace text that matches a specific pattern.
Most programming languages support regex. This means you can use regex in your coding projects to automate a lot of manual work when it comes to text processing.
Thanks for reading.
Happy pattern-matching!
Further Reading
How to Validate Emails with JavaScript + RegEx
About the Author
-
I’m an entrepreneur and a blogger from Finland. My goal is to make coding and tech easier for you with comprehensive guides and reviews.
Recent Posts
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:
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}$
This regex may look complicated, but two things to keep in mind:
- We’ll teach you how to read and write these in this article
- 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”.
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
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 charactern
– Newline charactert
– Tab characters
– Any whitespace character (includingt
,n
and a few others)S
– Any non-whitespace characterw
– Any word character (Uppercase and lowercase Latin alphabet, numbers 0-9, and_
)W
– Any non-word character (the inverse of thew
token)b
– Word boundary: The boundaries betweenw
andW
, but matches in-between charactersB
– Non-word boundary: The inverse ofb
^
– 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
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)
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:
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
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_
.
Likewise, if you want to find the last word your regex might look something like this:
w+$
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+
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 null
Code 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 youSayHelloISayGoodbye
with “Hello, Hi there”: it won’t match more than a single input:
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 oncem
– Force $ and ^ to match each newline individuallyi
– 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:
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:
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 123
without duplicating the “123” matcher in the regex.
/(Testing|tests) 123/ig
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
.
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
Likewise, we can switch this to a positive lookahead to enforce that our string muststart with “Test”
/^(?=Test).*$/gm
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-
- Repeating this group twice, to match
- 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 expression is a group of characters or symbols which is used to find a specific pattern from a text. And this is a kind of cheat sheet of learning regular expressions.
Regular expression is a group of characters or symbols which is used to find a specific pattern from a text.
A regular expression is a pattern that is matched against a subject string from left to right. The word «Regular expression» is a
mouthful, you will usually find the term abbreviated as «regex» or «regexp». Regular expression is used for replacing a text within
a string, validating form, extract a substring from a string based upon a pattern match, and so much more.
Imagine you are writing an application and you want to set the rules when user choosing their username. We want the username can
contains letter, number, underscore and hyphen. We also want to limit the number of characters in username so it does not look ugly.
We use the following regular expression to validate a username:
Above regular expression can accept the strings john_doe
, jo-hn_doe
and john12_as
. It does not match Jo
because that string
contains uppercase letter and also it is too short.
Table of Contents
- Basic Matchers
- Meta character
- Full stop
- Character set
- Negated character set
- Repetitions
- The Star
- The Plus
- The Question Mark
- Braces
- Character Group
- Alternation
- Escaping special character
- Anchors
- Caret
- Dollar
- Shorthand Character Sets
- Lookaround
- Positive Lookahead
- Negative Lookahead
- Positive Lookbehind
- Negative Lookbehind
- Flags
- Case Insensitive
- Global search
- Multiline
- Bonus
1. Basic Matchers
A regular expression is just a pattern of letters and digits that we used to search in a text. For example the regular expression
cat
means: the letter c
, followed by the letter a
, followed by the letter t
.
"cat" => The cat sat on the mat
The regular expression 123
matches the string «123». The regular expression is matched against an input string by comparing each
character in the regular expression to each character in the input string, one after another. Regular expressions are normally
case-sensitive so the regular expression Cat
would not match the string «cat».
"Cat" => The cat sat on the Cat
2. Meta Characters
Meta characters are the building blocks of the regular expressions. Meta characters do not stand for themselves but instead are
interpreted in some special way. Some meta characters have a special meaning that are written inside the square brackets.
The meta character are as follows:
Meta character | Description |
---|---|
. | Period matches any single character except a line break. |
[ ] | Character class. Matches any character contained between the square brackets. |
[^ ] | Negated character class. Matches any character that is not contained between the square brackets |
* | Matches 0 or more repetitions of the preceding symbol. |
+ | Matches 1 or more repetitions of the preceding symbol. |
? | Makes the preceding symbol optional. |
{n,m} | Braces. Matches at least «n» but not more than «m» repetitions of the preceding symbol. |
(xyz) | Character group. Matches the characters xyz in that exact order. |
| | Alternation. Matches either the characters before or the characters after the symbol. |
Escapes the next character. This allows you to match reserved characters [ ] ( ) { } . * + ? ^ $ | |
|
^ | Matches the beginning of the input. |
$ | Matches the end of the input. |
2.1 Full stop
Full stop .
is the simplest example of meta character. The meta character .
matches any single character. It will not match return
or new line characters. For example the regular expression .ar
means: any character, followed by the letter a
, followed by the
letter r
.
".ar" => The car parked in the garage.
2.2 Character set
Character sets are also called character class. Square brackets are used to specify character sets. Use hyphen inside character set to
specify the characters range. The order of the character range inside square brackets doesn’t matter. For example the regular
expression [Tt]he
means: an uppercase T
or lowercase t
, followed by the letter h
, followed by the letter e
.
"[Tt]he" => The car parked in the garage.
A period inside a character set, however, means a literal period. The regular expression ar[.]
means: a lowercase character a
, followed by letter r
, followed by a period .
character.
"ar[.]" => A garage is a good place to park a car.
2.2.1 Negated character set
In general the caret symbol represents the start of the string, but when it is typed after the opening square bracket it negates the
character set. For example the regular expression [^c]ar
means: any character except c
, followed by the character a
, followed by
the letter r
.
"[^c]ar" => The car parked in the garage.
2.3 Repetitions
Following meta characters +
, *
or ?
are used to specify how many times a subpattern can occurs. These meta characters act
differently in different situations.
2.3.1 The Star
The symbol *
matches zero or more repetitions of the preceding matcher. The regular expression a*
means: zero or more repetitions
of preceding lowercase character a
. But if it appears after a character set or class that it finds the repetitions of the whole
character set. For example the regular expression [a-z]*
means: any number of lowercase letters in a row.
"[a-z]*" => The car parked in the garage #21.
The *
symbol can be used with the meta character .
to match any string of characters .*
. The *
symbol can be used with the
whitespace character s
to match a string of whitespace characters. For example the expression s*cats*
means: zero or more
spaces, followed by lowercase character c
, followed by lowercase character a
, followed by lowercase character t
, followed by
zero or more spaces.
"s*cats*" => The fat cat sat on the cat.
2.3.2 The Plus
The symbol +
matches one or more repetitions of the preceding character. For example the regular expression c.+t
means: lowercase
letter c
, followed by any number of character, followed by the lowercase character t
.
"c.+t" => The fat cat sat on the mat.
2.3.3 The Question Mark
In regular expression the meta character ?
makes the preceding character optional. This symbol matches zero or one instance of
the preceding character. For example the regular expression [T]?he
means: Optional the uppercase letter T
, followed by the lowercase
character h
, followed by the lowercase character e
.
"[T]he" => The car is parked in the garage.
"[T]?he" => The car is parked in the garage.
2.4 Braces
In regular expression braces that are also called quantifiers used to specify the number of times that a group of character or a
character can be repeated. For example the regular expression [0-9]{2,3}
means: Match at least 2 digits but not more than 3 (
characters in the range of 0 to 9).
"[0-9]{2,3}" => The number was 9.9997 but we rounded it off to 10.0.
We can leave out the second number. For example the regular expression [0-9]{2,}
means: Match 2 or more digits. If we also remove
the comma the regular expression [0-9]{2}
means: Match exactly 2 digits.
"[0-9]{2,}" => The number was 9.9997 but we rounded it off to 10.0.
"[0-9]{2}" => The number was 9.9997 but we rounded it off to 10.0.
2.5 Character Group
Character group is a group of sub-pattern that is written inside Parentheses (...)
. As we discussed before that in regular expression
if we put quantifier after character than it will repeats the preceding character. But if we put quantifier after a character group than
it repeats the whole character group. For example the regular expression (ab)*
matches zero or more repetitions of the character «ab».
We can also use the alternation |
meta character inside character group. For example the regular expression (c|g|p)ar
means: lowercase character c
,
g
or p
, followed by character a
, followed by character r
.
"(c|g|p)ar" => The car is parked in the garage.
2.6 Alternation
In regular expression Vertical bar |
is used to define alternation. Alternation is like a condition between multiple expressions. Now,
you maybe thinking that character set and alternation works the same way. But the big difference between character set and alternation
is that character set works on character level but alternation works on expression level. For example the regular expression
(T|t)he|car
means: uppercase character T
or lowercase t
, followed by lowercase character h
, followed by lowercase character e
or lowercase character c
, followed by lowercase character a
, followed by lowercase character r
.
"(T|t)he|car" => The car is parked in the garage.
2.7 Escaping special character
Backslash is used in regular expression to escape the next character. This allows to to specify a symbol as a matching character
including reserved characters { } [ ] / + * . $ ^ | ?
. To use a special character as a matching character prepend before it.
For example the regular expression .
is used to match any character except new line. Now to match .
in an input string the regular
expression (f|c|m)at.?
means: lowercase letter f
, c
or m
, followed by lowercase character a
, followed by lowercase letter
t
, followed by optional .
character.
"(f|c|m)at.?" => The fat cat sat on the mat.
2.8 Anchors
In regular expression to check if the matching symbol is the starting symbol or ending symbol of the input string for this purpose
we use anchors. Anchors are of two types: First type is Caret ^
that check if the matching character is the start character of the
input and the second type is Dollar $
that checks if matching character is the last character of the input string.
2.8.1 Caret
Caret ^
symbol is used to check if matching character is the first character of the input string. If we apply the following regular
expression ^a
(if a is the starting symbol) to input string abc
it matches a
. But if we apply regular expression ^b
on above
input string it does not match anything. Because in input string abc
«b» is not the starting symbol. Let’s take a look on another
regular expression ^(T|t)he
which means: uppercase character T
or lowercase character t
is the start symbol of the input string,
followed by lowercase character h
, followed by lowercase character e
.
"(T|t)he" => The car is parked in the garage.
"^(T|t)he" => The car is parked in the garage.
2.8.2 Dollar
Dollar $
symbol is used to check if matching character is the last character of the input string. For example regular expression
(at.)$
means: a lowercase character a
, followed by lowercase character t
, followed by a .
character and the matcher
must be end of the string.
"(at.)" => The fat cat. sat. on the mat.
"(at.)$" => The fat cat sat on the mat.
3. Shorthand Character Sets
Regular expression provides shorthands for the commonly used character sets, which offer convenient shorthands for commonly used
regular expressions. The shorthand character sets are as follows:
Shorthand | Description |
---|---|
. | Any character except new line |
w | Matches alphanumeric characters: [a-zA-Z0-9_] |
W | Matches non-alphanumeric characters: [^w] |
d | Matches digit: [0-9] |
D | Matches non-digit: [^d] |
s | Matches whitespace character: [tnfrp{Z}] |
S | Matches non-whitespace character: [^s] |
4. Lookaround
Lookbehind and lookahead sometimes known as lookaround are specific type of non-capturing group (Use to match the pattern but not
included in matching list). Lookaheads are used when we have the condition that this pattern is preceded or followed by another certain
pattern. For example we want to get all numbers that are preceded by $
character from the following input string $4.44 and $10.88
.
We will use following regular expression (?<=$)[0-9.]*
which means: get all the numbers which contains .
character and preceded
by $
character. Following are the lookarounds that are used in regular expressions:
Symbol | Description |
---|---|
?= | Positive Lookahead |
?! | Negative Lookahead |
?<= | Positive Lookbehind |
?<! | Negative Lookbehind |
4.1 Positive Lookahead
The positive lookahead asserts that the first part of the expression must be followed by the lookahead expression. The returned match
only contains the text that is matched by the first part of the expression. To define a positive lookahead braces are used and within
those braces question mark with equal sign is used like this (?=...)
. Lookahead expression is written after the equal sign inside
braces. For example the regular expression (T|t)he(?=sfat)
means: optionally match lowercase letter t
or uppercase letter T
,
followed by letter h
, followed by letter e
. In braces we define positive lookahead which tells regular expression engine to match
The
or the
which are followed by the word fat
.
"(T|t)he(?=sfat)" => The fat cat sat on the mat.
4.2 Negative Lookahead
Negative lookahead is used when we need to get all matches from input string that are not followed by a pattern. Negative lookahead
defined same as we define positive lookahead but the only difference is instead of equal =
character we use negation !
character
i.e. (?!...)
. Let’s take a look at the following regular expression (T|t)he(?!sfat)
which means: get all The
or the
words from
input string that are not followed by the word fat
precedes by a space character.
"(T|t)he(?!sfat)" => The fat cat sat on the mat.
4.3 Positive Lookbehind
Positive lookbehind is used to get all the matches that are preceded by a specific pattern. Positive lookbehind is denoted by
(?<=...)
. For example the regular expression (?<=(T|t)hes)(fat|mat)
means: get all fat
or mat
words from input string that
are after the word The
or the
.
"(?<=(T|t)hes)(fat|mat)" => The fat cat sat on the mat.
4.4 Negative Lookbehind
Negative lookbehind is used to get all the matches that are not preceded by a specific pattern. Negative lookbehind is denoted by
(?<!...)
. For example the regular expression (?<!(T|t)hes)(cat)
means: get all cat
words from input string that
are after not after the word The
or the
.
"(?<!(T|t)hes)(cat)" => The cat sat on cat.
5. Flags
Flags are also called modifiers because they modify the output of a regular expression. These flags can be used in any order or
combination, and are an integral part of the RegExp.
Flag | Description |
---|---|
i | Case insensitive: Sets matching to be case-insensitive. |
g | Global Search: Search for a pattern throughout the input string. |
m | Multiline: Anchor meta character works on each line. |
5.1 Case Insensitive
The i
modifier is used to perform case-insensitive matching. For example the regular expression /The/gi
means: uppercase letter
T
, followed by lowercase character h
, followed by character e
. And at the end of regular expression the i
flag tells the
regular expression engine to ignore the case. As you can see we also provided g
flag because we want to search for the pattern in
the whole input string.
"The" => The fat cat sat on the mat.
"/The/gi" => The fat cat sat on the mat.
5.2 Global search
The g
modifier is used to perform a global match (find all matches rather than stopping after the first match). For example the
regular expression/.(at)/g
means: any character except new line, followed by lowercase character a
, followed by lowercase
character t
. Because we provided g
flag at the end of the regular expression now it will find every matches from whole input
string.
".(at)" => The fat cat sat on the mat.
"/.(at)/g" => The fat cat sat on the mat.
5.3 Multiline
The m
modifier is used to perform a multi line match. As we discussed earlier anchors (^, $)
are used to check if pattern is
the beginning of the input or end of the input string. But if we want that anchors works on each line we use m
flag. For example the
regular expression /at(.)?$/gm
means: lowercase character a
, followed by lowercase character t
, optionally anything except new
line. And because of m
flag now regular expression engine matches pattern at the end of each line in a string.
"/.at(.)?$/" => The fat cat sat on the mat.
"/.at(.)?$/gm" => The fat cat sat on the mat.
Bonus
- Positive Integers:
^d+$
- Negative Integers:
^-d+$
- US Phone Number:
^+?[ds]{3,}$
- US Phone with code:
^+?[ds]+(?[ds]{10,}$
- Integers:
^-?d+$
- Username:
^[wd_.]{4,16}$
- Alpha-numeric characters:
^[a-zA-Z0-9]*$
- Alpha-numeric characters with spaces:
^[a-zA-Z0-9 ]*$
- Password:
^(?=^.{6,}$)((?=.*[A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z]))^.*$
- email:
^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,4})*$
- IPv4 address:
^((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?).){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))*$
- Lowercase letters only:
^([a-z])*$
- Uppercase letters only:
^([A-Z])*$
- URL:
^(((http|https|ftp)://)?([[a-zA-Z0-9]-.])+(.)([[a-zA-Z0-9]]){2,4}([[a-zA-Z0-9]/+=%&_.~?-]*))*$
- VISA credit card numbers:
^(4[0-9]{12}(?:[0-9]{3})?)*$
- Date (MM/DD/YYYY):
^(0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)?[0-9]{2}$
- Date (YYYY/MM/DD):
^(19|20)?[0-9]{2}[- /.](0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])$
- MasterCard credit card numbers:
^(5[1-5][0-9]{14})*$