Let’s say you have a string and you want to check if it contains a specific word, character or substring. There are a lot of things that you need to be aware of in order to do the check properly without making silly mistakes. For example, using !=
instead of !==
during the check can sometimes result in false negatives. Similarly, you might be looking for a word in a case-insensitive manner but forget that the method you used is case-sensitive.
In this tutorial, you will learn about different methods to check if a particular word, character or substring exists in another string. We will also discuss the pros and cons of each of these methods in detail.
On This Page
- Using strpos() to Check if String Contains Substring [Case Sensitive]
- Using stripos() to Check if String Contains Substring [Case Insensitive]
- Using Regex to Check if String Contains Substring
- Using Regex to Check for Exact Word Matches in String
- Using strstr() to Check if String Contains Substring
- Quick Summary
Using strpos() to Check if String Contains Substring [Case Sensitive]
The easiest way to check if a string contains a specific word is with the help of PHP strpos() function. PHP strpos()
function returns the position of the first occurrence of a substring in a string. It returns FALSE if the word or substring was not found. This means that you can compare the return value of strpos()
to FALSE to check for a substring. The following example will show it in action.
PHP
$the_string = "I am 5 years older than you.";
$the_word = "years";
$the_character = "I";
$the_substring = "5 years";
// Output — The word "years" exists in given string.
if (strpos($the_string, $the_word) !== false) {
echo 'The word "'.$the_word.'" exists in given string.';
}
// Output — The character "I" exists in given string.
if (strpos($the_string, $the_character) !== false) {
echo 'The character "'.$the_character.'" exists in given string.';
}
// Output — The substring "5 years" exists in given string.
if (strpos($the_string, $the_substring) !== false) {
echo 'The substring "'.$the_substring.'" exists in given string.';
}
You should note that I have used the strict inequality operator (!==
). If the word we are looking for occurs at the beginning of the string, strpos()
will return 0 which will evaluate to FALSE value with !=
operator. Here is an example:
PHP
// Output — The character "I" does not exist in given string.
if (strpos($the_string, $the_character) != false) {
echo 'The character "'.$the_character.'" exists in given string.';
} else {
echo 'The character "'.$the_character.'" does not exist in given string.';
}
Another thing to keep in mind is that while you might be looking for an exact word match like “am”, the function will also return TRUE if the string contains words like “sam”, “pam” or “amazing”. This may or may not be what you want to achieve. I just wanted to point it out so that you can proceed with caution.
You can also use > -1
instead of !==
because even if strpos()
returns 0 as index value, it will still be greater than -1. However, remember that the greater than operator (>
) is slower than the strict inequality operator (!==
).
Using stripos() to Check if String Contains Substring [Case Insensitive]
If you are looking for a word or substring inside another string but don’t care about the case of the matched substring, you can use the stripos() function in PHP. This function is similar to the strpos()
function we discussed in the previous section. The only difference is that it ignores the case when looking for a substring inside another string.
PHP
$the_string = "Ben likes both apples and oranges.";
$the_word = "ben";
$the_character = "I";
$the_substring = "LiKes BoTH";
// Output — The word "ben" exists in given string.
if (stripos($the_string, $the_word) !== false) {
echo 'The word "'.$the_word.'" exists in given string.';
}
// Output — The character "I" exists in given string.
if (stripos($the_string, $the_character) !== false) {
echo 'The character "'.$the_character.'" exists in given string.';
}
// Output — The substring "LiKes BoTH" exists in given string.
if (stripos($the_string, $the_substring) !== false) {
echo 'The substring "'.$the_substring.'" exists in given string.';
}
The strpos()
function would have returned FALSE in all the cases above but stripos()
ignored the case and returned TRUE.
Another way to check for a case-insensitive match would be to first convert all the strings and substrings that you want to compare to the same case using either strtolower() or strtoupper() function. After that you could just use the strpos()
function to perform the check. However, it is simply easier to use the stripos()
function.
Using Regex to Check if String Contains Substring
Another option to see if there is a word, character or substring in a string is to use regular expressions. They are generally better suited when you are looking for more complex search patterns in a string.
In situations where you are searching for a particular word, using regular expression might not be a great idea. Keep in mind that using simple strpos()
is about 3 times faster than using regular expressions. Anyway, the following code snippet will show you how to look for a word, character or substring in a string using regular expressions:
PHP
$the_string = "I am 5 years older than you.";
$the_word = "years";
$the_character = "I";
$the_substring = "5 years";
// Output — The word "years" exists in given string.
if (preg_match('/years/', $the_string)) {
echo 'The word "'.$the_word.'" exists in given string.';
}
// Output — The character "I" exists in given string.
if (preg_match('/I/', $the_string)) {
echo 'The character "'.$the_character.'" exists in given string.';
}
// Output — The substring "5 years" exists in given string.
if (preg_match('/5 years/', $the_string)) {
echo 'The substring "'.$the_substring.'" exists in given string.';
}
As I mentioned earlier, using preg_match() only makes sense when you searching for more complicated patterns instead of simple words or substrings. For instance, you can use this function to check if a substring contains any words with 10 or more characters etc. Here is a basic example:
PHP
$the_string = 'Photosynthesis and jeopardize are big words.';
// Output — The given string contains words with 10 or more letters.
if (preg_match('/w{10,}/i', $the_string)) {
echo 'The given string contains words with 10 or more letters.';
}
You can make all the substring checks which use regex, case-insensitive by adding the flag i
at the end of the pattern. The following example will make it more clear:
PHP
$the_string = "Ben likes both apples and oranges.";
$the_word = "ben";
$the_character = "I";
$the_substring = "LiKes BoTH";
// Output — The word "years" exists in given string.
if (preg_match('/ben/i', $the_string)) {
echo 'The word "'.$the_word.'" exists in given string.';
}
// Output — The character "I" exists in given string.
if (preg_match('/I/i', $the_string)) {
echo 'The character "'.$the_character.'" exists in given string.';
}
// Output — The substring "5 years" exists in given string.
if (preg_match('/LiKes BoTH/i', $the_string)) {
echo 'The substring "'.$the_substring.'" exists in given string.';
}
Using Regex to Check for Exact Word Matches in String
While using strpos()
and stripos()
to look for a word is definitely faster than using regular expressions, using them when you are looking for exact word matches can be problematic. For instance, if you are looking for an exact match of the word “synthesis” in a sentence but it contains the word “Photosynthesis”, you will get a FALSE positive with strpos()
and stripos()
.
Using regular expressions can be very handy in such situations. You can use the expression b
in a regex pattern to indicate a word boundary. If the word you are looking for is surrounded by two b
in a regex pattern, the preg_match()
function will only match full words and return FALSE for partial matches. Here is an example:
PHP
$the_string = 'Photosynthesis and jeopardize are big words.';
$the_word = 'synthesis';
// Output — The word "synthesis" has an exact match in given string. [FALSE positive]
if (preg_match('/synthesis/', $the_string)) {
echo 'The word "synthesis" has an exact match in given string. [FALSE positive]';
}
// Output — The word "synthesis" has an exact match in given string. [FALSE positive]
if (strpos($the_string, $the_word)) {
echo 'The word "synthesis" has an exact match in given string. [FALSE positive]';
}
// Output — The word "synthesis" does not have an exact match in given string. [Expected Result]
if (preg_match('/bsynthesisb/', $the_string)) {
echo 'The word "synthesis" has an exact match in given string. [FALSE positive]';
} else {
echo 'The word "synthesis" does not have an exact match in given string. [Expected Result]';
}
Using strstr() to Check if String Contains Substring
The PHP strstr() function can also be used to see if a word, character or substring exists within another string. This function returns the portion of the first string starting from and including the first occurrence of the word or substring that we are looking for to the end of haystack. It will return FALSE if needle is not found. This means that we can simply check if a string contains a substring by looking for the return value of strstr()
. Here is an example:
PHP
$the_string = "I am 5 years older than you.";
$the_word = "years";
$the_character = "I";
$the_substring = "5 years";
// Output — The word "years" exists in given string.
if (strstr($the_string, $the_word) !== false) {
echo 'The word "'.$the_word.'" exists in given string.';
}
// Output — The character "I" exists in given string.
if (strstr($the_string, $the_character) !== false) {
echo 'The character "'.$the_character.'" exists in given string.';
}
// Output — The substring "5 years" exists in given string.
if (strstr($the_string, $the_substring) !== false) {
echo 'The substring "'.$the_substring.'" exists in given string.';
}
Just like the strpos()
function, the strstr()
function also contains a case-insensitive counterpart. You can use the stristr()
function to look for a word, character or substring inside another string.
Quick Summary
Let’s recap everything we have covered in this tutorial.
-
The best method to check if a string contains another word, character or substring is to use the PHP strpos() function. If you intend to perform a case insensitive search, you can use the PHP stripos() function instead. Both these function are easy to use and faster than other methods.
-
If you are not looking for a particular substring but for a matching pattern, using regular expressions for the search might be a good idea. You can also use regular expressions for simple searches but they are slower than the
strpos()
function. -
Regular expression can be helpful when you are looking for an exact match of a word. For example, if you are looking for the word “shy”,
strpos()
will also return TRUE if the string contains “Pushy”. On the other hand, you can use the word boundary expressionb
inside thepreg_match()
function to only return FALSE if you are looking for “shy” and the string contains “Pushy”. -
You can use other available PHP functions like strstr() for case-sensitive matches and stristr() for case-insensitive matches if you don’t want to deal with TRUTHY and FALSEY values.
Let me know if there is anything that you would like me to clarify in this tutorial. Also, you are more than welcome to comment if you know other techniques to check if a string contains another word, character or substring in PHP.
Rate this post —
Loading…
Регулярные выражения (их еще называют 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-канале
In this Java regex word boundary example, we will learn to match a specific word in a string. e.g. We will match “java” in “java is object oriented language”. But it should not match “javap” in “javap is another tool in JDL bundle”.
1. java regex word boundary matchers
Boundary matchers help to find a particular word, but only if it appears at the beginning or end of a line. They do not match any characters. Instead, they match at certain positions, effectively anchoring the regular expression match at those positions.
The following table lists and explains all the boundary matchers.
Boundary token | Description |
---|---|
^ |
The beginning of a line |
$ |
The end of a line |
b |
A word boundary |
B |
A non-word boundary |
A |
The beginning of the input |
G |
The end of the previous match |
Z |
The end of the input but for the final terminator, if any |
z |
The end of the input |
Solution Regex : bwordb
The regular expression token "b"
is called a word boundary. It matches at the start or the end of a word. By itself, it results in a zero-length match.
Strictly speaking, “b” matches in these three positions:
- Before the first character in the data, if the first character is a word character
- After the last character in the data, if the last character is a word character
- Between two characters in the data, where one is a word character and the other is not a word character
To run a “spcific word only” search using a regular expression, simply place the word between two word boundaries.
String data1 = "Today, java is object oriented language"; String regex = "\bjava\b"; Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(data1); while (matcher.find()) { System.out.print("Start index: " + matcher.start()); System.out.print(" End index: " + matcher.end() + " "); System.out.println(matcher.group()); } Output: Start index: 7 End index: 11 java
Please note that matching above regex with “Also, javap is another tool in JDL bundle” doesn’t produce any result i.e. doesn’t match any place.
3. Java regex to match word with nonboundaries – contain word example
Suppose, you want to match “java
” such that it should be able to match words like “javap
” or “myjava
” or “myjavaprogram
” i.e. java word can lie anywhere in the data string. It could be start of word with additional characters in end, or could be in end of word with additional characters in start as well as in between a long word.
"B"
matches at every position in the subject text where "B"
does not match. "B"
matches at every position that is not at the start or end of a word.
To match such words, use below regex :
Solution Regex : \Bword|word\B
String data1 = "Searching in words : javap myjava myjavaprogram"; String regex = "\Bjava|java\B"; Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(data1); while (matcher.find()) { System.out.print("Start index: " + matcher.start()); System.out.print(" End index: " + matcher.end() + " "); System.out.println(matcher.group()); } Output: Start index: 21 End index: 25 java Start index: 29 End index: 33 java Start index: 36 End index: 40 java
Please note that it will not match “java” word in first example i.e. “Today, java is object oriented language” because “\B” does not match start and end of a word.
3. Java regex to match word irrespective of boundaries
This is simplest usecase. You want to match “java” word in all four places in string “Searching in words : java javap myjava myjavaprogram”. To able to do so, simply don’t use anything.
Solution regex : word
String data1 = "Searching in words : java javap myjava myjavaprogram"; String regex = "java"; Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(data1); while (matcher.find()) { System.out.print("Start index: " + matcher.start()); System.out.print(" End index: " + matcher.end() + " "); System.out.println(matcher.group()); } Output: Start index: 21 End index: 25 java Start index: 26 End index: 30 java Start index: 34 End index: 38 java Start index: 41 End index: 45 java
That’s all for this java regex contain word example related to boundary and non-boundary matches of a specific word using java regular expressions.
Happy Learning !!
References:
Java regex docs
A common operation in many programming languages is to check if a string contains another string. While it’s a simple and common task, the method names often differ between programming languages. For example, here is a small sample of the methods used to achieve this in various languages:
- Java:
String.contains()
,String.indexOf()
, etc. - Python:
in
operator,String.index()
,String.find()
- Go:
strings.Contains()
- Ruby:
string.include?
You get the point. There are a million ways to do this, and it seems like each language implements it differently.
Anyway, let’s see a few of the ways in which you can check if string contains a substring in JavaScript.
Note: The first two methods shown below also works on arrays, which tells you if an array contains a given value (or the index of it for indexOf()
). Keep this in mind when reading the article as it’ll likely help you for similar use-cases.
The String.includes()
Method
This method was introduced in ES6 and is typically the preferred method for simple use-cases. If all you need to do is get a boolean value indicating if the substring is in another string, then this is what you’ll want to use.
It works like this:
> let str = 'stackabuse';
> let substr = 'stack';
> str.includes(substr);
true
As you can see, a boolean value is returned since the string «stack» is a substring of «stackabuse».
This is a case sensitive search, so the following will not match the substring:
> let str = 'StackAbuse';
> let substr = 'stack';
> str.includes(substr);
false
While this is enough for most use-cases, the includes()
method also provides another option that may be useful to you. A second argument can be provided that tells the method at which index to start the search. So if you know that the substring isn’t contained in the first 50 characters (or you just don’t want it to match those characters), then you can use the method like this:
str.includes(substr, 50);
An offset of below 0 just starts the search from index 0, and an offset greater than string.length
returns false
since the search starts from string.length
.
The String.indexOf()
Method
The String.indexOf()
method is much like the previous includes()
method (and it’s suitable to be used in a polyfill for includes()
as well), but the only difference is the return value. Instead of returning a boolean value indicating the presence of the substring, it actually returns the index location of the substring, or -1 if it isn’t present.
Here’s an example:
> let str = 'stackabuse';
> let substr = 'abuse';
> str.indexOf(substr);
5
> str.indexOf('apple');
-1
As you can see, this method returns the 0-based index position of the substring, and a -1 when the substring wasn’t found.
Just like the includes()
method, the indexOf()
method is case sensitive and also supports the offset parameter:
> let str = 'StackAbuse';
> let substr = 'abuse';
> str.indexOf(substr);
-1
> str.indexOf('Abu', 3);
5
> str.indexOf('Abu', 6);
-1
This method is useful for when you need to know the exact location of the substring, however, it’s not as clean when simply using it as a boolean:
let str = 'stackabuse';
let substr = 'stack';
if (str.indexOf(substr) > -1) {
console.log('Found the substring!');
}
In cases like this you should use the includes()
method instead as it’s more error-prone.
Regex
One of the more useful and powerful ways to check for a substring is to use regular expressions, or regex. Using regex for a task like this gives you a lot more flexibility than with the previous methods where you can only check for a constant string. While regex is too big of a subject to completely cover here, we can at least take a look at some of the useful features for our use-case.
Checking for substrings in a string with regex can be achieved using the RegExp.test()
method:
Check out our hands-on, practical guide to learning Git, with best-practices, industry-accepted standards, and included cheat sheet. Stop Googling Git commands and actually learn it!
> let str = 'stackabuse';
> /stack/.test(str);
true
Unlike the previous two methods, we can now do case insensitive searches with the i
flag:
> let str = 'StackAbuse';
> /stack/i.test(str);
true
As a more complex example, let’s say you want to see if a string contains a zip code (5 digit postal codes), but you don’t really care what zip code is in the string. This kind of problem can’t be solved using includes()
or indexOf()
. But with regex, we can easily test this:
> let str = 'My zip code is 90210';
> /d{5}/.test(str);
true
> str = 'My address is 123 Fake St.';
> /d{5}/.test(str);
false
While JavaScript isn’t necessarily known for its speed anyway, keep in mind that this will be slower than a simpler method like includes()
, so you should only use it for the more complex cases that can’t be solved with a simpler solution.
Conclusion
Manipulating and inspecting strings is one of the most common tasks performed in many programming languages, especially with user-facing code. For many different reasons, like validating strings, you’ll need to check if a string contains a substring. In this article we saw a few different methods provided by JavaScript that allows you to do exactly that.
In this Python tutorial we will cover below topics
- check if a string contains another substring
- Match exact substring inside string
- case-insensitive match
“in” and “not in” operators
The operators in
and not in
test for membership in Python. This is the best and most used method to check if Python string contains another string. As it has been identified that this operator has the least performance impact compared to other methods which I will show you in this tutorial. These operators would return boolean expression i.e. either True
or False
NOTE:
Empty strings are always considered to be a substring of any other string, so ""
in "abc"
will return True
.
#!/usr/bin/env python3 string = 'Entertainment' substring = 'ent' # returns boolean value print(substring in string) # based on the return value, the condition statement would be executed if substring in string: print('Found') else: print('Not Found')
Output from this script:
# python3 /tmp/check_string.py
True
Found
Similarly we can verify the use case for «not in
» operator. The operator not in
is defined to have the inverse truth value of in
.
#!/usr/bin/env python3 string = 'Entertainment' substring = 'ent' # returns boolean value print(substring not in string) # based on the return value, the condition statement would be executed if substring not in string: print('Not Found') else: print('Found')
Output from this script:
# python3 /tmp/check_string.py
False
Found
ALSO READ: Python strip() function Explained [Easy Examples]
case-insensitive match
To perform case-insensitive match of substrings within a string in Python can be achieved using a couple of methods.
Method 1: Using upper() or lower()
We can use str.upper()
or str.lower()
to convert the case of the string and then perform the match between strings and sub strings using «in
» or «not in
» operator
#!/usr/bin/env python3 string = 'Entertainment' substring = 'ENT' # returns boolean value print(substring.lower() in string.lower()) # based on the return value, the condition statement would be executed if substring.lower() in string.lower(): print('Found') else: print('Not Found')
Output from this script:
# python3 /tmp/check_string.py
True
Found
Method 2: Using regex search
Using regex
gives you more flexibility to search for a string. re.search
will scan through string looking for the first location where the regular expression pattern produces a match, and return a corresponding match object. It will return «None
» if no position in the string matches the pattern
The syntax would be:
re.search(pattern, string, flags=re.IGNORECASE)
We will use this regex search in our sample script
#!/usr/bin/env python3 import re string = 'Entertainment' substring = 'ENT' # returns match object print(re.search(substring, string, flags=re.IGNORECASE)) # based on the return value, the condition statement would be executed if re.search(substring, string, flags=re.IGNORECASE): print('Found') else: print('Not Found')
Output from this script:
# python3 /tmp/check_string.py
<_sre.SRE_Match object; span=(0, 3), match='Ent'>
Found
ALSO READ: HackerRank Solution: Python Merge the Tools [Strings]
Check for substrings in strings using str.index()
str.index()
can be used to lookup for the index
value of the first match of a pattern or substring inside a string. This will return the starting index number of the first occurrence of the match (if found) or it will raise a «ValueError
» exception
The syntax would be:
str.index(sub[, start[, end]])
Here you can define the starting and ending index
number to search for a substring inside a string
#!/usr/bin/env python3 string = 'Entertainment' substring = 'ent' # search for substring inside string print(string.index(substring)) # search for substring after index no 5 and before the last available index no print(string.index(substring, 5, len(string)))
Output from this script:
# python3 /tmp/check_string.py
10
10
If the match is Not Found then we get valueError
exception:
# python3 /tmp/check_string.py
Traceback (most recent call last):
File "/tmp/check_string.py", line 7, in <module>
print(string.index(substring))
ValueError: substring not found
We can suppress this using try except else
block:
#!/usr/bin/env python3 string = 'Entertainment' substring = 'ENT' # a = full string and b = substring def check_string(a, b): try: # check if b substring is present in a string a.index(b) # Raise exception for ValueError and instead print False except ValueError: print('False') else: print('True') check_string(string, substring)
Output from this script:
# python3 /tmp/check_string.py
False
HINT:
This method is not very useful if your intention is only search for substring inside a string, this function can be used if you have a requirement to search at a particular index location for a pattern or substring
ALSO READ: Python Anonymous Function [In-Depth Tutorial]
Check for substring in string using str.find()
You can choose str.find
over str.index
as here we don’t have to worry about handling exceptions. This method also returns the lowest index in the string where substring sub is found but if a pattern or subtring is not found then this will return «-1
«
The syntax would be:
str.find(sub[, start[, end]])
Below sample python script would return the index value when the first occurrence of substring is found in string
#!/usr/bin/env python3 string = 'Entertainment' substring = 'ent' # returns index value if match found and -1 if no match print('Value: ', string.find(substring))
Output from this script:
# python3 /tmp/check_string.py
Value: 10
If there is No Match
found, then the output would be:
# python3 /tmp/check_string.py
Value: -1
HINT:
The find()
method should be used only if you need to know the position of sub. To check if sub is a substring or not, use the in
or not in
operator
Use regular expressions (re.search)
We used re.search
earlier in this tutorial to perform case insensitive check for substring in a string. We can use the same method for case sensitive match without using flags = re.IGNORECASE
The re
module is not an inbuilt function so we must import this module. Either we can import all the contents of re
module or we can only import search
from re
#!/usr/bin/env python3 import re string = 'Entertainment' substring = 'ent' # returns match object print(re.search(substring, string)) # based on the return value, the condition statement would be executed if re.search(substring, string): print('Found') else: print('Not Found')
Output from this script:
# python3 /tmp/check_string.py
<_sre.SRE_Match object; span=(10, 13), match='ent'>
Found
If there is No match then re.search
would return None
and the output would be:
# python3 /tmp/check_string.py
None
Not Found
ALSO READ: Convert entire dataframe to lower case [SOLVED]
Match exact substring inside string
The above methods only checks for a sub inside string but that is not an exact match. For example abc
will match abcd
, dabc
and abc
. So if your requirement is to match for exact string i.e. abc
then we must use word boundaries with re.search
.
What are word boundaries “b” ?
b
matches the empty string, but only at the beginning or end of a word. A word is defined as a sequence of word characters. Note that formally, b
is defined as the boundary between a w
and a W
character (or vice versa), or between w
and the beginning/end of the string. This means that r'bfoob'
matches 'foo'
, 'foo.'
, '(foo)'
, 'bar foo baz'
but not 'foobar'
or 'foo3'
.
The syntax to be used would be:
re.search(r'b'+substring+r'b',string)
If you are using a variable with re.search
then you must escape the substring using:
re.search(r'b'+re.escape(substring)+r'b',string)
Let us use this in our example python script:
#!/usr/bin/env python3 import re string = 'abc abcd def' substring = 'abc' # returns match object if found or returns None if not found print(re.search(r'b'+re.escape(substring)+r'b', string))
The output from this script:
# python3 /tmp/check_string.py
<_sre.SRE_Match object; span=(0, 3), match='abc'>
But the substring is not matched then the output would be None
# python3 /tmp/check_string.py
None
ALSO READ: Python function return multiple values [SOLVED]
Conclusion
In this tutorial we learned about different methods available in Python to check if a string contains substring. Each of these methods have their own pros and cons so based on your requirement you may choose the appropriate function. In most cases the in operator should suffice the requirement using «in
» operator.
Lastly I hope this article on Python programming was helpful. So, let me know your suggestions and feedback using the comment section.