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

0
1149
views

Перевод статьи «4 Practical Use Cases for Regular Expressions».

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

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

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

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

Для начала — небольшое вступление о регулярных выражениях в JavaScript

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

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

Я знаю, что вы думаете: «А как насчет этого жуткого синтаксиса?!» Тут я с вами согласен. Я пользуюсь регулярными выражениями уже несколько лет, и каждый раз, когда мне нужно что-то кроме банального поиска совпадений, я гуглю правильный способ это сделать.

Но как бы вы иначе все это реализовали?..

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

Анатомия регулярных выражений

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

В JavaScript регулярные выражения могут определяться двумя способами:

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

let regExp = new RegExp('a|b');

2. При помощи литеральной нотации, где выражение окружается парой слэшей “/”.

let regExp = /a|b/;

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

Модификаторы или флаги

Называть их можно по-разному. Эти обозначения привносят дополнительный смысл в ваши регулярные выражения. Их шесть. Одни вы будете использовать постоянно, другие, может, пару раз за всю жизнь. Давайте их рассмотрим:

  • g — осуществляет глобальный поиск. Другими словами, вместо того чтобы вернуть первое попавшееся совпадение с шаблоном, поиск вернет все совпадения, найденные в строке.
  • i — поиск, не зависящий от регистра. Этот флаг очень полезен, поскольку позволяет игнорировать регистр при поиске совпадений. Без него «Hello» и «HELLO» считаются разными словами.
  • m — многострочный поиск. Если в строке встретятся символы переноса строки, благодаря этому флагу они будут проигнорированы и поиск на них не остановится.
  • s — позволяет . (точке) соответствовать также символу новой строки. Обычно символ точки соответствует любому одиночному символу, кроме символа новой строки.
  • u — «unicode». С этим флагом шаблон рассматривается как последовательность цифровых обозначений unicode.
  • y — осуществляет «липкий» поиск, т. е., поиск, начинающийся с указанной позиции (а не с начала строки).

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

//If you're using the RegExp object
 let re = new RegExp('[H|h]ello', 'gm');
 //If you're going with the literal syntax
 let re = /[H|h]ello/gm;

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

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

Указанные здесь use cases должны продемонстрировать, насколько полезны регулярные выражения. Следует помнить, что применяются они не только в логике кода: их использование поддерживается в большинстве IDE для поиска и замены текста.

Соответствие пароля шаблону

Проверка пароля

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

Ладно, последнюю часть я сам добавил, но суть вы уловили: в сообщении описывается шаблон, которому вы должны следовать, чтобы пароль оказался валидным. Для валидации можно, конечно, использовать просто код JavaScript, но зачем, если можно все это выразить одной строкой?

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

/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*\W).{8,}$/g

Вот отрывок кода, который вы можете протестировать:

let re = /^(?=.[a-z])(?=.[A-Z])(?=.\d)(?=.\W).{8,}$/g
 let passwords = ["Fernando", "f3rn4", "F3rnand0!", "fernando123!"]
 passwords.forEach( p => {
     let matches = p.match(re)
     if(!matches) console.log(p, "INVALID PASSWORD")
     else console.log(p, "is a valid password!")
 })
 /*
 Fernando INVALID PASSWORD
 f3rn4 INVALID PASSWORD
 F3rnand0! is a valid password!
 fernando123! INVALID PASSWORD
 */

В общем, мы используем то, что называется «позитивные опережающие проверки» (positive lookaheads). Это разделы выражения, которые движок будет искать внутри текста безотносительно к тому, где они находятся. Таким разделом является все, что указано внутри (?=…).

  • (?=.*[a-z]) — означает совпадение с любым символом, за которым следует буква в нижнем регистре.
  • (?=.*[A-Z]) — то же самое, только с буквой в верхнем регистре.
  • (?=.*\d) — все, за чем следует цифра.
  • (?=.*\W) — любой символ (кроме символа переноса строки), за которым следует любой не цифробуквенный символ.
  • .{8,} — проверяет, чтобы последовательность, совпадающая с шаблоном, насчитывала как минимум 8 символов (любых символов — на это указывает точка).
  • ^ и $ — проверяет, чтобы совпадение начиналось с начала слова (^) и до конца ($). Таким образом, допускается только совпадение с шаблоном целого слова, частичные совпадения не рассматриваются.

Если все эти условия соблюдены, возвращается соответствие с шаблоном, в противном случае пароль не валидный.

Проверка формата Email

Проверка формата электронного адреса

Такую проверку я реализовывал, наверное, миллион раз, пока занимался веб-разработкой. Как часто вам случалось видеть сообщение «Invalid Email format» при заполнении регистрационной формы? Сегодня эту проверку уже осуществляет элемент input типа «email».

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

Вот это волшебное регулярное выражение для полной проверки email-адреса:

/^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/

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

Для начала мы проверяем, валидно ли имя пользователя, т. е., просто смотрим, использованы ли валидные символы и есть ли там хоть один такой символ (знак + в конце).

^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+

Затем мы проверяем, есть ли там символ @ и имя хоста:

@[a-zA-Z0-9-]+

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

Наконец, опциональная часть — для проверки TLD (домена первого уровня), т. е., расширения доменного имени:

(?:\.[a-zA-Z0-9-]+)*$/

Об опциональности этой части говорит знак * в конце. Астериск (*) означает, что требуется 0 или больше вхождений этой группы (сама группа ограничена скобками). С этим шаблоном совпадет как .com, так и .co.uk ).

Вот короткий пример кода, показывающий работу этого выражения:

let emailRE = /^[a-zA-Z0-9.!#$%&’+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:.[a-zA-Z0-9-]+)$/
 let emails = ["fernando", "fernadno@", "fernando@test", "fernando@test.com", "valid_email123@host2.com", "a@1.com"]
 emails.forEach( p => {
     let matches = p.match(emailRE)
     if(!matches) console.log(p, "INVALID EMAIL")
     else console.log(p, "is a valid email!")
 })
 /*
 fernando INVALID EMAIL
 fernadno@ INVALID EMAIL
 fernando@test is a valid email!
 fernando@test.com is a valid email!
 valid_email123@host2.com is a valid email!
 a@1.com is a valid email!
 */

Умная замена символов

Умная замена

Но достаточно проверок, давайте рассмотрим модификацию строк.

Это еще одна сфера, где регулярные выражения показывают себя во всей красе, поскольку позволяют осуществлять весьма замысловатые замены символов. В данном примере я собираюсь показать, как превратить запись в camel case (ну, вы знаете, это когда выПишетеВсёВотТак) в обычную. Это простой пример, но он должен продемонстрировать, чего можно достичь при помощи группировок символов.

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

А вот альтернатива с применением регулярного выражения:

let camelRE = /([A-Z])/g
 let phrase = "thisIsACamelCaseString"
 console.log(phrase.replace(camelRE, " $1")
 /*
 this Is A Camel Case String
 */

Да, это все! Группа (скобки и все, что между ними) сохраняет совпадающую часть и вы можете обращаться к ней при помощи «$1». Если у вас больше одной группы, это число растет — $2, $3 и т. д. Суть здесь в том, что эти выражения будут соответствовать только одиночным заглавным буквам, расположенным где угодно в строке (благодаря флагу g) и вы сможете заменить эти буквы (благодаря вызову метода replace) ими же, только строчными, с добавлением пробела перед ними.

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

Превращение олдскульной функции в стрелочную функцию

Стрелочная функция

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

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

Чтобы было понятнее, я собираюсь превратить это:

function sayHello(first_name, last_name){
     console.log("Hello there ", first_name, last_name)
 }

вот в это:

const sayHello = (first_name, last_name) => {
     console.log("Hello there ", first_name, last_name)
 }

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

function (.+)(\(.+\))(\{.+\})

А затем все дело сводится к вызову метода replace. Опять же, вы можете использовать для этого вашу любимую IDE, а здесь я представлю быстрый скрипт на Node.js, с которым можно поиграться:

const fs = require("fs")
 const regExp = /function (.+)((.+))({.+})/gms
 fs.readFile("./test2.js", (err, cnt) => {
     console.log(cnt.toString().replace(regExp, "const $1 = $2 => $3"))
 })

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

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

Заключение

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

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

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

Please enter your comment!
Please enter your name here