RegEx не так сложны, как вам кажется. Часть 1

0
624
views

Перевод статьи «Regular Expressions Demystified: RegEx isn’t as hard as it looks».

RegEx не так уж сложны

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

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

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

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

Готовы посвятить 30 минут тому, чтобы, наконец, разобраться в RegEx? Усаживайтесь поудобнее!

0. Готовим себе «песочницу»

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

Знакомимся с синтаксисом

Мы будем использовать следующий синтаксис:

/выражение/.test('строка')

«Выражение» это, собственно, то регулярное выражение, которое мы будем строить. «Строка» это тестируемая строка. Метод test возвращает true или false, в зависимости от того, находит ли он вхождение выражения в строке.

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

Символы выражения между слэшами считаются обычными. Имена переменных не будут заменяться их содержимым. Чтобы сделать их динамичными, нам нужно использовать синтаксис new RegEx(variable_name). Но об этом мы поговорим ближе к концу статьи.

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

/a/.test("a"); //true
/a/.test("b"); //false

Если сработало, вы готовы начать.

1. Начнем с малого — с букв

Нам нужно проверить, содержится ли определенный буквенный символ в строке. Поищем букву «а».

Вот нужное нам выражение и три варианта строки:

/a/.test("abc"); //true 
/a/.test("bcd"); //false 
/a/.test("cba"); //true

Данное выражение делает именно то, что мы попросили, – ищет символ «а» в тестируемой строке. В нашем случае строки «abc» и «bca» содержат букву «а». Строка «bcd» не содержит такой буквы.

Разбор

Здесь сильно много слэшей, давайте от них избавимся. Мы уже знаем, что регулярное выражение окружается слэшами (/выражение/). Но мы можем присвоить значение «/а/» переменной — так код будет выглядеть лучше.

Тот же код:

let e=/a/; 
e.test("abc"); //true 
e.test("bcd"); //false 
e.test("cba"); //true

В нашем случае выражение между слэшами это одинарный символ «а». Мы ищем одну букву.

Поиск нескольких букв

Давайте масштабируем наше решение.

Что, если нам нужно найти больше одного символа? Допустим, мы ищем последовательность символов (можете считать ее подстрокой). Пример:

/ab/.test("abacus"); //true 
/bac/.test("abacus"); //true  
/abc/.test("abacus"); //false 
/abas/.test("abacus"); //false

Чтобы вернулось true, тестируемая строка должна содержать точно такую же последовательность букв, как между слэшами.

В строке «abacus» есть «bac», но нет «abas». Несмотря на то, что все буквы сами по себе присутствуют, точного совпадения последовательности букв нет.

Разбор

Символ /…/ . Слэш (/) обозначает начало и конец регулярного выражения. На точки не обращайте внимания, я просто обозначил ими место, куда помещается шаблон поиска. Символ «а» между слэшами (/а/) это шаблон, совпадение с которым мы ищем в тестируемой строке. И буквы «abc» между слэшами (/abc/) это тоже шаблон, только при поиске не отдельного символа, а последовательности.

2. Цифровые шаблоны

Допустим, нам нужно проверить, состоит ли строка из цифр.

Пример:

let e=/0|1|2|3|4|5|6|7|8|9/;
e.test("42"); //true 
e.test("The answer is 42"); //true

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

Второй кейс не должен возвращать true (ведь эта строка состоит не только из цифр), но об этом чуть позже.

Разберем символ (|). Он здесь означает слово «или». Мы встречались с этим символом вне темы регулярных выражений, используя его как поразрядное ИЛИ и логический оператор ИЛИ в условиях (там была двойная вертикальная черта — ||). Это все тот же символ.

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

Стоит ли печатать девять вертикальных черточек? Неа.

Вот тот же пример, только шаблон записан иначе:

e=/[0123456789]/; 
e.test("42"); //true 
e.test("The answer is 42"); //все еще true

Это уже лучше. 9 вертикальных черт заменены двумя квадратными скобками. Мы сэкономили 7 символов. А это на 77.7% меньше нажатий клавиш.

Кстати, все, что в квадратных скобках, означает «или то, или это». У нас в квадратных скобках набор символов. Поэтому в тестируемой строке должен быть или 0, или 1, или 2… и так далее.

Что вы говорите? Это все еще как-то длинно? Окей, давайте попробуем еще раз:

e=/[0-9]/; 
e.test(42); //true 
e.test("42"); //true 
e.test("The answer is 42"); //true!

Как насчет такого варианта? Выглядит гораздо чище, верно? Все, что в квадратных скобках, означает «или». А 0-9 обозначает диапазон, в данном случае от нуля до девяти.

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

Префикс и суффикс

Давайте теперь вернемся к вопросу второго кейса. В строке «The answer is 42» наш test находит совпадение, потому что ищет цифровые символы где-то внутри строки. Наш шаблон не предполагает, что в строке от начала до конца должны быть только цифры.

Здесь нам могут помочь символы ^ и $.

  • ^ означает начало строки. Учтите, что этот символ — двойной агент, способный нас обмануть. Мы снимем с него маску в последнем разделе.
  • $ означает конец строки.

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

/^a/.test("abc"); //true 
/^a/.test("bca"); //false 
/^http/.test("https://techrocks.ru"); //true
/^http/.test("ftp://techrocks.ru"); //false

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

Вторая строка начинается с «b», а наш шаблон ищет «a».

В четвертом кейсе мы ищем «http» в начале строки, а строка начинается с «ftp». Поэтому и во втором, и в четвертом случае мы получаем false.

А теперь давайте рассмотрим применение суффикса. Символ $ в конце шаблона заставляет test искать совпадение в конце строки.

/js$/.test("regex.js"); //true 
/js$/.test("regex.sj"); //false

Это можно озвучить как «Ищи js, за которым следует конец строки» или «Ищи строку, которая заканчивается на js».

Совпадение строки с шаблоном от начала до конца

Теперь мы можем написать шаблон целой строки.

let e=/^[0-9]$/ 
e.test("42"); //false - НЕТ! 
e.test("The answer is 42"); //false 
e.test("7"); //true

Странно, но первый вариант не вернул совпадения, когда мы добавили к шаблону ^ и $.

Шаблон /^[0-9]$/ читается следующим образом: «Иди в начало строки. Ищи единичный (одинарный) цифровой символ из набора символов. Проверь, заканчивается ли строка сразу после него». Вот почему мы получили true только в последнем случае. Там в строке была только одна цифра.

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

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

Сказка о трех мушкетерах

Знак вопроса (?), плюс (+) и астериск (*) встретились на поле боя. Их зрение слегка отличается.

Скромный знак вопроса говорит: «Я могу увидеть ничего или что-то одно».

Плюс говорит: «Мне нужно увидеть как минимум что-то одно или больше».

Астериск говорит: «Я круче всех. Я могу видеть ничего, что-то одно или больше».

Но один из них не полностью раскрыл, на что он способен.

Первым на сцену выходит знак вопроса:

/a?/.test(""); //true 
/a?/.test("a"); //true 
/a?/.test("b"); //true! 
/a?/.test("aa"); //true 
/^a?$/.test("aa"); //false
  1. Шаблон совпадает с пустой строкой «», поскольку «?» означает 0 символов или 1 символ.
  2. Совпадает с «а», поскольку в строке есть один символ «а».
  3. Совпадает с «b», поскольку в строке 0 символов «а».
  4. Совпадает с «аа», поскольку есть совпадение с первым символом «а», а второй символ не является частью шаблона.
  5. /^a?$/ не находит совпадений в строке «аа». Здесь не должно быть символа «а» или должен быть один символ «а» во всей строке и ничего, кроме него.

Плюс смотрит на вопросительный знак и замечает: «Я впечатлен, но твой фокус такой бинарный!». Он занимает сцену и показывает свои способности:

/a+/.test("a"); //true 
/a+/.test("aa"); //true 
/a+/.test("ba"); //true! 
/^a+$/.test("aa"); //true  
/a+/.test(""); //false 
/a+/.test("b"); //false 
/^a+$/.test("ab"); //false

Помните, что сказал плюс? Он совпадает с одним или большим количеством вхождений шаблона в тестируемой строке.

Во всех строках, где получено true, есть одна или более букв «а». Даже в 4-м кейсе, где мы ищем /^a+$/.

С false теперь должно быть все понятно, однако остановимся на последнем кейсе, где вернулось false. Шаблон /^a+$/ подразумевает, что вся строка должна состоять из одной или более букв «а», без любых других букв. Вот почему «ab» не проходит тест.

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

/a*/.test("a"); //true 
/a*/.test("aa"); //true 
/a*/.test("ba"); //true 
/a*/.test(""); //true 
/a*/.test("b"); //true 
/^a*$/.test("aa"); //true 
/^a*$/.test(""); //true  
/^a*$/.test("ab"); //false

Со всеми кейсами, кроме последнего, могли также справиться и вопросительный знак с плюсом. Шаблон /^a*$/ означает, что во всей строке от начала до конца может не быть буквы «а», может быть одна буква «а» или больше одной (но речь идет только об «а», никаких других букв в строке быть не должно). Вот почему пустая строка «»»» прошла тест, а по строке «ab» вернулось false.

Вернемся к примеру с поиском цифр

Помните, на чем мы остановились, прежде чем познакомились с тремя мушкетерами? Да, на кейсе «The answer is 42».

Что нам нужно сделать, если мы хотим проверить, содержит ли строка только цифры (одну или больше)?

//Давайте добавим плюс. 
let e=/^[0-9]+$/ 
e.test("4"); //true 
e.test("42"); //true 
e.test("The answer 42"); //false - Ура!

На помощь нам приходит плюс. Он означает одно или больше вхождений идущего перед ним символа или шаблона. В нашем случае это одна цифра или больше.

Также этот шаблон не совпадает с последней строкой, потому что в начале строки нет цифр.

Практика

  • Можете попробовать написать шаблон для шестнадцатеричных чисел (состоящих из цифр 0-9 и букв a-f с опциональным # впереди)?
  • Как насчет двоичных чисел? Можете проверить, состоит ли строка только из нулей и единиц?

Драматическая концовка

О, почти забыл. Шаблон [0-9] означает любой цифровой символ, но то же самое можно выразить иначе, при помощи \d.

let e=/^\d+$/; 
e.test("4"); //true 
e.test("42"); //true 
e.test("The answer 42"); //false - Ура!

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

Есть целый набор таких специальных шаблонов для обозначения цифр (\d), буквенно-числовых символов (\w), пробелов (\s).

Краткий обзор

  • [123] Выражение в квадратных скобках означает набор символов. Совпадение с любым из этих символов позволит пройти тест. Но имеется в виду только ОДИН символ.
  • [0-9] Поиск одной цифры в диапазоне от 0 до 9.
  • [0-5] Поиск одной цифры в диапазоне от 0 до 5.
  • [a-z] Поиск одной буквы, от a до z.
  • [A-F] Поиск одной буквы, от A до F.
  • [123]+ Поиск одного или более вхождений символов из набора. Например, этот шаблон совпадет с подстрокой «23132» строки «abc23132», поскольку там содержатся цифры 1, 2 и 3.
  • | вертикальная черта означает «или».
  • \d Сокращение, означающие цифры. Совпадает с любой одной цифрой.
  • \D Сокращение для нецифровых символов. Возвращает совпадение с любым символом, кроме любой цифры.

Продолжение читайте здесь.

ОСТАВЬТЕ ОТВЕТ

Please enter your comment!
Please enter your name here