Регулярні вирази для новачків


Дізнайтесь більше про нові кар'єрні можливості в EchoUA. Цікаві проекти, ринкова оплата, гарний колектив. Надсилайте резюме та приєднуйтеся до нас.

Що таке “регулярні вирази”?

Якщо Вам колись доводилося працювати з командним рядком, Ви, ймовірно, використовували маски імен файлів. Наприклад, щоб видалити всі файли в поточній директорії, які розпочинаються з букви “d”, можна написати rm d*.

Регулярні вирази є схожим, але набагато сильнішим інструментом для пошуку рядків, перевірки їх на відповідність певному шаблону та іншої подібної роботи. Англомовна назва цього інструмента – Regular Expressions чи просто RegExp. Інакше кажучи, регулярні вирази – спеціальна мова для опису шаблонів рядків.

Реалізація цього інструмента розрізняється в мовах програмування, хоч і не істотно. У цій статті ми орієнтуватимемося передусім на реалізацію Perl Compatible Regular Expressions.

Засади синтаксису

Зазначимо, що будь-який рядок сам по собі є регулярним виразом. Так, виразу Хаха, очевидно, відповідатиме рядок ” Хаха” і тільки він. Регулярні вирази є регістрозалежними, тому рядок “хаха” (з маленької літери) вже не відповідатиме виразу вище.

Проте вже тут слід бути акуратним – як і будь-яка мова, регулярні вирази мають спецсимволи, які треба екранувати. Ось їх список: . ^ $ * + ? { } [ ] | ( ). Екранування здійснюється у звичайний спосіб – додаванням перед спецсимволом.

Набір символів

Припустимо, ми хочемо знайти в тексті всі вигуки, що означають сміх. Просто Хаха нам не підійде – адже під нього не потраплять ” Хехе”, ” Хохо” і ” Хихи”. До того ж, проблему з регістром першої літери треба якось розв’язати.

Тут нам на допомогу прийдуть набори – замість вказівки конкретного символу, ми можемо записати цілий список, і якщо в досліджуваному рядку на вказаному місці стоятиме будь-який з названих символів, рядок вважатиметься відповідним. Набори записуються в квадратних дужках – патерну [abcd] відповідатиме будь-який із символів “a”, ” b”, ” c” або “d”.

Усередині набору більша частина спецсимволів не потребує екранування, проте використання \ перед ними не вважатиметься помилкою. Як і раніше, необхідно екранувати символи “\” і “^”, і, бажано “]” (так, [][] означає будь-який із символів “]” чи “[“, тоді як [[]х] – винятково послідовність “[х]”). Незвичайна, на перший погляд, поведінка регулярок із символом “]” насправді визначається відомими правилами, але набагато легше просто екранувати цей символ, ніж їх запам’ятовувати. Крім цього, екранувати треба символ “-“, він використовується для завдання діапазонів (див. нижче).

Якщо відразу після [ записати символ ^, то набір набуде зворотного значення – відповідним вважатиметься будь-який символ, крім вказаних. Так, патерну [^xyz] відповідає будь-який символ, крім, власне, ” x”, ” y” або “z”.

Отже, застосовуючи цей інструмент до нашого випадку, якщо ми напишемо [Хх][аоие]х[аоие], то кожен з рядків ” Хаха”, ” хехе”, ” хихи” і навіть ” Хохо” відповідатимуть шаблону.

Зумовлені класи символів

Для деяких наборів, що часто використовуються, є спеціальні шаблони. Так, для опису будь-якого пробільного символу (пропуск, табуляція, перенесення рядка) використовується /s, для цифр – /d, для символів латиниці, цифр і підкреслення ” _” – /w.

Якщо необхідно описати взагалі будь-який символ, для цього використовується крапка – .. Якщо вказані класи написати з великої літери (S, D, W) те вони змінюють своє значення на протилежне – будь-який непробільний символ, будь-який символ, який не є цифрою, і будь-який символ, крім латиниці, цифр або підкреслення відповідно.

Також за допомогою регулярних виразів є можливість перевірити положення рядка відносно іншого тексту. Вираз /b означає границю слова, /B – не границю слова, ^ – початок тексту, а $ – кінець. Так, за патерном bJavab у рядку “Java and JavaScript” знайдуться перші 4 символи, а за патерном bJavaB – символи з 10-го по 13-й (у складі слова ” JavaScript”).

Комікс про регулярні вирази з xkcd.ru

Діапазони

У Вас може виникнути необхідність позначити набір, до якого входять літери, наприклад, від ” б” до ” ф”. Замість того, щоб писати [бвгдежзиклмнопрстуф] можна скористатися механізмом діапазонів і написати [б-ф]. Так, патерну x[0-8a - F][0-8a - F] відповідає рядок “xA6”, але не відповідає “xb9” (по-перше, через те, що в діапазоні вказані тільки великі літери, по-друге, через те, що 9 не входить у проміжок 0-8).

Механізм діапазонів особливо актуальний для російської мови, адже для неї немає конструкції, аналогічної /w. Щоб позначити всі літери російського алфавіту, можна використати патерн [а-яА-ЯеЕ]. Зверніть увагу, що літера “ё” не входить до загального діапазону літер, тому її треба вказувати окремо.

Квантифікатори (вказівка кількості повторень)

Повернемося до нашого прикладу. Якщо у “вигуку, що сміється”, буде більше однієї голосної між літерами “х”, наприклад ” Хаахаааа”? Наша стара регулярка вже не зможе нам допомогти. Тут нам доведеться скористатися квантифікаторами.

КвантифікаторЧисло повтореньПрикладВідповідні рядки
{n}Рівно n разівХа{3}хаrХаааха
{m, n}Від m до n включноХа{2,4}хаХаа, Хааа, Хааааха
{m,}Не менше mХа{2,}хаХааха, Хаааха, Хааааха і т. д.
{,n}Не більше nХа{,3}хаХха, Хаха, Хааха, Хаааха

Зверніть увагу на те, що квантифікатор застосовується тільки до символу, який стоїть перед ним.

Деякі часто використовувані конструкції отримали в мові регулярних виразів спеціальні позначення:

КвантифікаторАналогЗначення
?{0,1}Нуль або одне входження
*{0,}Нуль або більше
+{1,}Одне або більше

Таким чином, за допомогою квантифікаторів ми можемо поліпшити наш шаблон для вигуків до [Хх][аоеи]+х[аоеи]*, і він зможе розпізнавати рядки ” Хааха”, ” хееееех” і ” Хихии”.

Це відбувається через те, що за умовчанням квантифікатор працює за т.зв. жадібним алгоритмом – намагається повернути якомога довший рядок, що відповідає умові. Розв’язати проблему можна двома способами. Перший – використати вираз <[^>]*>, який заборонить вважати вмістом тега праву кутову дужку. Другий – оголосити квантифікатор не жадібним, а ледачим. Робиться це за допомогою додавання праворуч до квантифікатора символу ?. Тоді для пошуку всіх тегів вираз звернеться до <.*?>.

Ревнива квантифікація

Іноді для пришвидшення пошуку (особливо в тих випадках, коли рядок не відповідає регулярному виразу) можна використати заборону алгоритму повертатися до попередніх кроків пошуку для того, щоб знайти можливі відповідності для частини регулярного виразу, що залишилася. Це називається ревнивою квантифікацією. Квантифікатор стає ревнивим за допомогою додавання до нього праворуч символу +. Ще одне застосування ревнивої квантифікації – унеможливлення небажаних збігів. Так, патерну ab*+a у рядку “ababa” відповідатимуть тільки перші три символи, але не символи з третього по п’ятий, оскільки символ “a”, який стоїть на третій позиції, вже був використаний для першого результату.

Дужкові групи

Для нашого шаблону “вигуку, що сміється”, залишилася зовсім небагато – врахувати, що літера “х” може зустрічатися більше одного разу, наприклад, ” Хахахахааахахооо”, а може і закінчуватися на літері ” х”. Ймовірно, тут треба застосувати квантифікатор для групи [аиое]+х, але якщо ми просто напишемо [аиое]х+, то квантифікатор + відноситиметься тільки до символу “х”, а не до всього виразу. Щоб це виправити, вираз треба взяти в круглі дужки: ([аиое]х)+.

Таким чином, наш вираз перетворюється на [Хх]([аиое]х?)+ – спочатку йде велика або рядкова “х”, а потім довільна ненульова кількість голосних, які (можливо, але не обов’язково) перемежовуються поодинокими рядковими “х”. Проте цей вираз розв’язує проблему лише частково – під цей вираз потраплять і такі рядки, як, наприклад, ” хихахех” – хтось, можливо, так і сміється, але припущення сумнівне. Очевидно, ми можемо використати набір з усіх голосних лише одного разу, а потім повинні якось спиратися на результат першого пошуку, але як?

Запам’ятовування результату пошуку по групі (зворотний зв’язок)

Виявляється, результат пошуку в дужковій групі записується як окремий елемент пам’яті, доступ до якої доступний для використання в подальших частинах регулярного виразу. Повертаючись до завдання з пошуком HTML-тегів на сторінці, нам може знадобитися не лише знайти теги, але й упізнати їх назву. У цьому нам може допомогти регулярний вираз <(.*?)>.

Результат пошуку за першою групою: “p”, “b”, “/b”, “i”, “/i”, “/i”, “/p”.

На результат пошуку по групі можна посилатися за допомогою виразу /n, де n – цифра від 1 до 9. Наприклад вираз (/w)(/w) /1/2 відповідають рядки ” aaaa”, ” abab”, але не відповідає ” aabb”.

Якщо вираз береться в дужки тільки для застосування до нього квантифікатора (не планується запам’ятовувати результат пошуку в цій групі), то відразу за першою дужкою варто додати ?:, наприклад (?:[abcd]+w).

З використанням цього механізму ми можемо переписати наш вираз до виду [Хх]([аоие])х?(?:1х?)*.

Перерахування

Щоб перевірити, чи задовольняє рядок хоча б одному з шаблонів, можна скористатися аналогом булевого оператора OR, який записується за допомогою символу |. Так, під шаблон Ганна|Самотність потрапляють рядки ” Ганна” і ” Самотність” відповідно. Особливо зручно використати перерахування всередині дужкових груп. Так, наприклад (?:a|b|c|d) повністю еквівалентне [abcd] (в даному випадку другий варіант прийнятніше в силу продуктивності й прочитання).

За допомогою цього оператора ми зможемо додати до нашого регулярного виразу для пошуку вигуків можливість розпізнавати сміх виду “Ахахаах” – єдиної усмішки, яка розпочинається з голосної: [Хх]([аоие])х?(?:1х?)*|[Аа]х?(?:ах?)+

Корисні сервіси

Потренуватися та / або перевірити свій регулярний вираз на якомусь тексті без написання коду можна за допомогою таких сервісів, як RegExr, Regexpal чи Regex101. Останній, на додаток, подає стислі пояснення до того, як регулярка працює.

Розібратися, як працює регулярний вираз, що потрапив до Вас, можна за допомогою сервісу Regexper – він уміє будувати зрозумілі діаграми за регулярним виразом.

RegExp Builder – візуальний конструктор функцій JavaScript для роботи з регулярними виразами.

Більше інструментів можна знайти в нашій підбірці.

Завдання на закріплення

Знайдіть час

Час має формат години:хвилини. І години, і хвилини складаються з двох цифр, приклад: 09:00. Напишіть регулярний вираз для пошуку часу в рядку: “Сніданок о 09:00”. Врахуйте, що “37:98” – некоректний час.

(2[0-3]|[0-1]d):[0-5]d

Java[^script]

Чи знайде регулярка Java[^script] щось у рядку Java? А в рядку JavaScript?

Відповіді: ні, так.

  • У рядку Java нічого не знайде, оскільки квадратні дужки в Java[^.] означають “один символ, крім указаних”. А після “Java” – кінець рядка, символів більше немає.
  • Так, знайде. Оскільки регексп регістрозалежний, то під [^script] цілком підходить символ “S”.

Колір

Напишіть регулярний вираз для пошуку HTML-кольору, заданого як #ABCDEF, тобто # і містить потім 6 шістнадцяткових символів.

Отже, треба написати вираз для опису кольору, який розпочинається з “#”, за яким йдуть 6 шістнадцяткових символів. Шістнадцятковий символ можна описати за допомогою [0-9a - fA - F]. Для його шестикратного повторення ми використовуватимемо квантифікатор {6}.

#[0-9a - fA - F]{6}

Розібрати арифметичний вираз

Арифметичний вираз складається з двох чисел і операції між ними, наприклад:

  • 1 + 2
  • 1.2 *3.4
  • – 3/ – 6
  • – 2-2

Список операцій: “+”, “-“, “*” і “/”.

Також можуть бути пропуски навколо оператора і чисел.

Напишіть регулярний вираз, який знайде як всю арифметичну дію, так і (через групи) два операнди.

Регулярний вираз для числа, можливо, дробового і негативного: -?d+ (.d+)?.

Оператор – це [+*/-]. Зазначимо, що дефіс ми екрануємо. Нам треба число, потім оператор, потім число, і необов’язкові пропуски між ними. Щоб отримати результат у необхідному форматі, додамо ?: до груп, пошук за якими нам не цікавий (окремо дробові частини), а операнди навпаки візьмемо в дужки. У результаті:

(-?d+ (?:.d+)?)s* ([-+*/]) s* (-?d+ (?:.d+)?)

Кросворди з регулярних виразів

Цікаве і корисне проведення часу! Знайти такі кросворди можна в нашій підбірці.


Удачі Вам, й пам’ятайте – не завжди задачу варто розв’язувати саме за допомогою регулярних виразів (“Програміст мав проблему, яку він розв’язував регекспами. Тепер у нього дві проблеми”). Іноді краще, наприклад, написати розгорнутий автомат кінцевих станів.

Задачі та їх розв’язання з javascript.ru; у статті використані комікси xkcd.

Київ, Харків, Одеса, Дніпро, Запоріжжя, Кривий Ріг, Вінниця, Херсон, Черкаси, Житомир, Хмельницький, Чернівці, Рівне, Івано-Франківськ, Кременчук, Тернопіль, Луцьк, Ужгород, Кам'янець-Подільський, Стрий - за статистикою саме з цих міст програмісти найбільше переїжджають працювати до Львова. А Ви розглядаєте relocate?


Trends: регулярний вираз для пошуку html-кольору, напишіть регулярний вираз для пошуку HTML кольору

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *