Регулярні вирази в perl h2>
Регулярні
висловів є найбільш складною темою практично для будь-якого програміста:
як для новачка, тільки що почав вивчати perl, так і для досвідченого
програміста, що раніше не зустрічався з регулярними виразами. Насправді,
регулярні вирази не так складні, як може здатися на перший погляд,
просто з самого початку потрібно побудувати правильні аналоги. p>
Для
Спершу розберемося - що ж таке регулярний вираз. По-англійськи пишеться так
- Regular Expression (звідси часто зустрічається скорочення "regexp" і
навіть по-русски "регексп"). По-перше, не варто шукати сенс у самому
терміні - це дослівний переклад з англійської мови, який представляється
занадто абстрактним. Але що б зрозуміти за яким принципом працюють регулярні
вирази, нам і потрібно саме що абстрагуватися на рівень припущень.
Приклад з пошуком входження підрядка повинен бути зрозумілий усім. Але, на самому
справі, хоча за допомогою регулярних виразів можна легко знайти будь-який збіг,
цей приклад не розкриває всієї краси регекспов. Краще згадайте як працює
пошук файлів за шаблоном (або по масці). Алгоритм має на увазі використання
певних символів (wildcards), які дозволяють як би закрити ту частину
імені, яка для нас не має значення. Однак самі wildcards не використовуються
в іменах файлів (що робить алгоритм менш гнучким). Так ось, пошук файлів за
шаблоном дозволяє відібрати ті імена файлів, що задовольняють заданим
умові. При цьому, можна вказати і точне ім'я, а можна в якомусь місці імені
зробити припущення (за допомогою все тих же wildcards). Так ось, регулярні
вирази дозволяють виконувати аналогічний пошук в межах певної
послідовності байт. Додайте до цього можливість роботи з різними
частинами утвореної маски як з окремими одиницями і ви зрозумієте принадність
регекспов. p>
Далі,
позбудемося упередження що регекспи призначені тільки для роботи з
рядками. Так, технологія орієнтована насамперед на рядки, (описание
бінарних даних вимагає трохи більших зусиль), але ніхто не заважає вам упакувати
дані в структуру і інтерполювати ім'я змінної, що містить значення цієї
структури всередині регекспа. p>
Ну
ніби як з базовою теорією розібралися. Тут залишається додати, що зрозумівши
філософію регулярних виразів, ви зможете самостійно розібратися з будь-яким
форматом регекспов. Так, наприклад SQL так само має на увазі можливість
використання регулярних виразів, але на відміну від perl, формат опису шаблонів
в SQL дещо іншою. p>
За частинам і все одразу h2>
Мета
регулярного виразу можна описати так: знайти ділянку рядки, відповідний
певним шаблоном, в основі якого лежить принцип припущень. Тобто,
шаблон не обов'язково є точною відповідністю шуканої підрядки. Якщо ви
все-таки не понімаетете що таке регулярні вирази і для яких цілей їх
використовують, повертайтеся до прикладу пошуку файлів за маскою. p>
Всередині
регулярних виразів мешкають кілька жадібних, багаторукого і цікавих істот,
не познайомившись з якими ви не зможете складати регекспи. Мова про
квантіфікаторах, уявних символи, класах і засланнях. Тут посилання - це посилання
на знайдений текст. Це стандартне визначення, але мені воно здається трохи не
відповідним. Накопичувачі або контейнери більш вдале визначення, тому що вони
фактично містять у собі частину (або всі) збігу. Під класами
маються на увазі набори символів. Уявні символи - це твердження. Тобто
уявний символ не є частиною шуканого значення, але, в навантаження до всього
іншого, вимагає що б виконувати певні умови. Квантіфікатор - це
ознака повторення шаблону. p>
Без
стакан ... тьху, практики тут не розберешся. Тому пропоную почати з самого
простого. Візьмемо елементарний приклад з рядками. Нижче наводиться шаблон в
якому зустрічаються всі три вищеописаних звіра p>
/^([^ s] *) s (.*)/ p>
Пробіжимося
за шаблоном зліва-направо. Слеш вказують кордону регекспа, так що їх відразу
можна викинути. Символ ^ відноситься до уявним символів. Він прив'язує шаблон до початку
рядка. Що це означає? Це означає, що ми знайдемо шукане, тільки у випадку якщо
воно знаходиться на початку вихідної рядка. Елементарно, Ватсон. Дивимося найпростіший
приклад p>
$ source
= 'Pupkin'; p>
$ source
= ~/^ Pupkin /; # Оператор поверне істину, так p>
# як в $ source Pupkin з самого
початку p>
$ source
= 'Vasya Pupkin'; p>
$ source
= ~/^ Pupkin /; # А тут вже буде брехня, тому що перед p>
# Пупкиным стоїть його ім'я. p>
Так
Отож, якщо прибрати з шаблону уявний символ прив'язки до початку рядка (^), то
результатом роботи другого оператора те ж буде істина. Для самих нетямущих
перепишу шаблони p>
$ source
= 'Pupkin'; p>
$ source
= ~/Pupkin /; # Оператор поверне істину,
так p>
# як в $ source Pupkin з
самого початку p>
$ source
= 'Vasya Pupkin'; p>
$ source
= ~/Pupkin /; # Тут те ж буде істина,
так як p>
# Пупкин в рядку є,
хоча і не з початку. p>
# Але ж і шаблон не
вимагав Пупкіна на початку рядка p>
Тепер
зрозуміло, що таке уявні символи? Просто додаткова умова, а не частина
шуканого. p>
Отже,
повернімося до наших баранів p>
([^ s] *) s (.*) p>
слеш
ми відкинули як обмежувачі, з прив'язкою до початку рядка той же розібралися.
Далі у нас круглі дужки. Ось тут, круглі дужки мають те ж саме значення,
що і взагалі в мовах програмування - вони змінюють пріоритет і групують
оператори. Так і тут - треба розглядати все те що в дужках як якесь
об'єднання. Одразу зауважу, що пара круглих дужок утворюють контейнер (або
посилання на знайдений текст у стандартному визначенні). p>
І
що ми бачимо? У нас два контейнери, розділених s. s - це спеціальний
символ, який вказує на будь-який символ з підмножини пробільних (пробіл,
табуляція, etc ...) Уточню. Те що у нас між контейнерами вказує на одиничний
пробільний символ. Ми підійшли до найважливішої основоположною - в регулярному
вираженні (просто шаблон) будь-який символ відповідає самому собі, якщо
тільки він не є метасимволів зі спеціальним значенням. Ось s як раз і
відноситься до таких метасимволів. Зізнаюся, що наш приклад взагалі часто-густо
складається з метасимволів. Так, так, у ньому немає жодного символу, відповідного
самому собі. p>
Отже,
що ж ми з'ясували? Ми з'ясували, що будемо шукати щось, що складається з двох
контейнерів, які розділені між собою одиничним пробільних символом.
Тепер настав час розібратися з вмістом контейнерів. Почнемо з правого - він простіше.
Точка в регекспе визначає будь-який символ, крім символу нового рядка (є
деякі моменти, коли абсолютно будь-який). Сподіваюся, що таке будь-який символ
зрозуміло? Це може бути
"1", "2", "8", "D", "a", "r"
або "b" і так далі і тому подібне від кодів з нуля до самого 255. p>
Ну
а тепер, дозвольте представити вам ... Символ * перетворює попередню частину
шаблону в маленьке ненажерливе істота - квантіфікатор. Цей самий
квантіфікатор зжере всі символи (тому що у нас перед цим була вказівка точка
- Будь-який символ) до самого кінця рядка. Безкоштовний сир лише в мишоловці, але
квантіфікатор цього не знає. Ми не даремно помістили його в контейнер. Після того,
як обробка регулярного виразу буде завершена у нас буде контейнер, в
якому збережеться все те, що зжер квантіфікатор. Так як у нас всього два
контейнера, то це контейнер буде у нас під номером два. Надалі ми так
і скажемо perl - а ну, віддай нам вміст другого контейнера. Ось так то. p>
Отже,
чого ми досягли? Ми будемо шукати щось, що складається з двох контейнерів,
розділених одиничним пробільних символом. Правий контейнер у нас буде
містити всю ту частину рядка, який знаходиться після одиничного пробільних
символу. Після виконання регулярного виразу ми зможемо використовувати
вміст правого (ну і лівого те саме) контейнера на свій розсуд. Ось
такий висновок на даний момент. p>
Пора
приступати до вмісту лівого контейнера. Нагадаю як він виглядає p>
[^ s] * p>
Квадратні
дужки визначають клас символів. Що таке клас символів? Припустимо, що
шукане не може бути представлене послідовністю символів, тобто
підрядком. Інакше кажучи, у прикладі з Пупкиным ми не можемо явно вказати p>
/Pupkin/ p>
Не
важливо, з яких причин. Може бути шукане дуже довге, а може бути
шукане - довільні варіанти рядків, що складаються з певних символів. Так
от у такому випадку ми визначимо клас символів. Наприклад символи латинського
алфавіту визначаються таким класом p>
[a-zA-Z] p>
Зауважте
як зручно - ми не вказуємо всі символи підряд. Ми просто визначаємо кордону з
допомогою метасимволи - (це як би навіть і не зовсім метасимволи, а тільки в
даному випадку). Замість перерахування цифрових символів ми можемо записати p>
[0-9] p>
Хоча
для цифрових символів є більш ефективне рішення - метасимволи d. Отже, у
нас в лівій частині визначено клас символів. Але якийсь цікавий клас
виходить - ніби прив'язаний до початку рядка. Ні, метасимволи ^ всередині класу
вказує на заперечення символів класу. Це означає, що на місці цієї частини
шаблон повинен знаходитися будь-який символ, що не входить до складу класу. Тобто,
для прикладу p>
[^ 0-9] p>
вказує,
що тут може бути будь-який нецифровому символ. Так і в нашому прикладі. Ну а з
метасимволів s ви вже знайомі. З огляду на заперечення отримуємо - будь-який
непробельний символ. Врахуйте, що клас визначає тільки безліч для
відповідності або заперечення, але не безліч для відбору. Тобто, якщо у вас
клас, то під шаблон потрапить тільки один символ, що задовольняє умові. Для
того, щоб відібрати кілька символів потрібно використовувати квантіфікатор, що
ми і робимо після опису класу символів. Тепер, щоб розібратися для
відбору яких рядків можна скористатися цим шаблоном давайте напишемо приклад. p>
#! C:/per/bin/perl-w p>
use strict; p>
reg ( "Vasya Pupkin "); p>
reg ( "Vasya Pupkin "); p>
reg ( "Vasyattpupkin "); p>
sub reg ( p>
print
"$ 1 = $ 1n $ 2 = $ 2nn" if $ _ [0] = ~/([^ s] *) s (.*)/; p>
) p>
В
результаті вийде p>
$ 1 = Vasya p>
$ 2 = Pupkin p>
$ 1 = p>
$ 2 = Vasya
Pupkin p>
$ 1 = Vasya p>
$ 2 = pupkin p>
Тепер
давайте розберемося чому і як. Перший тест однозначно потрапляє під шаблон:
Vasya не складається з пробільних символів, далі йде один пробільний символ
(натурально пробіл), а Pupkin становить частину, що залишилася рядка. Результат
другого тесту у нас какой то дивний. Перший контейнер у нас виявився порожній, а
друге чому то містить весь рядок без ведучого пробілу. З чим це пов'язано?
Та з тим, що квантіфікатор * означає нуль або більше. Так як перша в
рядку у нас пробільний символ, у правий контейнер, згідно з умовою, потрапляє
нуль непробельних символів. Далі, пробіл то не входить до складу контейнерів. Ну
а другий контейнер жере весь рядок до кінця. Третій варіант, я думаю, зрозумілий.
Я вже говорив, що кожен символ регулярного виразу відповідає
одиничного. І тільки квантіфікатори дозволяють їсти декілька символів одного
класу. У шаблоні контейнери розділені поодиноким пробільних символом. У лівий
контейнер потрапляє Vasya.
(табуляція у прикладі) пропускається, а правий контейнер їсть все що залишилося
- У тому числі і другу табулятор. Таким чином, отримуємо Пупкіна з ведучою
табуляцією. p>
Напевно
це не зовсім той результат, який ми хотіли б отримати. Нафига нам провідні
пробіли. Ну ви ж знаєте достатньо, що б перетворити роздільник контейнерів
в квантіфікатор. Ну так приступайте:) p>
/([^ s] *) s *(.*)/ p>
Тепер
наше регулярний вираз пропускатиме між ім'ям і прізвищем все
пробільні символи. Результат повинен бути таким. p>
$ 1 = Vasya p>
$ 2 = Pupkin p>
$ 1 = p>
$ 2 = Vasya
Pupkin p>
$ 1 = Vasya p>
Залишилось
з'ясувати, яким чином правильно інтерпретувати значення другого тесту.
По-перше потрібно позбутися від прив'язки до початку рядка (на мою цей спецсимволів
вже встиг загубитися в наших прикладах:). Отже, шаблон повинен обробляти
ситуації, коли на початку рядка може бути один або декілька пробільних
символів. Ну це ж елементарно, скажете ви, потрібно просто додати в початок
шаблону s і зробити з нього квантіфікатор. p>
/s * ([^ s] *) s *(.*)/ p>
Вітаю!
Ви пройшли вступний курс з регекспам;) p>
Про ненажеру і інші тонкощі h2>
Тепер
варто поговорити про тонкощі, які мають місце при складання
регулярних виразів. Найвідоміший - це ненажерливість квантіфікатора.
Чи означає це наступне: квантіфікатор має звичку вбирати в себе
максимальну рядок, яку тільки може з'їсти. Для прикладу можна взяти
наступний шаблон p>
/.* pupkin/ p>
Сенс
його очевидна - шукати Пупкіна перед яким може бути що то еще. Однак якщо
джерело містить кілька Пупкин, то квантіфікатор зжере все аж до
останнього Пупкіна. Наприклад пошук по цьому регекспу в рядку p>
Vasya
pupkin pupkin p>
приведе
до того, що квантіфікатор сожрет "Vasya pupkin", а не "Vasya
"Як можна було очікувати. Для вирішення цієї проблеми, гідною
пильної уваги, є ряд спеціальних символів. Перш за все символ
питання? дозволяє обмежити апетит квантіфікатора мінімальної рядком
збіги. Повертаючись до нашого прикладу з кількома Пупкиными отримаємо p>
/.*? pupkin/ p>
для
коректного поїдання "Vasya" з рядка "Vasya pupkin
pupkin ". Далі, конструкції з фігурними дужками дозволяють визначати
кордону апетиту квантіфікатора. Всередині фігурних дужок (природно після
самого квантіфікатора) може бути зазначено одне або два значення, перерахованих
через кому, які відповідно визначають межі жадібності. Пригадати про
специфікатор *. Аналогічний йому + перетворює шаблон в ненажеру, якого не
задовольняє менше одного збігу. Тобто при використанні + умова
відбору є істинним тільки коли є 1 і більше збігів. Зауважте,
що верхня межа у нас невизначений і може бути опущений всередині конструкції з
фігурними дужками. Якщо всередині фігурних дужок вказати всього одне значення без
ком, то квантіфікатор зжере тільки такий рядок, в якій збігів з
шаблоном буде саме зазначену кількість. p>
Що
б вам не здалося що ми знову збираємося в теоретичні хащі, нагадаю,
що все те про що ми зараз говоримо відноситься тільки до перевірки умови на
збіг ділянки рядки з шаблоном. Мало того, з квантіфікаторамі це далеко
не всі тонкощі. Існують ще деякі аспекти, такі як правила застосування
квантіфікаторов близько кордонів контейнерів. Але з цим вам доведеться розбиратися
самостійно. Загалом можна навести такий простий приклад p>
/(. (2,10 })/ p>
Це
регулярний вираз буде поміщати в контейнер від двох до десяти символів
рядка. При чому, з огляду на жадібність, по можливості квантіфікатор буде вбирати
найбільшу рядок. Тобто якщо рядок довжиною 10 або більше символів, то в
контейнер потраплять саме 10, а не 2 і не 5 символів. p>
$ 1 = Vasya Pupkin p>
$ 2 = in p>
$ 1 = Vasya Pupkin p>
$ 2 = kin p>
$ 1 = Vasya pupkin p>
$ 2 = kin p>
В
Загалом з квантіфікаторамі можна ще багато пустувати. Всього розповісти все одно
не удасться. Тут тільки один засіб - практикуватися. p>
Далі
на порядку денному таке поняття як альтернативні шаблони. Це елементи
регулярного виразу, які дозволяють визначати кілька варіантів
шаблону. Найбільш наочний приклад це визначення протоколу в рядку URL p>
/^ (http | ftp)/ p>
Уявний
символ прив'язки до початку рядка може бути поміщений і всередині круглих дужок --
результат від цього не змінюється. Дивно, адже конструкція з круглими дужками
використовується для визначення алтернатів, адже вона ж використовується і для
угруповання в контейнер. Саме так. Альтернативні шаблони призводять до
автоматичного виникнення нового контейнера. Тут важливо не облажався і
правильно визначити номер контейнера при отриманні результатів. Контейнер,
який був відкритий раніше, має найменший номер. Таким чином можна
розібратися навіть у вкладених контейнерах. p>
Є
ще одна фіча, яка може вам знадобитися. Це, так звані,
додаткові конструкції. Вони дозволяють виконувати перевірку до або після поточного
місця в шаблоні, але при цьому в сам шаблон не входять. Їх описувати я не буду,
тому що це звичайна довідкова інформація, яка є в будь-якій книзі по
perl. Просто - що б ви знали. p>
Ну
і в якості підсумку за курсом середньої заглибленості в регулярні вирази можна
зібрати все, що ми дізналися у вигляді перерахування складових елементів регулярних
виразів p>
поодинокі
символи (characters) - він і є одиночний, чого його коментувати;) p>
класи символів (character classes) - [], [^] p>
альтернативні шаблон?? (alternative match patterns) - (X | X | X) p>
квантіфікатори (quantifiers) - (),?, +, * p>
уявні символи (assertions) - s, ^, $, etc ... p>
контейнери
(backreferences) - $ 1, $ 2, $ x p>
додаткові
конструкції p>
Від
теорії до практики p>
В
perl є три основних оператора які працюють з рядками. Це p>
m//
- Перевірка збігів (або пошук) p>
s// /
- Підстановка p>
tr// /
- Заміна p>
Кожен
оператор має свої свої модифікатори. Для початку розглянемо для чого потрібні всі
три оператори. p>
Перший
- M// (або просто//) використовується для пошуку збігів з вказаним шаблоном.
Це якраз те, на чому ми тренувалися вище. Там же і приклад, як можна його
використовувати. Другий оператор s///дозволяє не тільки знаходити певні
ділянки, що збігаються з заданим шаблоном, але й виконувати нерівнозначних
підстановку. Фактично, s///це те ж що і m// (навіть модифікатори
збігаються), але з можливістю довільної підстановки. Сенс нерівнозначної
підстановки відкривається коли ми звертаємося до третього оператора tr// /.
Оператор заміни може заміняти ділянки тільки на рівнозначні по довжині. Як
наслідок - він працює швидше s// /. З усіх операторів s///найгнучкіший - він
дозволяє виконувати все те, що можуть m// і tr// /. З його допомогою можна згорнути
гори. Але, за все доводиться платити і тут ми розплачуємося швидкістю. tr// /
можна взагалі не розглядати (якщо кінцевий ви не фанат швидкості). А ось на s// /
хочеться зупинитися детальніше. p>
Перш
за все хочу попередити - не намагайтеся запхати в праву частину оператора s// /
(тобто в ту, яка визначає що будемо підставляти замість знайденого
шаблону) квантіфікатори, уявні символи і взагалі всякі інші
невизначеності. Все має бути чітко й однозначно. Робота оператора s///(в
інше як і m// /) має на увазі компіляцію на кожному етапі звернення до
регулярним виразом. Якщо ви не лінувалися (та й так він часто зустрічається) то
вже знаєте про модифікатор глобального пошуку g, який змушує працювати
регексп протягом залишку від попереднього результату і так до кінця рядка.
Так от, якщо в правій частині розмістити ім'я змінної-контейнера і заюзать
регексп з модифікаторами o і g, то напевно вийде бардак, так як o забороняє
повторну компіляцію шаблону. Загалом тут треба бути гранично уважним.
Ще хочу звернути вашу увагу на модифікатори e і ee. Вони дозволяють виконувати
код безпосередньо в процесі роботи регулярного виразу. Якщо у вас дуже
складне завдання і його дуже важко реалізувати в одному регулярному виразі,
розбийте їх на складові в правій частині - і працювати буде швидше і налагоджувати
простіше. p>
Список літератури h2>
Для
підготовки даної роботи були використані матеріали з сайту http://prolib.ru/
p>