Регулярные выражения как найти массив

I am trying to implement search filter and I have array:

Var aa = ["Sage","Salsify (usually Purple Salsify or Oyster Plant)"];

var regex = RegExp("[a-zA-Z]?");
var wordList = vegetables.filter((elem, index)=>{
    return regex.test(elem);
})

I think this will match only characters but how to get full string "Salsify (usually Purple Salsify or Oyster Plant)" when user type Salsify?

asked Jul 9, 2020 at 14:50

kittu's user avatar

1

For a simplistic filter, you could use String.prototype.includes():

var aa = ["Sage","Salsify (usually Purple Salsify or Oyster Plant)"];
var query = 'salsify';

var wordList = aa.filter((elem, index)=> elem.toLowerCase().includes(query.toLowerCase()));
console.log(wordList);

To use a regular expression, your RegExp would need to be built dynamically based on the search query:

var aa = ["Sage","Salsify (usually Purple Salsify or Oyster Plant)"];
var query = 'salsify';

var expr = new RegExp(query, "gi");

var wordList = aa.filter((elem, index)=> expr.test(elem));
console.log(wordList);

answered Jul 9, 2020 at 14:58

Anthony's user avatar

AnthonyAnthony

6,3622 gold badges17 silver badges33 bronze badges

2

If you do:

aa.filter((e) => /.*Salsify.*/.test(e))

You’ll get every element of aa in which the string Salsify appears.

answered Jul 9, 2020 at 14:57

Nathan Chappell's user avatar

Регулярные выражения — тема, которой боятся многие разработчики. Между тем они требуются в большинстве вакансий уровня middle. В статье разберём основные символы и способы создания регулярных выражений в JavaScript, которые с небольшими отличиями работают и в других языках программирования.

Рассмотрим тему регулярных выражений от простого к сложному.

Что такое регулярные выражения

Регулярные выражения (ещё их называют Regular Expressions, сокращённо regex или regexp, регулярки) — специальные шаблоны, которые используют для поиска и обработки текста. Для поиска в них можно задавать дополнительные команды, например игнорирование регистра.

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

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

Например, этот шаблон ищет email-адреса:

/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$/

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

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

Например, нам нужно проверить номер телефона в форме регистрации:

    function validatePhoneNumber(phoneNumber) {

  const phoneRegex = /^(+7|8)?(d{10})$/;

  return phoneRegex.test(phoneNumber);

}

// пример использования функции

 let phoneNumber = "+79123456789";

if (validatePhoneNumber(phoneNumber)) {

  console.log("Номер телефона валиден!");

} else {

  console.log("Номер телефона невалиден!");

}

В этом примере мы создали функцию validatePhoneNumber, которая принимает номер телефона в качестве аргумента и возвращает true, если номер телефона является действительным номером для России, и false в противном случае.

В регулярном выражении phoneRegex мы использовали символ ^, чтобы указать, что номер телефона должен начинаться с «+7» или «8» (возможно, без плюса). Затем мы использовали символы d для указания цифр и {10} для указания количества цифр в номере телефона (10 цифр).

В примере использования мы передали строку «+79123456789» функции validatePhoneNumber и проверили результат. 

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

1. Конструктор RegExp()

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

const regex = new RegExp("pattern", "flags");

Где «pattern» — это строка, содержащая регулярное выражение, а «flags» — дополнительные флаги, определяющие, как будет работать регулярное выражение. Например, флаг i указывает, что регулярное выражение должно игнорировать регистр символов.

Вот пример использования конструктора RegExp() для создания регулярного выражения, которое ищет все цифры в строке:

const regex = new RegExp("\d", "g");

Здесь мы передаём строку d в качестве первого аргумента, что означает «любая цифра». Флаг g указывает, что регулярное выражение должно искать все совпадения в строке.

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

2. Литералы

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

Вот как он выглядит:

const regex = /pattern/flags;

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

const regex = /d/g;

Здесь мы используем литерал /d/g, который означает «любая цифра» с флагом g, указывающим, что регулярное выражение должно искать все совпадения в строке.

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

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

Базовый синтаксис регулярных выражений в JavaScript

Разберём основу синтаксиса регулярных выражений в JavaScript.

Шаблоны и метасимволы

Шаблоны — набор символов, которые используют для поиска определённых сочетаний символов в тексте. Метасимволы — специальные символы, которые используют для составления шаблонов.

Вот несколько примеров шаблонов и метасимволов:

. — соответствует любому одиночному символу, кроме символа новой строки.

* — соответствует нулю или более повторениям предыдущего символа.

+ — соответствует одному или более повторениям предыдущего символа.

? — соответствует нулю или одному повторению предыдущего символа.

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

$ — соответствует концу строки.

() — определяет группу символов, которые могут быть повторены.

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

Квантификаторы — метасимволы, которые определяют количество повторений предыдущего символа. 

Некоторые квантификаторы:

{n} — соответствует ровно n повторениям предыдущего символа.

{n,m} — соответствует от n до m повторений предыдущего символа.

{n,} — соответствует n или более повторениям предыдущего символа.

? — делает предыдущий квантификатор ленивым, т. е. соответствует наименьшему возможному количеству повторений.

Пример использования квантификатора:

const pattern = /a{3}/; // соответствует трём символам "a" подряд

В тексте, который будет обрабатываться с помощью этого регулярного выражения, будут найдены все вхождения трёх символов a подряд. Например, строка «baaaad» будет соответствовать шаблону, а строка «abaa» — нет.

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

Некоторые модификаторы:

g — глобальный поиск, ищет все совпадения в строке.

i — игнорирование регистра, игнорирует различия между верхним и нижним регистром.

m — многострочный поиск, позволяет искать совпадения в нескольких строках.

Пример использования модификатора:

const pattern = /test/gi; // соответствует строкам "test" или "Test" или "TEST" глобально и без учёта регистра

Группы и обратные ссылки

Группы — последовательности символов, заключённые в скобки (), которые могут повторяться с помощью квантификаторов. Группы также позволяют использовать обратные ссылки, которые ссылаются на результат совпадения группы в регулярном выражении. Обратная ссылка обозначается символом и номером группы, например, 1 ссылается на первую группу.

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

​​const pattern = /(w+)s1/; // соответствует повторяющейся последовательности символов, разделённой пробелом

В этом примере группа (w+) соответствует любой последовательности символов, состоящей из буквенно-цифровых символов и подчёркивания. Затем s соответствует пробелу, а 1 обратно ссылается на первую группу (w+), так что регулярное выражение соответствует только тем строкам, в которых последовательность символов повторяется через пробел.

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

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

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

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

Примеры:

  • Поиск всех цифр в тексте.
    let str = "Моя любимая цифра — 7, а у моего друга — 3.";
const regex = /d/g; // экранируем метасимвол "d", который обозначает цифру
let result = str.match(regex);
console.log(result); // ["7", "3"]

  • Замена всех пробелов на символ подчёркивания.
    let str = "Это пример строки с несколькими пробелами.";
const regex = /s/g; // экранируем метасимвол "s", который обозначает пробел
let result = str.replace(regex, "_");
console.log(result); // Это_пример_строки_с_несколькими_пробелами.
  • Замена всех точек в тексте на запятые, кроме точек, которые находятся внутри чисел.
    let str = "По отчёту за 2022 год выручка составила 12.345.678 рублей.";
const regex = /(?<!d).|.(?!d)/g; // экранируем точку, используем негативный и позитивный просмотр вперёд и назад для исключения точек, находящихся внутри чисел
let result = str.replace(regex, ",");
console.log(result); // По отчёту за 2022 год выручка составила 12,345,678 рублей.

Как использовать регулярки с методами объекта RegExp и String

После того как базовый синтаксис и способы написания шаблонов усвоены, перейдём к использованию регулярок в работе. Для работы с регулярными выражениями в JavaScript есть несколько методов — рассмотрим каждый.

Метод test()

Метод test() проверяет, соответствует ли регулярное выражение заданной строке. Метод возвращает true, если строка соответствует регулярному выражению, и false, если не соответствует.

Пример использования метода test():

    let str = "Hello, world!";
const pattern = /Hello/;

if (pattern.test(str)) {
  console.log("String contains 'Hello'");
} else {
  console.log("String does not contain 'Hello'");
}

В этом примере мы используем метод test() для проверки, содержит ли строка str подстроку «Hello». Регулярное выражение /Hello/ соответствует строке «Hello», и метод test() вернёт true.

Метод exec()

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

Пример использования метода exec():

    let str = "Hello, world!";
const pattern = /Hello/;

let result = pattern.exec(str);

console.log(result); // ["Hello", index: 0, input: "Hello, world!"]

В этом примере мы используем метод exec() для поиска совпадений регулярного выражения /Hello/ в строке str. Метод exec() возвращает массив, содержащий найденное совпадение «Hello», индекс первого символа совпадения в исходной строке и саму исходную строку.

Метод match()

Метод match() используется для поиска всех совпадений регулярного выражения в заданной строке. Метод возвращает массив, содержащий все найденные совпадения.

Пример использования метода match():

    let str = "The quick brown fox jumps over the lazy dog.";
const pattern = /the/gi;

let result = str.match(pattern);

console.log(result); // ["the", "the"]

В этом примере мы используем метод match() для поиска всех совпадений регулярного выражения /the/gi в строке str. Метод match() возвращает массив, содержащий все найденные совпадения, включая повторения.

Метод replace()

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

Пример использования метода replace():

    let str = "Привет, мир!";
const pattern = /мир/;

let newstr = str.replace(pattern, "земля");

console.log(newstr); // "Привет, земля!"

В этом примере мы используем метод replace() для замены слова «мир» на «земля» в строке «Привет, мир!» Регулярное выражение /мир/ ищет все совпадения со словом «мир» в строке, и метод replace() заменяет их на слово «земля». Результатом работы метода является новая строка «Привет, земля!»

Метод search()

Метод search() принимает один аргумент — регулярное выражение. Он ищет первое совпадение с заданным регулярным выражением в исходной строке и возвращает индекс первого символа совпадения. Если совпадение не найдено, метод возвращает -1.

Например:

    let str = "Это текст для примера";
const pattern = /текст/;

let index = str.search(pattern);

console.log(index); // 4

В этом примере мы используем метод search() для поиска первого совпадения со словом «текст» в строке «Это текст для примера». Регулярное выражение /текст/ ищет первое совпадение со словом «текст» в строке, и метод search() возвращает индекс первого символа этого совпадения, который равен 4.

Метод split()

Метод split() принимает один аргумент — регулярное выражение. Он разбивает исходную строку на массив подстрок, используя заданное регулярное выражение как разделитель.

Пример использования метода split():

    let str = "Это текст для примера";
const pattern = / /;

let words = str.split(pattern);

console.log(words); // ["Это", "текст", "для", "примера"]

В этом примере мы используем метод split() для разделения строки «Это текст для примера» на массив слов, используя регулярное выражение / / в качестве разделителя. Регулярное выражение / / ищет все пробелы в строке, и метод split() использует их в качестве разделителя для разделения строки на массив слов. Результатом работы метода является массив [«Это», «текст», «для», «примера»].

Когда и как использовать регулярки на практике

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

Разберём подробнее несколько вариантов использования.

Валидация email-адресов

Один из популярных примеров использования регулярных выражений — валидация email-адресов.

Шаблон будет выглядеть так:

/^[^s@]+@[^s@]+.[^s@]+$/, где

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

@ — символ «@»

. — символ «.» (экранированный с помощью слеша)

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

Таким образом, этот паттерн соответствует любому email-адресу, который состоит из локальной части (часть до символа «@») и доменной части (часть после символа «@» и до символа «.») с верным форматом.

Для проверки email-адреса на соответствие шаблону можно использовать метод test():

    function validateEmail(email) {
  const regex = /^[^s@]+@[^s@]+.[^s@]+$/;
  return regex.test(email);
}

console.log(validateEmail("user@example.com")); // true
console.log(validateEmail("invalid@.com")); // false
console.log(validateEmail("invalidemail.com")); // false

В этом примере функция validateEmail() принимает email-адрес в качестве аргумента и использует регулярное выражение для проверки его соответствия формату. Метод test() возвращает true, если email-адрес соответствует паттерну, и false в противном случае.

Форматирование текста

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

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

Допустим, у нас есть дата в формате «гггг-мм-дд» (например, «2023-03-29»), а мы хотим преобразовать её в формат «дд.мм.гггг» (например, «29.03.2023»). Для этого мы можем использовать регулярное выражение, чтобы разделить дату на отдельные части, а затем использовать эти части для создания новой строки в нужном формате.

    let date = "2023-03-29";
const regex = /^(d{4})-(d{2})-(d{2})$/;
let match = date.match(regex);
let formattedDate = match[3] + "." + match[2] + "." + match[1];
console.log(formattedDate); // "29.03.2023"

Давайте разберём это регулярное выражение:

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

(d{4}) — группа, соответствующая 4 цифрам (год)

— — символ «-«

(d{2}) — группа, соответствующая 2 цифрам (месяц)

— — символ «-«

(d{2}) — группа, соответствующая 2 цифрам (день)

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

В этом примере мы используем метод match() для поиска соответствия регулярному выражению в строке date. Если соответствие найдено, метод match() возвращает массив, содержащий соответствующие группы в порядке их появления в регулярном выражении. Затем мы используем эти группы для создания новой строки в нужном формате.

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

Извлечение номеров телефонов из строки

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

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

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

Для этого вы можете использовать следующий код:

    const string = "Мой номер телефона: +7 (999) 123-45-67. Номер телефона моего друга: +7 (987) 654-32-10.";
const regex = /+ds?(d{3})s?d{3}-d{2}-d{2}/g;
const phoneNumbers = string.match(regex);

console.log(phoneNumbers);
// Output: ["+7 (999) 123-45-6	7", "+7 (987) 654-32-10"]

В этом примере мы используем регулярное выражение /+ds?(d{3})s?d{3}-d{2}-d{2}/g, чтобы извлечь номера телефонов из строки string.

Регулярное выражение ищет символ «+», и за ним следует одна или несколько цифр, за которыми следует необязательный пробел. Затем идут открывающая и закрывающая скобки, за которыми следуют три цифры, после чего снова идёт необязательный пробел. Далее идут три цифры, тире, две цифры, тире и ещё две цифры. В результате, если в строке есть номер телефона в формате, соответствующем регулярному выражению, он будет извлечён и сохранён в массиве phoneNumbers.

Ещё несколько советов по использованию регулярок

  • Пишите максимально простые регулярные выражения и не усложняйте код без необходимости. Чем проще регулярное выражение, тем быстрее и эффективнее оно будет работать.
  • Тестируйте регулярные выражения перед использованием. Это позволит выявить возможные ошибки или проблемы в выражении и сделать необходимые исправления. Используйте онлайн-сервисы, такие как regex101.com, regexr.com, regexplanet.com и подобные. 
  • Не злоупотребляйте использованием регулярок. Иногда другие простые методы могут оказаться более эффективными и лёгкими в понимании. 
  • Используйте комментарии и форматирование кода, чтобы облегчить его чтение и обслуживание. Регулярные выражения могут быть очень сложными для понимания. 
  • Используйте библиотеки. Есть множество библиотек уже готовых шаблонов — часто этого достаточно для решения задачи.

Заключение

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

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

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

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

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

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

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

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

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

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

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

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

Содержание

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

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

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

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

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

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

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

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

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

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

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

  12. Замена

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

  14. Итого

Где пощупать

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

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

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

  3. Myregexp

  4. Regexr

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

Поиск текста

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

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

Regex: море

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

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

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

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

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

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

Regex: море

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

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

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

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

Regex: море

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

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

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

Regex: корабл

Найдет:

На корабле

И тут корабль

У корабля

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

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

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

Текст:

Аня

Ася

Оля

Аля

Валя

Regex: А.я

Результат:

Аня

Ася

Оля

Аля

Валя

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

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

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

А6я

А&я

А я

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

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

Regex: file.

Найдет:

file.txt

file1.txt

file2.xls

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

Regex: .txt

Результат:

file.txt

log.txt

file.png

1txt.doc

one_txt.jpg

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

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

Regex: .txt

Результат:

file.txt

log.txt

file.png

1txt.doc

one_txt.jpg

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

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

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

. — точка

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

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

Regex: А..а

Результат:

Анна

Алла

аоикА74арплт

Аркан

А^&а

Абба

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

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

Результат:

Анна

Алла

аоикА74арплт

Аркан

А^&а

Абба

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • И число 1

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

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

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

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

Аня

Ася

Аля

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

А6я

А&я

А я

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

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

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

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

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

Regex: [^0-9].txt

Результат:

file.txt

log.txt

file_1.txt

1.txt

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

Regex: fruits[0]

Найдет: fruits0

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

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

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

Regex: fruits[0]

Найдет: fruits[0]

Не найдет: fruits0

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

Regex: fruits[[0-9]]

Найдет:

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

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

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

Не найдет:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Regex: fruits[[0-9]]

Найдет:

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

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

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

Не найдет:

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

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

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

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

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

  • 2 цифры дня

  • точка

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

  • точка

  • 4 цифры года

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

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

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

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

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

Найдет:

01.01.1999

05.08.2015

Тоже найдет:

08.08.8888

99.99.2000

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

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

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

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

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

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

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

Не найдет:

08.08.8888

99.99.2000

Но найдет:

33.01.2000

01.19.1999

05.06.2999

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

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

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

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

Найдет:

Оля

Олечка

Котик

Не найдет:

Оленька

Котенка

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

Regex: А(н|л)я

Найдет:

Аня

Аля

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

Regex: Ан|ля

Найдет:

Аня

Аля

Оля

Малюля

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

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

  • А(н|л)я

  • А[нл]я

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

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

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

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

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

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

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

  • 0[1-9]

  • [12][0-9]

  • 3[01]

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

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

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

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

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

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

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

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

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

  • ИЛИ   0[1-9]

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

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

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

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

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

Найдет:

Анна

Алла

Алина

Антонина

Без скобок:

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

Найдет:

Анна

Алла

Аннушка

Кукулинка

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

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

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

Метасимволы

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

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

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

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

Символ

Эквивалент

Пояснение

d

[0-9]

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

D

[^0-9]

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

s

[ fnrtv]

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

S

[^ fnrtv]

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

w

[[:word:]]

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

W

[^[:word:]]

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

.

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

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

Символ

Пояснение

Пробел

r

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

n

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

t

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

v

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

f

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

[b]

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

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

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

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

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

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

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

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

console.log("abcbbdef");

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

adef

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

См также:

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

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

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

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

Пояснение

[[:alnum:]]

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

[[:alpha:]]

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

[[:digit:]]

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

[[:graph:]]

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

[[:print:]]

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

[[:space:]]

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

[[:punct:]]

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

[[:word:]]

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

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

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

dd.dd.dddd

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

Спецсимволы

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

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

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

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

Regex: 2^2 = 4

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

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

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

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

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

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

  • test@mail.ru

  • olga31@gmail.com

  • pupsik_99@yandex.ru

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

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

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

  • Потом @

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

  • Точка

  • Буквы

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

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

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

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

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

Regex: w+@w+.w+

Найдет:

test@mail.ru

olga31@gmail.com

pupsik_99_and_slonik_33_and_mikky_87_and_kotik_28@yandex.megatron

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

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

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

?

Ноль или одно

*

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

+

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

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

Regex: .*dd.dd.dddd.*

Найдет:

01.01.2000

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

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

Regex: .*@.*..*

Найдет:

test@mail.ru

olga31@gmail.com

pupsik_99@yandex.ru

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

@yandex.ru

test@.ru

test@mail.

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

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

Regex: logd*.txt

Найдет:

log.txt

log1.txt

log2.txt

log3.txt

log33.txt

log133.txt

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

Regex: Назина?

Найдет:

Назин

Назина

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

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

Найдет:

ХихиХаха

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

Хихи

Хаха

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

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

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

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

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

Regex: d*.d*.d*

Найдет:

.0.1999

05.08.20155555555555555

03444.025555.200077777777777777

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

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

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

{n}

Ровно n раз

{m,n}

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

{m,}

Не менее m

{,n}

Не более n

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

dd.dd.dddd

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

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

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

Regex: data{2}

Найдет: dataa

Не найдет: datadata

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

Regex: (data){2}

Найдет: datadata

Не найдет: dataa

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

Regex: x{3}

Найдет: x{3}

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

Regex: <.*>

Ожидание:

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

Реальность:

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

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

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

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

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

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

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

<[^>]*>

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

Жадный

Ленивый

*

*?

+

+?

{n,}

{n,}?

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

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

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

Regex: арка

Найдет:

арка

чарка

аркан

баварка

знахарка

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

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

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

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

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

Или так:

Regex: .* арка .*

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

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

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

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

Regex: bаркаb

Найдет:

арка

Не найдет:

чарка

аркан

баварка

знахарка

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

Regex: bарка

Найдет:

арка

аркан

Не найдет:

чарка

баварка

знахарка

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

Regex: аркаb

Найдет:

арка

чарка

баварка

знахарка

Не найдет:

аркан

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

Regex: BакрB

Найдет:

закройка

Не найдет:

акр

акрил

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

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

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

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

Regex: ^Я нашел!$

Найдет:

Я нашел!

Не найдет:

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

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

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

Символ

Значение

b

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

B

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

^

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

$

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

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

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

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

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

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

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

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

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

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

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

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

ЯП

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

JavaScript

vi

Perl

$

PHP

$matches[1]

Java

Python

group[1]

C#

match.Groups[1]

Visual Basic .NET

match.Groups(1)

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

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

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

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

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

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

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

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

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

Пример

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

(?=шаблон)

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

Блюдо(?=11)

Блюдо1

Блюдо11

Блюдо113

Блюдо511

(?!шаблон)

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

Блюдо(?!11)

Блюдо1

Блюдо11

Блюдо113

Блюдо511

(?<=шаблон)

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

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

Ольга Назина

Анна Назина

(?шаблон)

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

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

Ольга Назина

Анна Назина

Замена

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

RegEx: Ольга

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

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

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

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

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

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

Замена: $1

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

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

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

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

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

Замена: $1

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

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

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

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

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

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

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

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

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

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

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

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

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

Привет, 777!

Текст стал:

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

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

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

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

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

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

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

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

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

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

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

Вот и всё!

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

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

ДД.ММ.ГГГГ

ГГГГ-ММ-ДД

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

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

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

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

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

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

Вот и всё!

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

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

Текст был:

05.08.2015

01.01.1999

03.02.2000

Текст стал:

2015-08-05

1999-01-01

2000-02-03

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

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

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

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

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

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

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

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

RegEx: d+. (.*)

Замена: $1

Текст был:

1. Раз

2. Два

Текст стал:

Раз

Два

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

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

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

Книги

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

Статьи

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

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

Итого

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

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

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

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

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

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

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

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

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

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

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

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

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

Регулярные выражения (RegExp) — это очень эффективный способ работы со строками.

Составив регулярное выражение с помощью специального синтаксиса вы можете:

  • искать текст в строке
  • заменять подстроки в строке
  • извлекать информацию из строки

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

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

Регулярные выражения реализованные в  UNIX, таких как grep, sed и популярных текстовых редакторах, начали набирать популярность и были добавлены в язык программирования Perl, а позже и в множество других языков.

JavaScript, наряду с Perl, это один из языков программирования в котором поддержка регулярных выражений встроена непосредственно в язык.

Сложно, по полезно

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

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

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

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

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

Как выглядят регулярные выражения

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

Первый способ заключается в создании нового объекта RegExp с помощью конструктора:

const re1 = new RegExp('hey')

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

const re1 = /hey/

Вы знаете что в JavaScript есть литералы объектов и литералы массивов? В нём также есть литералы regexp.

В приведённом выше примере hey называется шаблоном. В литеральной форме он находится между двумя слэшами, а в случае с конструктором объекта, нет.

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

Как они работают?

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

Это довольно просто.

Вы можете попробовать протестировать регулярное выражение с помощью метода RegExp.test(String), который возвращает логическое (boolean) значение:

re1.test('hey')                     // ✅
re1.test('blablabla hey blablabla') // ✅

re1.test('he')        // ❌
re1.test('blablabla') // ❌

В примере выше мы просто проверили удовлетворяет ли "hey" шаблону регулярного выражения, который храниться в re1.

Это проще простого, но вы уже знаете много о регулярных выражениях.

Закрепление

/hey/

сработает независимо от того где находится hey внутри строки.

Если вы хотите найти строки, которые начинаются с hey, то используйте оператор ^:

/^hey/.test('hey')     // ✅
/^hey/.test('bla hey') // ❌

Если вы хотите найти строки, которые заканчиваются на hey, то используйте оператор $:

/hey$/.test('hey')     // ✅
/hey$/.test('bla hey') // ✅
/hey$/.test('hey you') // ❌

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

/^hey$/.test('hey') // ✅

Чтобы найти строку начинающуюся с одной подстроки, а заканчивающуюся другой подстрокой вы можете использовать .*, который будет совпадать с любым символом повторяющимся 0 или более раз:

/^hey.*joe$/.test('hey joe')             // ✅
/^hey.*joe$/.test('heyjoe')              // ✅
/^hey.*joe$/.test('hey how are you joe') // ✅
/^hey.*joe$/.test('hey joe!')            // ❌

Поиск элементов по диапазону

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

/[a-z]/ // a, b, c, ... , x, y, z
/[A-Z]/ // A, B, C, ... , X, Y, Z
/[a-c]/ // a, b, c
/[0-9]/ // 0, 1, 2, 3, ... , 8, 9

Эти регулярные выражения ищут строки, которые содержат хотя бы один символ из выбранного диапазона:

/[a-z]/.test('a')  // ✅
/[a-z]/.test('1')  // ❌
/[a-z]/.test('A')  // ❌

/[a-c]/.test('d')  // ❌
/[a-c]/.test('dc') // ✅

Диапазоны можно комбинировать:

/[A-Za-z0-9]/
/[A-Za-z0-9]/.test('a') // ✅
/[A-Za-z0-9]/.test('1') // ✅
/[A-Za-z0-9]/.test('A') // ✅

Поиск многократных совпадений элемента диапазона

Вы можете проверить содержит ли строка только один символ из диапазона с помощью символа -:

/^[A-Za-z0-9]$/

/^[A-Za-z0-9]$/.test('A')  // ✅
/^[A-Za-z0-9]$/.test('Ab') // ❌

Инверсия шаблона

Символ ^ в начале шаблона привязывает его к началу строки.

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

/[^A-Za-z0-9]/.test('a') // ❌
/[^A-Za-z0-9]/.test('1') // ❌
/[^A-Za-z0-9]/.test('A') // ❌
/[^A-Za-z0-9]/.test('@') // ✅

Метасимволы

  • d совпадает с любым числом, эквивалентно [0-9]
  • D совпадает с любым символом, который не является числом, эквивалентно [^0-9]
  • w совпадает с любым буквенно-числовым символом, эквивалентно [A-Za-z0-9]
  • W совпадает с любым символом, который не является буквенно-числовым значением, эквивалентно [^A-Za-z0-9]
  • s совпадает с любым пробельным символом: пробел, табуляция, символ новой строки и пробелы Unicode
  • S совпадает с любым символом, который не является пробелом
  • совпадает с null
  • n совпадает с символом новой строки
  • t совпадает с символом табуляции
  • uXXXX совпадает с символом Unicode с кодом XXXX (требуется флаг u)
  • . совпадает с любым символовом, кроме символа новой строки (таким как n) (если вы не используете флаг s, объясним позже)
  • [^] совпадает с любым символом, включая символ новой строки. Полезно при работе с многострочными строками

Выбор в регулярных выражениях

Если вы хотите выбрать одну или другую строку, используйте оператор |.

/hey|ho/.test('hey') // ✅
/hey|ho/.test('ho')  // ✅

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

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

/^d$/

Вы можете использовать квантификатор ?, который сделает этот символ необязательным. В нашем случае цифра должна встречаться 0 или 1 раз:

/^d?$/

но что если мы хотим чтобы регулярное выражение срабатывало на несколько цифр?

Вы можете сделать это 4 способами, используя +, *, {n} и {n,m}.

+

Совпадает с одним или более (>=1) элементами:

/^d+$/

/^d+$/.test('12')     // ✅
/^d+$/.test('14')     // ✅
/^d+$/.test('144343') // ✅
/^d+$/.test('')       // ❌
/^d+$/.test('1a')     // ❌

*

Совпадает с 0 или более (>=0) элементами:

/^d+$/

/^d*$/.test('12')     // ✅
/^d*$/.test('14')     // ✅
/^d*$/.test('144343') // ✅
/^d*$/.test('')       // ✅
/^d*$/.test('1a')     // ❌

{n}

Совпадает точно с n количеством элементов:

/^d{3}$/

/^d{3}$/.test('123')  // ✅
/^d{3}$/.test('12')   // ❌
/^d{3}$/.test('1234') // ❌

/^[A-Za-z0-9]{3}$/.test('Abc') // ✅

{n,m}

Совпадает с диапазоном от n до m элементов:

/^d{3,5}$/

/^d{3,5}$/.test('123')    // ✅
/^d{3,5}$/.test('1234')   // ✅
/^d{3,5}$/.test('12345')  // ✅
/^d{3,5}$/.test('123456') // ❌

m можно опустить и оставить второй предел без ограничений, чтобы было минимум n элементов:

/^d{3,}$/

/^d{3,}$/.test('12')        // ❌
/^d{3,}$/.test('123')       // ✅
/^d{3,}$/.test('12345')     // ✅
/^d{3,}$/.test('123456789') // ✅

Опциональные элементы

Следующий за элементом знак ?, сделает его необязательным:

/^d{3}w?$/

/^d{3}w?$/.test('123')   // ✅
/^d{3}w?$/.test('123a')  // ✅
/^d{3}w?$/.test('123ab') // ❌

Группы

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

Пример ниже ищет точное совпадение из 3 цифр за которым следует один или более буквенно-числовые символов:

/^(d{3})(w+)$/

/^(d{3})(w+)$/.test('123')          // ❌
/^(d{3})(w+)$/.test('123s')         // ✅
/^(d{3})(w+)$/.test('123something') // ✅
/^(d{3})(w+)$/.test('1234')         // ✅

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

/^(d{2})+$/

/^(d{2})+$/.test('12')   // ✅
/^(d{2})+$/.test('123')  // ❌
/^(d{2})+$/.test('1234') // ✅

Захват групп

До сих пор мы видели, как тестировать строки и проверять, содержат ли они определенный шаблон.

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

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

По умолчанию, группы итак захватываются. Теперь вместо использования RegExp.test(String), который просто возвращает логическое значение, мы будем использовать один из следующих методов:

  • String.match(RegExp)
  • RegExp.exec(String)

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

Если совпадений не найдено, то он возвращает null.

'123s'.match(/^(d{3})(w+)$/)
//Array [ "123s", "123", "123s" ]

/^(d{3})(w+)$/.exec('123s')
//Array [ "123s", "123", "s" ]

'hey'.match(/(hey|ho)/)
//Array [ "hey", "hey" ]

/(hey|ho)/.exec('hey')
//Array [ "hey", "hey" ]

/(hey|ho)/.exec('ha!')
//null

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

'123456789'.match(/(d)+/)
//Array [ "123456789", "9" ]

Опциональные группы

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

/^(d{3})(s)?(w+)$/.exec('123 s') //Array [ "123 s", "123", " ", "s" ]
/^(d{3})(s)?(w+)$/.exec('123s') //Array [ "123s", "123", undefined, "s" ]

Ссылка на найденную группу

Каждой найденной группе присваивается число. $1 ссылается на первый элемент, $2 на второй, и так далее. Это полезно, когда мы будет говорить о замене части строки.

Именованный захват групп

Это новая возможность ES2018.

Группе можно назначить имя, а не просто слот в возвращаемом массиве:

const re = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/
const result = re.exec('2015-01-02')

// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

Именованный захват групп в JS RegExp

Использование match и exec без групп

Существует разница при использовании match и exec без групп: в первом элементе массива будет находится не полностью найденная строка, а прямое совпадение:

/hey|ho/.exec('hey')   // [ "hey" ]

/(hey).(ho)/.exec('hey ho') // [ "hey ho", "hey", "ho" ]

Незахватываемые группы

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

'123s'.match(/^(d{3})(?:s)(w+)$/)
// null
'123 s'.match(/^(d{3})(?:s)(w+)$/)
// Array [ "123 s", "123", "s" ]

Флаги

Вы можете использовать следующие флаги на любых регулярных выражениях:

  • g: ищет совпадения глобально
  • i: делает регулярное выражение не чувствительным к регистру
  • m: включает многострочный режим. В этом режиме ^ и $ совпадают с началом и концом всей строки. Без этого флага, с многострочными строками они совпадают с началом и концом каждой строки.
  • u: включает поддержку Unicode (добавлено в ES6/ES2015)
  • s: (новое в ES2018) сокращение от «single line», он позволяет . совпадать с символами новой строки

Флаги можно комбинировать, а также они добавляются в конец строки литерала:

/hey/ig.test('HEy') // ✅

или передаются вторым параметром в конструктор объекта RegExp:

new RegExp('hey', 'ig').test('HEy') // ✅

Инспектирование регулярных выражений

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

  • source — строка шаблона
  • multiline — принимается значение true если установлен флаг m
  • global — принимается значение true если установлен флаг g
  • ignoreCase — принимается значение true если установлен флаг i
  • lastIndex
/^(w{3})$/i.source     //"^(\d{3})(\w+)$"
/^(w{3})$/i.multiline  //false
/^(w{3})$/i.lastIndex  //0
/^(w{3})$/i.ignoreCase //true
/^(w{3})$/i.global     //false

Экранирование

Специальные символы:

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

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

/^\$/
/^^$/ // /^^$/.test('^') ✅
/^$$/ // /^$$/.test('$') ✅

Границы строк

b и B позволяют определить находится ли строка в начале или конце слова:

  • b совпадает если набор символов находится в начале или конце слова
  • B совпадает если набор символов не находится в начале или конце слова

Пример:

'I saw a bear'.match(/bbear/)    //Array ["bear"]
'I saw a beard'.match(/bbear/)   //Array ["bear"]
'I saw a beard'.match(/bbearb/) //null
'cool_bear'.match(/bbearb/)     //null

Замена с помощью регулярных выражений

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

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

Теперь давайте рассмотрим как заменять части строки на основе шаблона.

У объекта String в JavaScript есть метод replace(), который можно использовать без регулярных выражений для одной замены в строке:

"Hello world!".replace('world', 'dog') //Hello dog!
"My dog is a good dog!".replace('dog', 'cat') //My cat is a good dog!

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

"Hello world!".replace(/world/, 'dog') //Hello dog!

Использование флага g — это единственный способ заменить несколько вхождений в строке на ванильном JavaScript:

"My dog is a good dog!".replace(/dog/g, 'cat') //My cat is a good cat!

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

"Hello, world!".replace(/(w+), (w+)!/, '$2: $1!!!')
// "world: Hello!!!"

Вместо строки можно использовать функцию, чтобы делать ещё более интересные вещи. В неё будет передан ряд аргументов, таких как возвращают методы String.match(RegExp) или RegExp.exec(String), где количество аргументов зависит от количества групп:

"Hello, world!".replace(/(w+), (w+)!/, (matchedString, first, second) => {
  console.log(first);
  console.log(second);

  return `${second.toUpperCase()}: ${first}!!!`
})
//"WORLD: Hello!!!"

Жадность

Регулярные выражения называются жадными по умолчанию.

Что это значит?

Возьмём например это регулярное выражение:

/$(.+)s?/

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

/$(.+)s?/.exec('This costs $100')[1]
//0

но что если у нас есть больше слов после числа, это отвлекает

/$(.+)s?/.exec('This costs $100 and it is less than $200')[1]
//100 and it is less than $200

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

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

/$(.+?)s/.exec('This costs $100 and it is less than $200')[1]
//100

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

Опережение: соответствие строки в зависимости от того что за ней следует

Используйет ?= для поиска совпадений в строке за которой следует определённая подстрока

/Roger(?=Waters)/

/Roger(?= Waters)/.test('Roger is my dog') //false
/Roger(?= Waters)/.test('Roger is my dog and Roger Waters is a famous musician') //true

?! выполняет обратную операцию и находит совпадений в строке за которыми не следует определённая подстрока:

/Roger(?!Waters)/

/Roger(?! Waters)/.test('Roger is my dog') //true
/Roger(?! Waters)/.test('Roger is my dog and Roger Waters is a famous musician') //false

Ретроспектива: соответствие строки в зависимости от того что ей предшествует

Это новая возможность ES2018.

Опережение использует символ ?=. Ретроспектива использует ?<=:

/(?<=Roger) Waters/

/(?<=Roger) Waters/.test('Pink Waters is my dog') //false
/(?<=Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //true

Инверсия ретроспективы использует ?<!:

/(?<!Roger) Waters/

/(?<!Roger) Waters/.test('Pink Waters is my dog') //true
/(?<!Roger) Waters/.test('Roger is my dog and Roger Waters is a famous musician') //false

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

Флаг u является обязательным при работе с Unicode строками, в частности когда может понадобится обрабатывать строки в астральных плоскостях, которые не включены в первые 1600 символов Unicode.

Например эмодзи, но и только они.

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

/^.$/.test('a') // ✅
/^.$/.test('?') // ❌
/^.$/u.test('?') // ✅

Поэтому, всегда используйте флаг u.

Unicode, как и обычные символы, может обрабатывать диапазоны:

/[a-z]/.test('a')  // ✅
/[1-9]/.test('1')  // ✅

/[?-?]/u.test('?')  // ✅
/[?-?]/u.test('?')  // ❌

JavaScript проверяет внутренние коды представления, поэтому ? < ? < ? на самом деле u1F436 < u1F43A < u1F98A. Посмотрите полный список эмодзи чтобы увидеть коды и узнать их порядок.

Экранирование свойств Unicode

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

Экранирование свойств Unicode — это возможность ES2018, которая добавляет очень крутую функцию, расширяя эту концепцию на всех Unicode символы и добавляя p{} и P{}.

У любого Unicode символа есть набор свойств. Например Script определяет семейство языков, ASCII — это логическое значение равное true для ASCII символов и т.д. Вы можете положить это свойство в фигурные скобки и регулярное выражение будет проверять чтобы его значение было истинным:

/^p{ASCII}+$/u.test('abc')   // ✅
/^p{ASCII}+$/u.test('ABC@')  // ✅
/^p{ASCII}+$/u.test('ABC?') // ❌

ASCII_Hex_Digit — это ещё одно логическое свойство, которое проверяет содержит ли строка тольк валидные шестнадцатеричные цифры:

/^p{ASCII_Hex_Digit}+$/u.test('0123456789ABCDEF') //✅
/^p{ASCII_Hex_Digit}+$/u.test('h')   

Существует много других логических свойств, которые вы можете проверить просто добавив их имя в фигурные скобки, включая Uppercase, Lowercase, White_Space, Alphabetic, Emoji и другие:

/^p{Lowercase}$/u.test('h') // ✅
/^p{Uppercase}$/u.test('H') // ✅

/^p{Emoji}+$/u.test('H')   // ❌
/^p{Emoji}+$/u.test('??') // ✅

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

/^p{Script=Greek}+$/u.test('ελληνικά') // ✅
/^p{Script=Latin}+$/u.test('hey') // ✅

Прочитать больше обо всех свойствах вы можете здесь.

Примеры

Извлечение числа из строки

Предположим, что есть строка содержащая только одно число, которое нужно извлечь. /d+/ должен сделать это:

'Test 123123329'.match(/d+/)
// Array [ "123123329" ]

Поиск E-mail адреса:

Простейший подход заключается в проверке безпробельных символов до и после знака @, с помощью S:

/(S+)@(S+).(S+)/

/(S+)@(S+).(S+)/.exec('copesc@gmail.com')
//["copesc@gmail.com", "copesc", "gmail", "com"]

Однако, это упрощенный пример, так как под него попадает множество не валидных E-mail адресов.

Захват текста между двойными кавычками

Представим, что у вас есть строка, которая содержит текст заключённый в двойные кавычки и вам нужно извлечь этот текст.

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

Мы найдём то что нам нужно в result[1]:

const hello = 'Hello "nice flower"'
const result = /"([^']*)"/.exec(hello)
//Array [ ""nice flower"", "nice flower" ]

Получение содержимого из HTML тега

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

/<spanb[^>]*>(.*?)</span>/

/<spanb[^>]*>(.*?)</span>/.exec('test')
// null
/<spanb[^>]*>(.*?)</span>/.exec('<span>test</span>')
// ["<span>test</span>", "test"]
/<spanb[^>]*>(.*?)</span>/.exec('<span class="x">test</span>')
// ["<span class="x">test</span>", "test"]

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

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

JavaScript поддерживает регулярные выражения в синтаксисе Perl. Perl (Practical Extraction and Report Language) был первым распространенным языком программирования, который обеспечивал интегрированную поддержку регулярных выражений и большие возможности обработки и манипулирования текстом.

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

Функция Что оно делает
exec() Поиск совпадения в строке. Возвращает массив информации или null при несовпадении.
test() Проверка, соответствует ли строка шаблону. Возвращает true или false.
search() Поиск совпадения в строке. Возвращает индекс первого совпадения или -1, если не найден.
replace() Поиск совпадения в строке, и замена совпавшей подстроки строкой замены.
match() Поиск совпадения в строке. Возвращает массив информации или null при несовпадении.
split() Разбивает строку на массив подстрок с помощью регулярного выражения.

Методы exec() и test() являются методами RegExp, которые принимают строку в качестве параметра, тогда как методы search(), replace(), match() и split() являются методами String, которые принимают регулярное выражение в качестве параметра.

Определение регулярных выражений

В JavaScript регулярные выражения представлены объектом RegExp, который является нативным объектом JavaScript, таким как String, Array и так далее. Существует два способа создания нового объекта RegExp: один использует литеральный синтаксис, а другой — конструктор RegExp().

Литеральный синтаксис использует косую черту (/pattern/) для переноса шаблона регулярного выражения, тогда как синтаксис конструктора использует кавычки ("pattern"). В следующем примере демонстрируются оба способа создания регулярного выражения, которое соответствует любой строке, начинающейся с «Mr.».

// Литеральный синтаксис
var regex = /^Mr./;

// Синтаксис конструктора
var regex = new RegExp("^Mr\.");

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

При использовании синтаксиса конструктора необходимо дважды экранировать специальные символы; это означает, что для поиска «.» вам нужно написать "\." вместо ".". Если есть только один обратный слеш, он будет интерпретирован синтаксическим анализатором строк JavaScript как экранирующий символ и будет удален.

Сравнение шаблонов с регулярным выражением

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

Символы, которым дается особое значение в регулярном выражении:
. * ? + [ ] ( ) { } ^ $ | . Вам нужно будет использовать обратную косую черту, если вы хотите использовать их буквально. Например, если вы использовать в регулярном выражении знак точка (.), вам нужно писать . Все остальные символы автоматически принимают свои буквальные значения.

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

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

Квадратные скобки, окружающие шаблон символов, называются классом символов, например [abc]. Класс символов всегда соответствует одному символу из списка указанных символов, что означает, что выражение [abc] соответствует только символу a, b или c.

Также могут быть определены классы отрицательных символов, которые соответствуют любому символу, кроме указанных в скобках. Класс отрицанных символов определяется путем размещения символа вставки (^) сразу после открывающей скобки, например [^abc], который соответствует любому символу, кроме a, b и c.

Вы также можете определить диапазон символов, используя символ дефиса (-) внутри класса символов, например [0-9]. Давайте посмотрим на некоторые примеры классов:

RegExp Что оно делает
[abc] Соответствует любому из символов a, b или c.
[^abc] Соответствует любому одному символу, кроме a, b или c.
[a-z] Соответствует любому символу от строчной буквы a до строчной буквы z.
[A-Z] Соответствует любому символу от прописных букв A до прописных букв Z.
[a-Z] Соответствует любому символу от строчной буквы a до прописной буквы Z.
[0-9] Соответствует одной цифре от 0 до 9.
[a-z0-9] Соответствует одному символу между a и z или между 0 и 9.

В следующем примере показано, как определить, существует ли шаблон в строке или нет, используя регулярное выражение с методом JavaScript test():

var regex = /ca[kf]e/;
var str = "He was eating cake in the cafe.";

// Проверяет строку на соответствие регулярному выражению
if(regex.test(str)) {
    alert("Match found!");
} else {
    alert("Match not found.");
}

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

var regex = /ca[kf]e/g;
var str = "He was eating cake in the cafe.";
var matches = str.match(regex);
alert(matches.length); // Выводит: 2

Регулярные выражения не являются исключительными для JavaScript. Такие языки, как Java, Perl, Python, PHP и т. д., используют одинаковые обозначения для поиска шаблонов в тексте.

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

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

Сокращение Что оно делает
. Соответствует любому отдельному символу, кроме новой строки n.
d Соответствует любому числовому символу. Тоже самое [0-9]
D Соответствует любому нечисловому символу. Тоже самое [^0-9]
s Соответствует любому пробельному символу (пробел, табуляция, символ новой строки или символ возврата каретки). Тоже самое [ tnr]
S Соответствует любому непробельному символу. Тоже самое [^ tnr]
w Соответствует любому буквенному символу (определяется как от a до z, от A до Z, от 0 до 9, и подчеркивание). Тоже самое [a-zA-Z_0-9]
W Соответствует любому несловесному символу. Тоже самое [^a-zA-Z_0-9]

В следующем примере показано, как найти и заменить пробел символом дефиса в строке, используя регулярное выражение с методом JavaScript replace():

var regex = /s/g;
var replacement = "-";
var str = "Earth revolves aroundnthetSun";

// Заменяем пробелы, новые строки и вкладки
document.write(str.replace(regex, replacement) + "<hr>");

// Заменяем только пробелы
document.write(str.replace(/ /g, "-"));

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

В предыдущем разделе мы узнали, как найти один символ в строке. Но что, если вы хотите сопоставить более одного символа? Например, допустим, вы хотите найти слова, содержащие один или несколько символов буквы p, или слова, содержащие не менее двух символов p.

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

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

RegExp Что оно делает
p+ Соответствует одному или нескольким вхождениям буквы p.
p* Соответствует нулю или более вхождений буквы p.
p? Соответствует нулю или одному вхождению буквы p.
p{2} Соответствует ровно двум вхождениям буквы p.
p{2,3} Соответствует как минимум двум вхождениям буквы p, но не более трех вхождений.
p{2,} Соответствует двум или более вхождениям буквы p.
p{,3} Совпадает не более чем с тремя вхождениями буквы p.

Регулярное выражение в следующем примере разбивает строку на последовательность символов с помощью метода JavaScript split():

var regex = /[s,]+/;
var str = "My favourite colors are red, green and blue";
var parts = str.split(regex);

// Проход по циклу и отображение подстрок
for(var part of parts){
    document.write("<p>" + part + "</p>");
}

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

Есть определенные ситуации, когда вы захотите сопоставить начало или конец слова или строки. Для этого можно использовать два якоря — это каретка (^), обозначающая начало строки, и знак доллара ($), обозначающий конец строки.

RegExp Что оно делает
^p Соответствует букве p в начале строки.
p$ Соответствует букве p в конце строки.

Регулярное выражение в следующем примере будет соответствовать только тем именам в массиве имен, которые начинаются с буквы «J» с помощью функции JavaScript test():

var regex = /^J/;
var names = ["James Bond", "Clark Kent", "John Rambo"];

// Проходим по циклу и отображаем совпадающие имена
for(var name of names) {
    if(regex.test(name)) {
        document.write("<p>" + name + "</p>")
    }
}

Модификаторы (флаги)

Модификатор шаблона позволяет вам контролировать способ обработки соответствия шаблону. Модификаторы размещаются непосредственно после регулярного выражения, например, если вы хотите искать шаблон без учета регистра, вы можете использовать модификатор i, например: /pattern/i.

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

Модификатор Что оно делает
g Ищет глобальное совпадение.
i Делает совпадение без учета регистра.
m Изменяет поведение ^ и $ для сопоставления с границей новой строки (то есть началом или концом каждой строки в многострочной строке) вместо границы строки.
o Оценивает выражение только один раз.
s Изменяет поведение . (точка) для соответствия всем символам, включая символы новой строки..
x Позволяет вам использовать пробел и комментарии в регулярном выражении для ясности.

В следующем примере показано, как использовать модификаторы g и i в регулярном выражении для выполнения глобального поиска без учета регистра с помощью метода match() JavaScript.

var regex = /color/gi;
var str = "Color red is more visible than color blue in daylight.";
var matches = str.match(regex); // глобальное совпадение без учета регистра
console.log(matches);
// expected output: ["Color", "color"]

Аналогично, в следующем примере показано, как сопоставлять начало каждой строки в многострочной строке, используя модификатор ^ anchor и m с методом JavaScript match().

var regex = /^color/gim;
var str = "Color red is more visible than ncolor blue in daylight.";
var matches = str.match(regex); // глобальный, без учета регистра, многострочное соответствие
console.log(matches);
// expected output: ["Color", "color"]

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

Чередование позволяет указать альтернативную версию шаблона. Чередование в регулярном выражении работает так же, как оператор OR в условном выражении if-else.

Вы можете указать чередование, используя вертикальную черту (|). Например, регулярное выражение /fox|dog|cat/ соответствует строке «fox», или строке «dog», или строке «cat». Вот пример:

var regex = /fox|dog|cat/;
var str = "The quick brown fox jumps over the lazy dog.";
var matches = str.match(regex);
console.log(matches);
// expected output: ["fox", index: 16, ...]

Альтернативы оцениваются слева направо, пока не будет найдено совпадение. Если левая альтернатива совпадает, правая альтернатива полностью игнорируется, даже если у нее есть совпадение.

Группировка

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

Например, в regexp /go+/ квантификатор + применяется только к последнему символу o и соответствует строкам «go», «goo» и так далее. В регулярном выражении /(go)+/ квантификатор + применяется к группе символов g и o и соответствует строкам «go», «gogo» и т. д.

var regex = /(go)+/i; 
var str = "One day Gogo will go to school.";
var matches = str.match(regex); // совпадение без учета регистра
console.log(matches);
// expected output: ["Gogo", "go", index: 8, ...]

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

Если регулярное выражение включает флаг g, метод match() возвращает только массив, содержащий все сопоставленные подстроки, а не объект сопоставления.

Границы слов

Символ границы слова (b) помогает вам искать слова, которые начинаются и/или заканчиваются определенным шаблоном. Например, регулярное выражение /bcar/ соответствует словам, начинающимся с шаблона car, и будет соответствовать cart, carrot, или cartoon, но не будет совпадать с oscar.

Аналогично, регулярное выражение /carb/ соответствует словам, оканчивающимся шаблоном car, и будет соответствовать oscar или supercar, но не будет соответствовать cart. Аналогично, /bcarb/ соответствует словам, начинающимся и заканчивающимся шаблоном car и будет соответствовать только слову car.

Следующий пример выделит слова, начинающиеся с car жирным шрифтом:

var regex = /(bcarw*)/g;
var str = "Words begining with car: cart, carrot, cartoon. Words ending with car: oscar, supercar.";
var replacement = '<b>$1</b>';
var result = str.replace(regex, replacement);
document.write(result);

О регулярных выражениях в PHP см. Руководство по регулярным выражениям в PHP.

Понравилась статья? Поделить с друзьями:
  • Как исправить теги в wordpress
  • Как найти тесака в новиграде
  • Как найти все взаимно простые числа
  • Как найти делимое если известно делитель частное
  • Для продолжения easybcd нужен доступ к дистрибутиву windows vista или выше как исправить