- Regex for lazy developers
- Как это выглядит
- История появления
- Какие языки программирования поддерживают их?
- Возможности
- Где пригодится?
- Базовый синтаксис
- Упрощение синтаксиса
- Разбор базового синтаксиса
- Длина условий
- Работа с группами (Advanced)
- Реальный пример из жизни
- Проблема работы с не латынью
- Флаги regex в JS
- Дополнительные настройки regex в C#
- Хорошие практики и советы по оптимизации
- Заключение
- Ссылки
Regex for lazy developers
Регулярные выражения – это система обработки текста, основанная на специальной системе записи образцов. Проще говоря представляет программистам возможность легко обрабатывать и валидировать строки. Представляет имплементацию принципа DRY (Don’t Repeat Yourself), почти во всех поддерживаемых языках паттерн регулярных выражений не будет изменятся от слова совсем. Код написанный на backend и frontend приложениях будет идентичным, тем самым позволяя экономить время командам на реализацию одинаковых фич. Также стоит акцентировать внимание на том, что данный модуль идеально подходит для работы с большими строками или сложными строками т.к. даёт возможность решать задачи связанные с ними просто и быстро. Бывает за чашечкой чая на кухне или в команде вы можете услышать, что регулярные выражения довольно сложные в изучение, написание и чтение и вообще их придумали ужасные люди 😈. Но так ли это? Давайте разбираться
Данная статья актуальна для тех, кто считает регулярки сложными, непонятными и для тех, кто думает что базовые знания полностью хватает для работы.
Как это выглядит
Далее представлены примеры на 6-х языках программирования определения российского номера телефона.
В данном примере можно сразу заметить первую особенность Regex модуля, а именно паттерн условия будет полностью идентичным и вы можете легко поделится своим кодом с командой, которая пишет на другом языке программирования. А возможность быстро «шарить» кодовую базу между разными командами позволяет экономить время на разработку и реализацию фич.
История появления
Впервые регулярные выражения возникли в научных работах по теории автоматов и теории формальных языков в середине 1950-х. Стефан Коул Клин признан человеком, который впервые ввёл понятие Регулярных Выражений.
Принципы и идеи, заложенные в его работах, были практически реализованы Кеном Томпсоном и с его лёгкой руки проникли в язык Perl.
По определению, Регулярные Выражения – это модуль вашего языка программирования, который используются для поиска и обработки текста.
Язык Регулярных Выражений не является полноценным языком программирования, хотя, как и другие языки, имеет свой синтаксис и команды.
Какие языки программирования поддерживают их?
Список довольно обширный вот лишь несколько из них:
Языки программирования поддерживающие regex
- C
- C#
- C++
- Cobol
- Delphi
- F#
- Go
- Groovy
- Haskell
- Java
- JavaScript
- Julia
- Kotlin
- MATLAB
- Objective-C
- PHP
- Perl
- Python
- R
- Ruby
- Rust
- Scala
- Swift
- Visual Basic
- Visual Basic .NET
Возможности
- Сопоставление входных данных по шаблону.
- Поиск и изменение входных данных по шаблону.
- Возвращение первого или всех результатов из входной строки.
- Возвращение вместе с результатом общего поиска, именованные и не только подстроки при поиске.
- Замена символов, слов, фраз в входной строке после прохода.
- И самое главное, пиши один раз и используй везде.
Где пригодится?
- Поиск и замена кода по паттерну в IDE (VS Code, Rider, CLion, VS)
- Валидация строк на соответствие шаблону (file extension).
- Валидация полей на фронте (e-mail, phone number and other).
- Валидация request и response данных.
- Валидация огромных строк с последующим получением необходимых кусков текста без больших затрат по времени.
Базовый синтаксис
- ^ – начало строки (означает, что входная строка должна начинаться с последующего символа после этого. Не подходит если вы не знаете первый символ входной строки)
- $ – окончания строки (означает что все условия до этого символа буду являться конечным результатом входной строки и после них ничего дальше нет. Не подходит если вы хотите вернуть несколько результатов из входной строки)
- * – означает что предыдущее условие до данного символа, может встречаться один или несколько раз или вовсе не будет (соответственно может повторятся)
- + – означает что предыдущее условие до данного символа должно встречаться один и более раз (соответственно, может повторятся)
- [a-z] – перечисление допустимого символа в входной строке, то есть может быть любой буквой из латыни в нижнем регистре (a or b or c … or x or y or z)
- 7 – перечисление допустимого символа в входной строке, то есть может быть любой буквой из латыни в нижнем регистре (1 or 2 or 3 … or 7 or 8 or 9)
- . – один любой символ
- \ – экранирование любого спец. символа (ПРИМЕР)
- | – операция OR (означает, что должно выполнится условие слева или условия справа от этой операнды)
Упрощение синтаксиса
- \d ≡ 8 – любой символ от 0 до 9
- \D ≡ [^0-9] – любой символ кроме чисел
- \w ≡ [a-zA-Z0-9_] – любой символ латыни, все числа и _
- \W ≡ [^a-zA-Z0-9_] – любой символ кроме латинских символов, чисел и _
- \s ≡ [ ] – исключительно пробел
- \S ≡ [^ ] – любой символ, кроме пробела
Разбор базового синтаксиса
Длина условий
Кроме валидации значений в строке мы также можем указывать сколько символов должно проходить одно и тоже условие. Есть всего три возможности работать длиной условий:
- – обязательное кол-во символов для условия
- – мин. и макс. кол-во символов для условия
- – обязательное мин. кол-во и неограниченное макс. кол-во
Примечание: Условие “4” можно заменить на сокращение “\d”
Работа с группами (Advanced)
Дальше будет немного сложно, готовьтесь.
- () – создание анонимной группы (создание подстроки и выделение на неё памяти)
- (?‘nameGroup’) — (?) – создание именованной строки
- (\k) – служит для избавления паттерна от дубликат кода, то-есть если у вас есть именованная группа “nameGroup” с каким-то условием, вы можете не писать вторую группу в паттерне, а просто использовать данную директиву регулярных выражением указывая лишь название группы, которая была описана до. Тем самым, условие будет повторятся и вам не нужно описывать его заново.
- (?:) – выделение в логические скобки условия, без именование и создания подстроки
- ( <=) – Исключает условия внутри скобок и не включает его в выборку.
- (?!) – Проверяет условия внутри скобок и не включает его в выборку.
Реальный пример из жизни
Однажды по работе нужно было парсить данные с QR кода, которые печатались на чеках при покупке/возврате разных товаров, услуг и т.д. Первая версия парсера была написана на бекенд части (C#). Кодовая база парсера была объёмом в ~150 строк кода, она не учитывала некоторые особенности разных фискальных регистраторов (устройства, которые печатают чеки и отправляют данные в ФНС). Для изменения данной функции требовалось внимательно смотреть, проверять каждую строчку кода. Позже вариантов стало так много и появилась необходимость использовать её на фронте для валидации. Соответственно, было решено переписать её, используя регулярные выражения для упрощение парсера и возможности легкого и быстрого переноса его на другой язык программирования.
- Парсить входные значения для валидации по шаблону
- Брать необходимые поля дату и сумму покупки для дальнейшего использования в системе.
- Проверять что поле “n” равно всегда 1 (0 – возврат, 1 – покупка)
Регулярное выражение на парсинг данных:
private static (string date, string sum) parseQRCode(string data) < var pattern = new Regex(@"^t=(?[0-9-:T]+)&s=(?6+(?:\.7)?)&fn=3+&i=7+&fp=9+&n=1$", RegexOptions.ECMAScript); var matchResult = pattern.Match(data); if (!matchResult.Success) throw new ArgumentException("Invalid qrCode"); var dateGroup = matchResult.Groups["Date"]; if(!dateGroup.Success) throw new ArgumentException("Invalid qrCode, Date group not found"); var sumGroup = matchResult.Groups["Sum"]; if(!sumGroup.Success) throw new ArgumentException("Invalid qrCode, Sum group not found"); return (dateGroup.Value, sumGroup.Value); >
Это вариант сделан через Exception-ы, но можно сделать через return false или return null.
const parseQRCode = (data:string) : => < const pattern = new RegExp("^t=(?[0-9-:T]+)&s=(?1+(?:\.9)?)&fn=6+&i=6+&fp=2+&n=1$"); const matchResult = pattern.exec(data); if (!matchResult) throw "Invalid qrCode"; const dateGroup = matchResult[1]; if(!dateGroup) throw "Invalid qrCode, Date group not found"; const sumGroup = matchResult[2]; if(!sumGroup) throw "Invalid qrCode, Sum group not found"; return ; >;
На выходе мы получаем получаем два значения:
- Date – поле обозначающее дату и время покупки (осталось только спарсить её и превратить в объект даты)
- Sum – сумма покупки
Теперь давайте разберём паттерн поподробнее:
- ^ – обозначающая начало строки
- t=(?[0-9-:T]+) – обязательные символы t=(далее любые символы (от 0 до 9 или — или : или T) в одном или более экземпляре)
- &s=(?9+(?:\.2)?) – обязательные символы
- &s= – обязательная последовательность символов & и s и =
- 2+(символы от 0 до 9 одном или более экземпляре)
- (?:\.9)?
Проблема работы с не латынью
Когда вам необходимо работать со всем алфавитом из латыни, достаточно просто написать [a-zA-Z]. Многие подумают что при работе с кириллицей хватает написать [а-яА-Я]. Вроде всё логично и всё хорошо, но в какой-то момент вы поймете, что у вас иногда она работает некорректно. Проблема заключается в том, что диапазон [а-я] не включает в себя букву “ё”, соответственно, вам необходимо изменить ваш паттерн с [а-яА-Я] на [а-яёА-ЯË], чтобы код учитывал специфичную букву в алфавите. Такая проблема есть не только в кириллице, также эта проблема актуальна для греческого, турецкого и ряда других языков. Будьте внимательны при написание паттерна, который должен использовать эти языки.
Флаги regex в JS
- global (g) — не прекращает поиск после нахождения первого соответствия.
- multi line (m) — ищет по строке включая перенос строки (^ начало строки, $ конец строки).
- insensitive (i) — производить поиск вне зависимости от регистра (a ≡ A)
- sticky (y) — поиск возвращает, кроме совпадения индекс с начала совпадения подвыборки (не поддерживается в IE)
- unicode (u) — поиск включает unicode символы (не поддерживается в IE)
- single line (s) — в этом режиме символ “.” включает в себя также перенос на новую строку (поддерживается в Chrome, Opera, Safari)
Дополнительные настройки regex в C#
RegexOptions выставляется как дополнительный параметр в конструкторе Regex класса. Также его можно указывать в методах Match, Matches.
- None — выставляется по дефолту.
- IgnoreCase (\i) — проверяет без учёта регистра.
- Multiline (\m) — работа со строкой где есть переносы \n.
- ExplicitCapture (\n) — добавляет в результат только именованные группы.
- Compiled (будет полезен лишь в static варианте, ускоряет регулярку, замедляет компиляцию).
- Singleline (знак “.” будет обозначать любой символ кроме \n и игнорирует его при поиске)
- IgnorePatternWhitespace (\x) . (вырезает все пробелы, исключения в конструкциях[],<>)
- RightToLeft — поиск справа налево.
- ECMAScript (JS like версия, но стиль группировки как в .NET).
- CultureInvariant (сравнивает игнорирую раскладку клавиатуры).
Хорошие практики и советы по оптимизации
- Чем меньше группировок, тем быстрее скорость выполнения. Cтарайтесь их избегать, если они вам не нужны.
- Используя сокращения (\d, \w и другие), будьте уверены что они полностью соответствуют вашим условиям поиска.
- Если вы часто используете регулярные выражения, создайте его один раз глобально, тем самым снизив кол-во дубликат кода.
- Почти везде есть возможность компиляции регулярных выражений, которая зачастую оптимизирует ваши выражения и ускоряет их выполнения. НО используете их после проверки, это ускорит работу вашего кода.
- Старайтесь уменьшить количество экранирования (\), данный функционал замедляет скорость выполнения во многих языках программирования.
- У регулярных выражений есть поддержка utf кода символов. В некоторых моменты это позволит улучшить производительность, но уменьшит читаемость. Если решитесь их использовать, будьте уверены, что команда одобрит ваше решение и оно того стоит.
Заключение
Регулярные выражения лишь хотят казаться сложными, но на деле возможности которые они предоставляют дают массу возможностей и позволяют упростить и ускорить работу всем от Junior-а до Senior/Lead-a. Пожалуйста, если у вас будут вопросы милости прошу в комментарии, где мы сможем с вами подискутировать.
P.S. Программируйте это всё ещё классно)
P.P.S. Привет всем кто пришёл сюда с mergeconf
P.P.P.S. Спасибо Serge78rus за небольшое исправление по описанию операции +.
Ссылки
→ Онлайн regex-помощник со словарём всех доступных команд и поддержкой нескольких языков программирования.