Как найти строку не содержащие символ

Since no one else has given a direct answer to the question that was asked, I’ll do it.

The answer is that with POSIX grep, it’s impossible to literally satisfy this request:

grep "<Regex for 'doesn't contain hede'>" input

The reason is that with no flags, POSIX grep is only required to work with Basic Regular Expressions (BREs), which are simply not powerful enough for accomplishing that task, because of lack of alternation in subexpressions. The only kind of alternation it supports involves providing multiple regular expressions separated by newlines, and that doesn’t cover all regular languages, e.g. there’s no finite collection of BREs that matches the same regular language as the extended regular expression (ERE) ^(ab|cd)*$.

However, GNU grep implements extensions that allow it. In particular, | is the alternation operator in GNU’s implementation of BREs. If your regular expression engine supports alternation, parentheses and the Kleene star, and is able to anchor to the beginning and end of the string, that’s all you need for this approach. Note however that negative sets [^ ... ] are very convenient in addition to those, because otherwise, you need to replace them with an expression of the form (a|b|c| ... ) that lists every character that is not in the set, which is extremely tedious and overly long, even more so if the whole character set is Unicode.

Thanks to formal language theory, we get to see how such an expression looks like. With GNU grep, the answer would be something like:

grep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input

(found with Grail and some further optimizations made by hand).

You can also use a tool that implements EREs, like egrep, to get rid of the backslashes, or equivalently, pass the -E flag to POSIX grep (although I was under the impression that the question required avoiding any flags to grep whatsoever):

egrep "^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$" input

Here’s a script to test it (note it generates a file testinput.txt in the current directory). Several of the expressions presented in other answers fail this test.

#!/bin/bash
REGEX="^([^h]|h(h|eh|edh)*([^eh]|e[^dh]|ed[^eh]))*(|h(h|eh|edh)*(|e|ed))$"

# First four lines as in OP's testcase.
cat > testinput.txt <<EOF
hoho
hihi
haha
hede

h
he
ah
head
ahead
ahed
aheda
ahede
hhede
hehede
hedhede
hehehehehehedehehe
hedecidedthat
EOF
diff -s -u <(grep -v hede testinput.txt) <(grep "$REGEX" testinput.txt)

In my system it prints:

Files /dev/fd/63 and /dev/fd/62 are identical

as expected.

For those interested in the details, the technique employed is to convert the regular expression that matches the word into a finite automaton, then invert the automaton by changing every acceptance state to non-acceptance and vice versa, and then converting the resulting FA back to a regular expression.

As everyone has noted, if your regular expression engine supports negative lookahead, the regular expression is much simpler. For example, with GNU grep:

grep -P '^((?!hede).)*$' input

However, this approach has the disadvantage that it requires a backtracking regular expression engine. This makes it unsuitable in installations that are using secure regular expression engines like RE2, which is one reason to prefer the generated approach in some circumstances.

Using Kendall Hopkins’ excellent FormalTheory library, written in PHP, which provides a functionality similar to Grail, and a simplifier written by myself, I’ve been able to write an online generator of negative regular expressions given an input phrase (only alphanumeric and space characters currently supported, and the length is limited): http://www.formauri.es/personal/pgimeno/misc/non-match-regex/

For hede it outputs:

^([^h]|h(h|e(h|dh))*([^eh]|e([^dh]|d[^eh])))*(h(h|e(h|dh))*(ed?)?)?$

which is equivalent to the above.

I have something like this

aabbabcaabda

for selecting minimal group wrapped by a I have this /a([^a]*)a/ which works just fine

But i have problem with groups wrapped by aa, where I’d need something like
/aa([^aa]*)aa/ which doesn’t work, and I can’t use the first one like /aa([^a]*)aa/, because it would end on first occurence of a, which I don’t want.

Generally, is there any way, how to say not contains string in the same way that
I can say not contains character with [^a]?

Simply said, I need aa followed by any character except sequence aa and then ends with aa

finnw's user avatar

finnw

47.7k24 gold badges143 silver badges221 bronze badges

asked Apr 4, 2009 at 19:22

Jakub Arnold's user avatar

Jakub ArnoldJakub Arnold

85.1k89 gold badges230 silver badges327 bronze badges

2

By the power of Google I found a blogpost from 2007 which gives the following regex that matches string which don’t contains a certain substring:

^((?!my string).)*$

It works as follows: it looks for zero or more (*) characters (.) which do not begin (?! — negative lookahead) your string and it stipulates that the entire string must be made up of such characters (by using the ^ and $ anchors). Or to put it an other way:

The entire string must be made up of characters which do not begin a given string, which means that the string doesn’t contain the given substring.

gp_sflover's user avatar

gp_sflover

3,4395 gold badges37 silver badges48 bronze badges

answered Mar 5, 2010 at 13:39

Grey Panther's user avatar

Grey PantherGrey Panther

12.8k6 gold badges45 silver badges64 bronze badges

7

In general it’s a pain to write a regular expression not containing a particular string. We had to do this for models of computation — you take an NFA, which is easy enough to define, and then reduce it to a regular expression. The expression for things not containing «cat» was about 80 characters long.

Edit: I just finished and yes, it’s:

aa([^a] | a[^a])aa

Here is a very brief tutorial. I found some great ones before, but I can’t see them anymore.

answered Apr 4, 2009 at 19:30

Claudiu's user avatar

3

All you need is a reluctant quantifier:

regex: /aa.*?aa/

aabbabcaabda   => aabbabcaa

aaaaaabda      => aaaa

aabbabcaabda   => aabbabcaa

aababaaaabdaa  => aababaa, aabdaa

You could use negative lookahead, too, but in this case it’s just a more verbose way accomplish the same thing. Also, it’s a little trickier than gpojd made it out to be. The lookahead has to be applied at each position before the dot is allowed to consume the next character.

/aa(?:(?!aa).)*aa/

As for the approach suggested by Claudiu and finnw, it’ll work okay when the sentinel string is only two characters long, but (as Claudiu acknowledged) it’s too unwieldy for longer strings.

answered Apr 5, 2009 at 7:32

Alan Moore's user avatar

Alan MooreAlan Moore

73.6k12 gold badges100 silver badges156 bronze badges

2

answered Apr 4, 2009 at 19:24

finnw's user avatar

finnwfinnw

47.7k24 gold badges143 silver badges221 bronze badges

I’m not sure it’s a standard construct, but I think you should have a look on «negative lookahead» (which writes : «?!», without the quotes).
It’s far easier than all answers in this thread, including the accepted one.

Example :
Regex : «^(?!123)[0-9]*w»
Captures any string beginning by digits followed by letters, UNLESS if «these digits» are 123.

http://msdn.microsoft.com/en-us/library/az24scfc%28v=vs.110%29.aspx#grouping_constructs
(microsoft page, but quite comprehensive) for lookahead / lookbehind

PS : it works well for me (.Net). But if I’m wrong on something, please let us know. I find this construct very simple and effective, so I’m surprised of the accepted answer.

answered Nov 21, 2014 at 11:27

AFract's user avatar

AFractAFract

8,6186 gold badges47 silver badges69 bronze badges

I the following code I had to replace add a GET-parameter to all references to JS-files EXCEPT one.

<link rel="stylesheet" type="text/css" href="/login/css/ABC.css" />
<script type="text/javascript" language="javascript" src="/localization/DEF.js"></script>
<script type="text/javascript" language="javascript" src="/login/jslib/GHI.js"></script>
<script type="text/javascript" language="javascript" src="/login/jslib/md5.js"></script>
sendRequest('/application/srvc/EXCEPTION.js', handleChallengeResponse, null);
sendRequest('/application/srvc/EXCEPTION.js",handleChallengeResponse, null);

This is the Matcher used:

(?<!EXCEPTION)(.js)

What that does is look for all occurences of «.js» and if they are preceeded by the «EXCEPTION» string, discard that result from the result array. That’s called negative lookbehind. Since I spent a day on finding out how to do this I thought I should share.

answered Nov 22, 2012 at 12:18

jsaddwater's user avatar

jsaddwaterjsaddwater

1,7712 gold badges18 silver badges28 bronze badges

".*[^(\.inc)]\.ftl$"

In Java this will find all files ending in «.ftl» but not ending in «.inc.ftl», which is exactly what I wanted.

canadian_scholar's user avatar

answered Dec 1, 2011 at 19:17

twopigs's user avatar

1

image

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

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

В данной статье мы сосредоточимся на валидации.

Что конкретно мы будем делать? Мы возьмем несколько регулярок из validator.js (наиболее популярной библиотеки для валидации данных с помощью регулярных выражений) и произведем их подробный разбор. Также мы рассмотрим несколько дополнительных регулярок и один алгоритм.

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

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

Еще парочка полезных ссылок:

  • Интерактивный редактор (песочница) для создания и тестирования регулярных выражений. Одной из особенностей данной песочницы является автоматически генерируемое объяснение регулярных выражений, что бывает очень полезным

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

    // Валидация `URL` с протоколом `http(s)`
    const regexp = VerEx()
    // начало строки
    .startOfLine()
    // затем `http`
    .then('http')
    // затем, возможно, `s`
    .maybe('s')
    // затем `://`
    .then('://')
    // затем, возможно, `www.`
    .maybe('www.')
    // затем любой символ, кроме пробела
    .anythingBut(' ')
    // конец строки
    .endOfLine()
    
    const isURL = (str) => regexp.test(str)
    
    console.log(isURL('https://www.google.com')) // true

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

Является ли строка пустой?

Функция:

const isEmpty = (str) => str.trim().length === 0

console.log(isEmpty('not empty')) // false
console.log(isEmpty('   ')) // true

Метод trim() удаляет пробелы в начале и конце строки. Свойство length содержит количество символов, из которых состоит строка. Если строка не содержит символов, значит, ее свойство length имеет значение 0. В этом случае выражение str.length === 0 возвращает true. В противном случае, возвращается false.

Данную функцию можно переписать так:

const isEmpty = (str) => !str.trim().length
// `0` - ложное значение, `1` и больше - истинное значение,
// `!` - логическое НЕ, выполняет преобразование значения в логическое и меняет его на противоположное
// получается `!false`, или `true`

// или так
const _isEmpty = (str) => str.trim() === ''

Является ли значение «логическим»?

Функция:

const isBoolean = (str) => ['true', 'false', '1', '0'].indexOf(str) >= 0

console.log(isBoolean('true')) // true
console.log(isBoolean(false)) // false (см. ниже)

Метод indexOf() возвращает индекс элемента или -1 при отсутствии элемента в массиве. Если элемент в массиве есть, его индекс будет равным 0 или больше (в пределах длины массива — 1). В этом случае выражение arr.indexOf(str) >= 0 возвращает true. Иначе, возвращается false.

Обратите внимание: в данном случае логическими считаются не только значения true и false, но также 1 (обозначающее истину) и 0 (обозначающее ложь). Также обратите внимание, что функция принимает только строку (как и функции, которые будут рассматриваться в дальнейшем), поэтому второй вызов функции isBoolean() с логическим значением false возвращает false.

Данную функцию можно переписать так:

const bools = ['true', 'false', '1', '0']

const isBoolean = (str) => bools.indexOf(str) !== -1

// или так
const _isBoolean = (str) => bools.indexOf(str) > -1

Размялись? Отлично. Переходим к регуляркам.

Состоит ли строка только из букв?

Функция:

// для латиницы
const isAlpha = /^[A-Z]+$/i.test(str)

// для кириллицы
const _isAlpha = /^[А-ЯЁ]+$/i.test(str)

// для латиницы и криллицы
const __isAlpha = /^[A-ZА-ЯЁ]+$/i.test(str)

Посимвольный разбор:

  • ^ и $ — символы начала и конца строки, соответственно (границы)
  • [] — набор символов, перечисление (символ строки должен совпадать хотя бы с одним из вариантов)
  • A-Z или А-ЯЁ — диапазон букв (например, А-ЯЁ — это все буквы киррилицы от А до Я + буква Ё, которая стоит особняком)

Обратите внимание: A-Z — это не тоже самое, что символьный класс w. w означает любая буква латиницы ИЛИ любая цифра ИЛИ символ нижнего подчеркивания.

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

Читаем регулярку: строка ДОЛЖНА состоять хотя бы из одной (одной или более) буквы, независимо от регистра.

Состоит ли строка только из букв и/или целых чисел?

Функция:

// для латиницы
const isAlphaNumeric = (str) => /^[0-9A-Z]+$/i.test(str)

// для кириллицы
const _isAlphaNumeric = (str) => /^[0-9А-ЯЁ]+$/i.test(str)

// для латиницы и кириллицы
const __isAlphaNumeric = (str) => /^[0-9A-ZА-ЯЁ]+$/i.test(str)

Разбор: такие же спецсимволы, что и в предыдущем примере +

  • 0-9 — диапазон цифр от 0 до 9; данный диапазон можно заменить символьным классом d, означающим любое число, для латиницы получится /^[dA-Z]+$/i

Обратите внимание: здесь мы также не можем использовать w из-за нижнего подчеркивания. Впрочем, мы можем исключить его из проверки, написав что-то вроде /^[^W_]$/i, где [^] означает любой символ, кроме указанных в наборе (одним из таких символов является _), W — НЕ буква латиницы, НЕ число и НЕ нижнее подчеркивание. Таким образом, [^W_] означает любой символ, который НЕ является нижним подчеркиванием, а также не относится к буквам латиницы ИЛИ цифрам. Или в переводе на человеческий язык — любой символ, который является буквой латиницы или числом.

Читаем регулярку: строка ДОЛЖНА состоять хотя бы из одной буквы И/ИЛИ (арабского — в дальнейшем предполагается) числа, без учета регистра

Является ли значение почтовым индексом?

Функция:

const isPostalCode = (str) => /^d{6}$/.test(str)

Разбор:

  • {6} — точно 6 вхождений предшествующего символа, т.е. символ перед { должен повторяться 6 раз, поскольку почтовый индекс (для России) состоит из 6 цифр (квантификатор)

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

Читаем: значение ДОЛЖНО состоять (точно — в дальнейшем предполагается) из 6 цифр.

Является ли значение номером паспорта?

Функция:

// оригинал из `validator.js`
const passportNumber = /^d{2}d{2}d{6}$/
// можно упростить
const _passportNumber = /^d{10}$/

const removeSpaces = (str) => str.replace(/s+/g, '')

const isPassportNumber = (str) => _passportNumber.test(removeSpaces(str))

Разбор:

  • в самом регулярном выражении нет ничего нового — 10 цифр (или 2 цифры + 2 цифры + 6 цифр — в оригинале)
  • removeSpaces() — утилита для удаления из строки пробелов, поскольку номер паспорта может выглядеть как 12 34 567890, 1234 567890 и т.д.
  • s означает пробельный символ: пробел, табуляция, перенос строки и т.д. (символьный класс)
  • g — глобальный поиск, т.е. будут обнаружены все пробелы, имеющиеся в строке, а не только первый (флаг)

Читаем: значение ДОЛЖНО состоять из 10 цифр.

Является ли значение числом (целым или с плавающей точкой/запятой)?

Функция:

const numeric = (delimiter = '.') =>
  new RegExp(`^[+-]?([0-9]*\${delimiter})?[0-9]+$`)

const isNumeric = (str, delimiter) => numeric(delimiter).test(str)

Разбор:

  • numeric — функция, возвращающая регулярное выражение с указанным разделителем (delimiter), которым по умолчанию является символ .; это один из тех немногих случаев, когда для создания регулярки используется объект RegExp, позволяющий создавать регулярное выражение динамически, в отличие от //, создающего статический шаблон

Обратите внимание: символ . необходимо экранировать с помощью или, в данном случае, с помощью \ (чтобы не экранировать $). Экранирование превращает . в обычную точку, в противном случае, этот символ будет иметь специальное значение — любой символ. Мы также экранируем любой другой символ, переданный в функцию isNumeric() в качестве разделителя (побочный эффект), но это не страшно.

  • ? — НОЛЬ или ОДИН предшествующий символ (квантификатор); по сути, применение этого квантификатора делает предшествующий символ опциональным (необязательным)

  • * — НОЛЬ или БОЛЕЕ предшествующих символов (квантификатор); этот квантификатор также делает предшествующий символ необязательным

  • [+-]? — необязательный + или -

  • ([0-9]*.)? — необязательная группа, которая МОЖЕТ состоять из числа и точки ИЛИ только из точки (или другого разделителя)

  • [0-9]+ — хотя бы одна цифра

Читаем: значение ДОЛЖНО состоять хотя бы из одной цифры и МОЖЕТ включать знак + или - в начале строки, за которым МОЖЕТ следовать любое количество цифр и разделитель ИЛИ только разделитель

Является ли строка цветом в шестнадцатиричном формате?

Функция:

const hexColor = /^#?([0-9A-F]{3}|[0-9A-F]{4}|[0-9A-F]{6}|[0-9A-F]{8})$/i

const isHexColor = (str) => hexColor.test(str)

Разбор:

  • длина регулярки может испугать, но это всего лишь повторяющийся шаблон
  • #? — необязательный символ #
  • [0-9A-F]{n}n любых чисел ИЛИ букв латиницы от A до F (без учета регистра)
  • | — альтерация, ИЛИ (строка должна совпадать с одним из вариантов — наборов)

Читаем: строка ДОЛЖНА состоять ЛИБО из 3, ЛИБО из 4, ЛИБО из 6, ЛИБО из 8 букв латиницы от A до F (без учета регистра) И/ИЛИ цифр и МОЖЕТ включать символ # в начале

Является ли строка цветом в формате RGB или RGBA?

// RGB без альфа-канала - прозрачности
const rgbColorRegexp =
  /^rgb((([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]),){2}([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]))$/

// RGBA с прозрасностью
const rgbaColorRegexp =
  /^rgba((([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]),){3}(0?.d|1(.0)?|0(.0)?))$/

// RGB в процентах
const rgbColorPercentRegexp =
  /^rgb((([0-9]%|[1-9][0-9]%|100%),){2}([0-9]%|[1-9][0-9]%|100%))/

// RGBA в процентах
const rgbaColorPercentRegexp =
  /^rgba((([0-9]%|[1-9][0-9]%|100%),){3}(0?.d|1(.0)?|0(.0)?))/

// строка должна совпадать хотя бы с одним вариантом
const isRgbColor = (str) =>
  rgbColorRegexp.test(str) ||
  rgbaColorRegexp.test(str) ||
  rgbColorPercentRegexp.test(str) ||
  rgbaColorPercentRegexp.test(str)

Двигаемся дальше.

Является ли значение номером сотового телефона?

Функция (для России):

const mobilePhoneRegexp = /^(+?7|8)?9d{9}$/

const removeNonDigits = (str) => replace(/D+/g, '')

const isMobilePhone = (str) => mobilePhoneRegexp.test(removeNonDigits(str))

Разбор:

  • +? — опциональный символ +; обратите внимание на экранирование
  • (+?7|8)? — опциональная группа, которая МОЖЕТ состоять из символа + и числа 7 или 8 ИЛИ только из числа 7 или 8
  • removeNonDigits() — утилита для удаления всех символов, которые не являются числом (D)

Читаем: строка ДОЛЖНА состоять из 10 цифр, первым из которых ДОЛЖНО БЫТЬ число 9, и МОЖЕТ включать символ + и число 7 или 8 ИЛИ только число 7 или 8 в начале

Лирическое отступление

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

// Определяем, является ли строка названием изображения в формате PNG, JPG, JPEG или SVG с помощью опережающей проверки
// Строка ДОЛЖНА состоять из одного и более (любого) символа, включать точку и заканчиваться на `png`, `jpg`, `jpeg` или `svg` (без учета регистра)
const isImage = (str) => /.+.(?=png|jpe?g|gif|svg)/i.test(str)

// Определяем, что строка НЕ является названием изображения в формате WebP или AVIF с помощью негативной опережающей проверки
// ПОСЛЕ строки, состоящей из одного или более символа, НЕ ДОЛЖНО быть слова `webp` или `avif` (без учета регистра)
const isNotWebpOrAvif = (str) => /.+.(?!webp|avif)/i.test(str)

// Что касается ретроспективной проверки, то я не смог придумать достойного примера - такого, который бы имел какие-то преимущества по сравнению с использованием метода `startsWith()`

// Определяем, что значение является долларами США с помощью ретроспективной проверки
// ПЕРЕД хотя бы одной цифрой ДОЛЖЕН быть символ `$`
const isUSD = (str) => /(?<=$)d+/.test(str)

// Определяем, что значение НЕ является евро с помощью негативной ретроспективной проверки
// ПЕРЕД хотя бы одной цифрой НЕ ДОЛЖНО быть символа `€`
const isNotEUR = (str) => /(?<!€)d+/.test(str)

// Определяем, является ли строка эмодзи (или эмоджи)
// Флаг `u` и класс `p{...}` - тема для отдельного разговора, however...
const isEmoji = (str) => /p{So}/u.test(str)

// Универсальная функция для поиска символов на любом языке
const languageAgnostic = /[p{Alpha}p{M}p{Nd}p{Pc}p{Join_C}]/u

const isLanguageAgnosticSymbol = (str) => languageAgnostic.test(str)

Еще парочка полезных утилит

// Определяем, является ли строка HTML-тегом
// [^>] - означает любой символ, кроме `>`
// ([^>]+) - один или более таких символов
const isHTMLTag = (str) => /(^<([^>]+)>$)/i.test(str)

const tag =
  '<p id="myID" class="my-class" data-type="my_type" style="color: green;">'

console.log(isHTMLTag(tag)) // true

// Находим слова в одинарных или двойных (парных) кавычках
// обратите внимание: в данном случае мы не учитываем, что кавычки могут быть экранированы
const quotes = /(["'])([^"'])*1/g
const getQuottedWords = (str) => str.match(quotes)

const str = `some string with "double quotes" and 'single quotes'`

console.log(getQuottedWords(str).join('n'))
/*
  "double quotes"
  'single quotes'
*/

// или так (короче, но также без учета экранирования)
const _quotes = /(["']).*1/g

// или так (с учетом экранирования)
const __quotes = /(["'])([^"'\])*1/g
const _getQuottedWords = (str) => str.match(__quotes)

const str = "some string with "escaped double quotes""

console.log(..._getQuottedWords(str)) // "escaped double quotes"

// Определяем, является ли строка временем в формате ЧЧ:ММ:СС
// Строка ДОЛЖНА начинаться с 1 с ведущим 0 или без него и любым числом (01-19) ИЛИ
// 2 и любым числом в диапазане от 0 до 3 (20-23) - часы,
// затем ДОЛЖНЫ следовать две группы, состоящие из двоеточия и любого двухзначного числа,
// первая цифра которого ДОЛЖНА находиться в диапазоне от 0 до 5 (01-59) - часы и минуты
const isTime = (str) => /([01]d|2[0-3])(:[0-5]d){2}$/.test(str)

console.log(isTime('12:00')) // false
console.log(isTime('12:00:00')) // true

Функция для удаления из строки ВСЕХ пробелов

const str = '  some   string  with   crazy    spaces '

const formatted = str
  .replace(/s{2,}/g, ' ')
  .trim()
  .replace('crazy', 'normal')

console.log(formatted) // 'some string with normal spaces'

Функция для форматирования даты

// В таком формате возвращается дата из `<input type="date">`
const str = '2021-06-29'
// допустим, что мы хотим преобразовать ее в привычный для нас формат - `ДД.ММ.ГГГГ`

// С помощью обычных скобочных групп
// определяем скобочные группы 1, 2 и 3
const regexp = /(d{4})-(d{2})-(d{2})/

// производим замену
const formatted = str.replace(regexp, '$3.$2.$1')
console.log(formatted) // 29.06.2021

// С помощью именованных групп захвата
// определяем группы `year`, `month` и `day`,
// чтобы не запоминать (считать) номера групп (количество скобок)
const _regexp = /(?<year>d{4})-(?<month>d{2})-(?<day>d{2})/

// производим замену
const _formatted = str.replace(_regexp, '$<day>.$<month>.$<year>')
console.log(_formatted) // 29.06.2021

// С помощью функции-заменителя
// это не имеет особого смысла, просто для примера
const regexp = /(d{4})-(d{2})-(d{2})/

const result = str.replace(
  regexp,
  // первый аргумент, который нам не нужен - это объект совпадения `match`
  (_, year, month, day) => `${day}.${month}.${year}`
)
console.log(result) // 29.06.2021

Теперь поговорим о строках, к содержанию которых предъявляются особые требования. Под такими строками я подразумеваю URL (или, если угодно, URI) и email. В принципе, сюда же можно отнести пароли, требования к которым предъявляются не нормативными документами (RFC), а клиентами/заказчиками/разработчиками.

URL

URL (Uniform Resource Locator — унифицированный указатель ресурса) — система унифицированных адресов электронных ресурсов, или единообразный определитель местонахождения ресурса.

Он состоит из следующих частей:

Требования, предъявляемые к «урлам», содержатся, в основном, в RFC 3986. Согласно этому документу URL может содержать следующие символы:

ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~:/?#[]@!$&'()*+,;=

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

Также в названном документе приводится регулярка для разбора урлов (приложение B), которая выглядит так (числа — это номера групп):

^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(?([^#]*))?(#(.*))?
 12            3  4          5       6  7        8 9

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

const url = /^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(?([^#]*))?(#(.*))?/
const str = 'https://example.com/main?search#hash'
const result = str.match(url)
console.log(result)
/*
  [
    0: "https://example.com/main?search#hash"
    1: "https:"
    2: "https"
    3: "//example.com"
    4: "example.com"
    5: "/main"
    6: "?search"
    7: "search"
    8: "#hash"
    9: "hash"
    // ...
  ]
*/

Данная регулярка отлично подходит для простых URL (как в приведенном примере), но в более сложных случаях лучше использовать конструктор URL():

const str = 'https://user:pwd@example.com:80/main?query#anchor'
const result = new URL(str)
console.log(result)
/*
  {
    hash: "#anchor"
    host: "example.com:80"
    hostname: "example.com"
    href: "https://user:pwd@example.com:80/main?query#anchor"
    origin: "https://example.com:80"
    password: "pwd"
    pathname: "/main"
    port: "80"
    protocol: "https:"
    search: "?query"
  }
*/

Вот один из возможных вариантов регулярки для проверки URL с учетом протокола http(s), а также без учета некоторых символов:

/
  https?://               - `http` с необязательным `s`
  (www.)?                  - опциональные `www` и `.`
  [-w@:%.+~#=]{1,256}    - любые символы из набора в количестве 1-256
  .                        - точка
  [a-z0-9()]{2,}            - домен размером от 2 символов
  b                        - граница строки
  ([-w()@:%.+~#=//?&]*)  - поддомен, строка запроса, якорь и т.д.
/i

Тестируем:

const url =
  /https?://(www.)?[-w@:%.+~#=]{1,256}.[a-z0-9()]{1,6}b([-w()@:%.+~#=//?&]*)/i

const urls = [
  'http://www.example.ru',
  'https://www.example.ru',
  'http://example.ru',
  'http://www.example.ru/main',
  'htt://example.ru',
  'www.example.ru',
  'www.mp3.com',
  'www.e.org',
  'http://e.org',
  'http://www.e.org',
  'https://www.e.org',
  'www.ab.com',
  'http://ab.com',
  'http://www.ab.com',
  'https://www.ab.com',
  'www.example',
  'www.example-.ru',
  'www.-example.ru',
  'example.ru',
  'http://www.example',
  'http://example',
  'www.mp3#.com'
]

urls.forEach((u) => {
  if (url.test(u)) {
    console.log(`%cCorrect: ${u}`, 'color: green;')
  } else {
    console.info(`%cWrong: ${u}`, 'color: red;')
  }
})
/*
  Correct: http://www.example.ru
  Correct: https://www.example.ru
  Correct: http://example.ru
  Correct: http://www.example.ru/main
  Wrong: htt://example.ru
  Wrong: www.example.ru
  Wrong: www.mp3.com
  Wrong: www.e.org
  Correct: http://e.org
  Correct: http://www.e.org
  Correct: https://www.e.org
  Wrong: www.ab.com
  Correct: http://ab.com
  Correct: http://www.ab.com
  Correct: https://www.ab.com
  Wrong: www.example
  Wrong: www.example-.ru
  Wrong: www.-example.ru
  Wrong: example.ru
  Wrong: http://www.example
  Wrong: http://example
  Wrong: www.mp3#.com
*/

Email

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

Адрес электронной почты (email) состоит из из следующих частей:

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

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

// (хотя бы один (любой) символ)@(хотя бы один символ).(хотя бы один символ)
const isEmail = (str) => /^(.+)@(.+).(.+)$/.test(str)

Или можно обойтись вообще без регулярки:

// преобразуем строку в массив
const isEmail = ([...str]) =>
  // проверяем, что в массиве есть символ `@`, он находится, как минимум, на второй позиции
  str.indexOf('@') > 0 &&
  // и является единственным
  str.indexOf('@') === str.lastIndexOf('@') &&
  // проверяем, что в массиве есть точка,
  str.indexOf('.') > 0 &&
  // она стоит после символа `@`
  str.indexOf('@') < str.indexOf('.') &&
  // и не является последним символом
  str.indexOf('.') < str.length - 1

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

Вот гораздо более продвинутый (надежный) пример:

/^
  (
    (
      [^<>()[]\.,;:s@"]+   - один или более символ, кроме указанных
      (. - точка
        [^<>()[]\.,;:s@"]+ - один или более символ, кроме указанных
      )
      *                       - ноль или более
    )
    |                         - ИЛИ
    (".+")                    - один или более символ в двойных кавычках
  )
    @                         - символ `@`
  (
    (
      [                      - открывающая квадратная скобка
        [0-9]{1,3}            - от 1 до 3 цифр
        .                    - точка
        [0-9]{1,3}
        .
        [0-9]{1,3}
        .
        [0-9]{1,3}
      ]                      - закрывающая квадратная скобка
    )
    | - ИЛИ
    (
      (
        [-a-z0-9]+            - одна или более буква латиницы, цифра или символ `-`
        .                    - точка
      )+                      - один или более
      [a-z]{2,}               - хотя бы `2` буквы латиницы
    )
  )
$/i                           - без учета регистра

Функция:

const email =
  /^(([^<>()[]\.,;:s@"]+(.[^<>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-z-0-9]+.)+[a-z]{2,}))$/i

const isEmail = (str) => email.test(str)

// тестируем
// вместо `@`, написали `_`
console.log(isEmail('my_mail.com')) // false
// пропустили `.`
console.log(isEmail('my@mailcom')) // false
// пропустили начало
console.log(isEmail('@mail.com')) // false
// ок
console.log(isEmail('my@mail.com')) // true

Пароль

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

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

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

{8,}                        // от 8 символов
{8,20}                      // от 8 до 20 символов
(?=.*d)                    // минимум одна цифра
(?=.*[a-z])                 // минимум одна буква в нижнем регистре
(?=.*[A-Z])                 // минимум одна буква в верхнем регистре
(?=.*[-#!$@%^&*_+~=:;?/])  // минимум один символ из набора

Функция:

const password =
  /^(?=.*d)(?=.*[a-z])(?=.*[A-Z])(?=.*[-#!$@%^&*_+~=:;?/])[-w#!$@%^&*+~=:;?/]{8,}$/

const isPassword = (str) => password.test(str)

// буквы в верхнем и нижнем регистре
console.log(isPassword('Password')) // false
// + число
console.log(isPassword('Passw0rd')) // false
// + спецсимвол, но недостаточная длина
console.log(isPassword('Pas_w0r')) // false
// ok
console.log(isPassword('Pas$_W0rd')) // true

Не забывайте модифицировать [-w#!$@%^&*+~=:;?/] при добавлении/удалении групп.

Номер карты

Напоследок, рассмотрим один интересный алгоритм — алгоритм Луна, который используется для вычисления контрольной цифры номера пластиковой карты в соответствии со стандартом ISO/IEC 7812 с целью ее (номера) валидации.

В упрощенном виде этот алгорит включает следующие шаги:

  1. Цифры проверяемой последовательности нумеруются справа налево.
  2. Цифры, оказавшиеся на нечетных местах, остаются без изменений.
  3. Цифры, стоящие на четных местах, умножаются на 2.

Обратите внимание: речь идет не о «четности» числа, а о четности его позиции в строке.

  1. Если в результате такого умножения возникает число больше 9, оно заменяется суммой цифр получившегося произведения — однозначным числом, то есть цифрой.
  2. Все полученные в результате преобразования цифры складываются. Если сумма кратна 10, то исходные данные верны.

Реализация алгоритма из Википедии:

function luhnAlgorithm(value) {
  value = value.replace(/D/g, '')

  // контрольная сумма
  let nCheck = 0
  // индикатор "четности" позиции числа
  let bEven = false

  // перебираем числа в обратном порядке
  for (let n = value.length - 1; n >= 0; n--) {
    // извлекаем число из строки
    // позиция первого извлеченного числа является нечетной
    let nDigit = parseInt(value.charAt(n), 10)

    // если позиция числа четная и при умножении на 2 число становится больше 9,
    // вычитаем из числа 9
    // результат будет таким же, как при сложении цифр, из которых состоит число
    // например, `6 * 2 = 12`, `1 + 2 = 3` и `12 - 9 = 3`
    if (bEven && (nDigit *= 2) > 9) {
      nDigit -= 9
    }

    // прибавляем число к сумме
    nCheck += nDigit
    // инвертируем индикатор
    bEven = !bEven
  }

  // если контрольная сумма делится на 10 без остатка,
  // значит, номер карты является валидным
  return nCheck % 10 == 0
}

validator.js предлагает такой вариант рассматриваемого алгоритма:

const creditCard =
  /^(?:4[0-9]{12}(?:[0-9]{3,6})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12,15}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35d{3})d{11}|6[27][0-9]{14})$/

function isCreditCard(str) {
  const sanitized = str.replace(/[- ]+/g, '')
  if (!creditCard.test(sanitized)) {
    return false
  }
  let sum = 0
  let digit
  let tmpNum
  let shouldDouble
  for (let i = sanitized.length - 1; i >= 0; i--) {
    digit = sanitized.substring(i, i + 1)
    tmpNum = parseInt(digit, 10)
    if (shouldDouble) {
      tmpNum *= 2
      if (tmpNum >= 10) {
        sum += (tmpNum % 10) + 1
      } else {
        sum += tmpNum
      }
    } else {
      sum += tmpNum
    }
    shouldDouble = !shouldDouble
  }
  return !!(sum % 10 === 0 ? sanitized : false)
}

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

// функция принимает строку
const isCreditCard = (str) =>
  // строка не должна быть пустой
  // можно воспользоваться нашей функцией `!isEmpty()` или
  str.trim().length !== 0 &&
  str
    // удаляем из строки все НЕ числа
    .replace(/D/g, '')
    // преобразуем строку в массив
    .split('')
    // меняем порядок следования элементов массива на противоположный
    .reverse()
    // s - контрольная сумма
    // c - текущий символ строки, начиная с последнего
    // i - индекс элемента
    .reduce(
      (s, c, i) =>
        (s +=
          // если индекс элемента - нечетное число (1, 3 и т.д.),
          // значит, число находится на четной позиции, умножаем его на 2
          // если при этом число становится больше 9, вычитаем из него 9
          // преобразуем результат в число с помощью `+`
          +(i % 2 !== 0 && (c *= 2) > 9 ? (c -= 9) : c)),
      0) // начальная контрольная сумма
    // если контрольная сумма делится на 10 без остатка,
    // значит, номер карты является валидным
    % 10 === 0

console.log(isCreditCard('1234 5678 9009 8765')) // false
console.log(isCreditCard('5555 5555 5555 4444')) // true

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

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

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

const isEmpty = (str) => !str.trim().length

const isBoolean = (str) => ['true', 'false', '1', '0'].indexOf(str) > -1

const isAlpha = /^[A-ZА-ЯЁ]+$/i.test(str)

const isAlphaNumeric = (str) => /^[0-9A-ZА-ЯЁ]+$/i.test(str)

const numeric = (delimiter = '.') =>
  new RegExp(`^[+-]?([0-9]*\${delimiter})?[0-9]+$`)
const isNumeric = (str, delimiter) => numeric(delimiter).test(str)

const isPostalCode = (str) => /^d{6}$/.test(str)

const isPassportNumber = (str) => /^d{10}$/.test(str)

const isMobilePhone = (str) => /^(+?7|8)?9d{9}$/.test(str)

const isURL = (str) => /https?://(www.)?[-w@:%.+~#=]{1,256}.[a-z0-9()]{1,6}b([-w()@:%.+~#=//?&]*)/i.test(str)

const isEmail = (str) => /^(([^<>()[]\.,;:s@"]+(.[^<>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-z-0-9]+.)+[a-z]{2,}))$/i.test(str)

const isStrongPassword = (str) => /^(?=.*d)(?=.*[a-z])(?=.*[A-Z])(?=.*[-#!$@%^&*_+~=:;?/])[-w#!$@%^&*+~=:;?/]{8,}$/.test(str)

const isCreditCard = (str) =>
  str.trim().length !== 0 &&
  str
    .replace(/D/g, '')
    .split('')
    .reverse()
    .reduce((s, c, i) =>
      (s += +(i % 2 !== 0 && (c *= 2) > 9 ? (c -= 9) : c)), 0) % 10 === 0

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


Купить VPS-хостинг с быстрыми NVMе-дисками и посуточной оплатой у хостинга Маклауд.

Перевод статьи «Powerful regex for the practical dev».

Regex (регулярные выражения, англ. regular expressions) — это поиск на стероидах. В этом посте я на примерах покажу, как задавать эффективные условия поиска.

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

Для примеров мы будем использовать следующий текст (демо):

Alabama (AL) Birmingham (Dec 14, 1819) 4,903,185
Hawaii* (HI) Honolulu (Aug 21, 1959) 1,415,872
Michigan (MI) Detroit (Jan 26, 1837) 9,986,857
North Dakota (ND) Fargo (Nov 2, 1889) 762,062
Wyoming* (WY) Cheyenne (Jul 10, 1890) 578,759

(штат, его id, крупнейший город штата, дата основания и количество населения)

Как пользоваться этим руководством

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

Настройка

Откройте свою IDE или пройдите по ссылке.

  • Откройте современную IDE (я использую VSCode)
  • Вставьте пример
  • Откройте поиск (ctrl + f или cmd + f)
  • Включите regex (обычно значок .*).

Базовые шаблоны поиска

Буквы — [a-zA-Z]

  • [a-z] — буквы в нижнем регистре.
  • [A-Z] — буквы в верхнем регистре.
  • В VSCode регистр имеет значение, только если включена опция «match case» (Aa).

Найдено 102 совпадения с шаблоном, потому что шаблон — все буквы.

Слова — [a-zA-Z]+

  • [a-zA-Z] — буквы.
  • + — это квантификатор, означающий, что символ из указанного набора может повторяться один раз и более. Таким образом мы показываем, что ищем не каждую конкретную букву, а отдельные последовательности букв.

Найдено 21 соответствие с шаблоном поиска, потому что именно столько слов в нашем тексте.

Определенные слова — (Jan|Jul|Dec)

Шаблон (Jan|Jul|Dec) дает совпадение не со всеми словами, а именно с Jan, Jul или Dec.

Двузначные числа — [0-9]{2}

  • [0-9] — цифры.
  • {2} — это квантификатор, показывающий, сколько цифр из набора должно идти подряд.

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

Четырехзначные числа — [0-9]{4}

  • [0-9] — цифры.
  • {4} — квантификатор, показывающий, что мы ищем 4 последовательно идущие цифры.

2-3 буквы, идущие подряд — [a-z]{2,3}

  • [a-z] — буквы.
  • {2,3} — квантификатор, в котором задан диапазон от 2 до 3. То есть мы ищем последовательно идущие 2-3 буквы.

Обратите внимание, что с шаблоном совпали длинные слова. Как и в предыдущем примере с цифрами, это означает, что в каждом таком слове есть несколько совпадений.

Слова из 6 и более букв — [a-z]{6,}

  • [a-z] — буквы.
  • {6,} — квантификатор, в котором указан только нижний лимит. То есть, мы ищем последовательности из шести и более букв.

Три последовательно идущие буквы и/или цифры — w{3}

  • w — означает «буквы или цифры» (см. шпаргалку внизу статьи).
  • {3} — квантификатор, показывающий, что мы ищем три последовательно идущих символа из указанного набора.

В длинных словах найдено несколько совпадений с заданным шаблоном.

Поиск сочетаний из трех букв и/или цифр, идущих отдельным «блоком» — bw{3}b

  • w{3} — как мы знаем из предыдущего примера, это три любые буквы и/или цифры, идущие последовательно.
  • b — указывает на начало и конец слова (см. шпаргалку внизу статьи).

Обратите внимание, что благодаря b в выборку не попали длинные слова.

Отдельные слова, состоящие из трех букв — b[a-z]{3}b

  • [a-z]{3} — любые три буквы.
  • b — указывает на начало и конец слова.

Два слова — [a-zA-Z]+s[a-zA-Z]+

Это выглядит страшнее, чем есть на самом деле. Мы задаем шаблон «слово пробел слово».

  • [a-zA-Z]+ — слово (любая буква из заданного набора, одна или больше).
  • s — пробел (см. шпаргалку внизу статьи).

Обратите внимание, что сейчас с шаблоном совпала только Северная Дакота (North Dakota): только это название подходит к шаблону «слово пробел слово».

Одно или два слова — [a-zA-Z]+(s[a-zA-Z]+)?

Тоже выглядит пугающе, но фактически мы задаем шаблон «слово (пробел слово)?»

  • [a-zA-Z]+ — слово.
  • s — пробел.
  • ( … )? — опциональность. Вопросительный знак — это квантификатор, показывающий, что идущий перед ним блок встречается ноль или один раз (т. е. этого блока может и не быть).

Обратите внимание, что теперь в выборку попала не только Северная Дакота (потому что второе слово опционально).

Символы подстановки

Все, что заключено в скобки (жадная подстановка) — (.*)

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

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

Все, что заключено в скобки (ленивая подстановка) — (.*?)

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

Строки, содержащие символ звездочки — ^.**.*$

  • ^ и $ указывают на начало и конец строки соответственно (задаются опционально).
  • .* — подстановка.
  • * — экранированный символ звездочки.

Строки без символа звездочки — ^[^*]+$

  • ^ и $ указывают на начало и конец строки.
  • [^ … ] — совпадает со всем, что не указано в этих квадратных скобках:
    • * — символ звездочки (экранированный).
    • [^*] — совпадает со всем, кроме символа звездочки.
  • + — квантификатор: одно и более повторение совпадения.

Все строки, содержащие символ «е» — ^.*[e].*$

  • ^ и $ указывают на начало и конец строки.
  • .* — подстановка.
  • [e] — буква «е».

Все строки, в которых не содержится символ «е» — ^[^e]+$

  • ^ и $ указывают на начало и конец строки.
  • [^ … ] — совпадает со всем, что не указано в этих квадратных скобках:
    • [^e] — совпадает со всем, кроме буквы «е».
  • + — квантификатор: одно и более повторение совпадения.

Выражение в скобках, начинающееся с указанных слов — ((Jan|Jul|Dec).*)

  • ( и ) — сами скобки.
  • (Jan|Jul|Dec) — совпадает со словами Jan, Jul или Dec.
  • .* — подстановка.

Смешанные соответствия

Короткий формат даты в скобках — [a-z]{3}s+[0-9]+

  • [a-z]{3} — ровно три буквы.
  • s+ — один или больше пробелов.
  • [0-9]+ — одна или больше цифр.

Полная дата в скобках — [a-z]{3}s+[0-9]+,s[0-9]+

Выражение не так страшно, как кажется. По сути это «слово число, число».

  • [a-z]{3} — ровно три буквы.
  • s+ — один или больше пробелов.
  • , — запятая.
  • [0-9]+ — одна или больше цифр.

Слова, в середине которых есть буква «m» — [a-z]+[m][a-z]+

  • [a-z]+ — одна или больше букв.
  • [m] — буква m.

Обратите внимание, что Michigan не попал в выборку. Это потому, что в этом слове буква m стоит в начале, а не в середине.

Слова, в которых есть буква «m» (вообще, в любом месте) — ([a-z]+)?[m]([a-z]+)?

Опять же, выглядит страшно, но по сути это «(слово)? m (слово)?».

  • ( … )? — указывает на опциональность:
    • [a-z]+ — слово.
    • ([a-z]+)? — слово является опциональным.
  • [m] — буква m.

Обратите внимание, что теперь и Michigan совпадает с заданным шаблоном.

Исключение совпадений

Давайте попробуем найти совпадения с шаблоном, но при этом исключить из найденного сам шаблон. Это называется lookaround. (Есть «просмотр вперед» — lookahead и «просмотр назад» — lookbehind. А lookaround — их комбинация).

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

Слово в скобках (включая скобки) — ([a-z]+)

  • ( и ) — сами скобки.
  • [a-z]+ — слово.

Слово в скобках (но взятое без скобок, только само слово) — (?<=()[a-z]+(?=))

  • [a-z]+ — слово.
  • (?<= … ) — блок символов должен начинаться с указанного шаблона, но этот шаблон не входит в результат:
    • ( — символ открывающей скобки,
    • (?<=() — совпадение начинается от открывающей скобки, но сама скобка в него не входит.
  • (?= … ) — блок символов кончается указанным шаблоном, но этот шаблон исключается из результата:
    • ) — символ закрывающей скобки,
    • (?=)) — с шаблоном совпадает все, вплоть до закрывающей скобки, которая в результат не входит.

Все, что в скобках (сами скобки исключаются) — (?<=().*?(?=))

  • (?<=() — совпадает со всем, начиная от открывающей скобки (при этом не захватывая саму скобку).
  • .*? — ленивая подстановка.
  • (?=)) — совпадает со всем, вплоть до закрывающей скобки (при этом не захватывая саму скобку).

Все, что взято в скобки в строках, где есть звездочка — (?<=*.*().*?(?=))

  • (?<= … ) — начало шаблона, исключающееся из результата:
    • * — символ звездочки,
    • .* — жадная подстановка,
    • ( — символ открывающей скобки,
    • (?<=*.*() — подстановка от звездочки до открывающей скобки (при этом не захватывается ни то, ни другое).
  • .*? — ленивая подстановка.
  • (?=)) — совпадает со всем, что стоит до закрывающей скобки (при этом сама скобка не захватывается).

Все, что идет перед звездочкой, не захватывая саму звездочку — ^.*(?=*)

  • ^ — начало строки.
  • .* — жадная подстановка.
  • (?=*) — совпадает со всем, что идет до звездочки, не включая саму звездочку.

Шпаргалка

  • . ^ $ * + ? ( ) [ { | — зарезервированные символы
  • Обратный слэш служит для экранирования:
  • (abc) это шаблон для abc (в группах регулярных выражений)
  • (abc) это шаблон для (abc) (т. е. со скобками)
  • [a-zA-Z] — буквы (регистр имеет значение)
  • [0-9] или d — цифры
  • [a-c1-3#] — набор из символов a b c 1 2 3 #
  • .* — жадная подстановка, .*? — ленивая подстановка
  • ^ — начало строки, $ — конец строки
  • s — пробел, t — табуляция, n — новая строка
  • w — буквы и цифры, W — не буквы и не цифры
  • b граница слова, B — не граница слова
  • + — один повтор и более
  • {3} — ровно три повтора
  • {1,3} — от одного до трех повторов
  • {3,} — больше трех повторов
  • [^ … ] совпадает со всеми символами, кроме указанных в квадратных скобках
  • (?<= … ) — совпадает с тем, что начинается с указанных в скобках символов, но сами символы в результат не входят (look behind)
  • (?= … ) — совпадает со всем, что оканчивается на символы, указанные в скобках, но сами символы в результат не входят (look ahead).

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

Регулярные выражения (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"]

Понравилась статья? Поделить с друзьями:
  • Как найти дело по душе гдз
  • Как найти сони плейстейшен 4 если украли
  • После растяжки онемела нога как исправить
  • Как найти одну сотую часа
  • Как найти девушки в ярославле