Scroll to navigation

perlretut(1) 2007-10-27-16:31 perlretut(1)

Назва

perlretut - навчальний посібник регулярних виразів Perl

Опис

Ця сторінка забезпечує підставовим посібником для розуміння, створення і використання регулярних виразів Perl. Вона служить як доповнення до сторінки-довідки про регулярні вирази perlre(1). Регулярні вирази складають одне ціле з операторами "m//", "s///", "qr//" і "split", тож цей посібник накладається деякою мірою з розділом "Оператори типу залапковування регулярних виразів" сторінки perlop(1) і "split" з perlfunc(1).

Perl є мовою, що славиться своїми видатними можливостями в галузі обробки текстів, а регулярні вирази являються одним з основних факторів, що криється за цією славою. Регулярні вирази Perl включають ефективність і гнучкість, яку ви не знайдете в більшості інших мов програмування. Оволодіння навіть основами регулярних виразів дозволить вам маніпулювання текстом з несподіваною легкістю.

Що таке регулярний вираз? Це просто ланцюжок, що описує шаблон (зразок). Шаблони широко застосовуються на сьогоднішній день; прикладами можуть служити шаблони, що ви їх вводите в пошуковий двигун для знаходження веб-сторінок, або шаблони для переліку файлів в каталозі, як от "ls *.txt" або "dir *.*". У Perl, шаблони, описані за допомогою регулярних виразів, використовуються для пошуку ланцюжків, здобуття їхніх частин і для операції пошуку та заміни.

Регулярні вирази мають незаслужену репутацію як такі, що являються занадто абстрактними і важкими в розумінні. Проте, регулярні вирази будуються, послуговуючись простими поняттями, такими як умови і цикли, тож їх не набагато важче вивчити ніж відповідні if-умови і while-цикли в самій мові Perl. Насправді, основною перепоною у вивченні є звикання до лаконічної нотації, що застосовується для вираження цих понятть.

Цей посібник полегшує процес навчання, обговорюючи принципи регулярних виразів разом з нотацією, по одному за один раз і з багатьма прикладами. Перша частина посібника прогресує від найпростішої форми пошуку слова до основних понять про регулярні вирази. Якщо ви засвоєте першу частину, то володітимете всіма необхідними засобами для вирішення 98% ваших потреб. Друга частина посібника призначається для тих, що оволоділи основами і жадають додаткових потужних знарядь. Тут розглянуто досконаліші оператори регулярних виразів і найостанніші передові нововведення 5.6.0.

Для стислості, ми іноді скорочуємо "регулярні вирази" до "рег. вирази". Як тільки ви побачите останнє, то можете подумки здійснити операцію "s/ре-(вирази)/регулярні \1/gi" і стане зрозуміліше.

Частина 1: основи

Простий збіг зі словом

Найпростіший регулярний вираз - це просто слово, або узагальнено - ланцюжок знаків. Регулярний вираз, що складається зі слова збіжиться з будь-яким ланцюжком, що містить це слово:


"Hello World" =~ /World/; # зійдеться
Про що йдеться в цьому твердженні? "Hello World" - це звичайний ланцюжок, включений у подвійні лапки. "World" являється регулярним виразом, а "//", що оточують "/World/" вказують perl(1) здійснити пошук по ланцюжку щодо співпадань. Оператор "=~" пов'язує ланцюжок із регулярним виразом і спричинить істинне значення, якщо регулярний вираз зійшовся, або хибне - якщо ні. В нашому випадку, "World" збіглося з другим словом з "Hello World", тож вираз виявиться істинним. Подібні вирази корисні в умовах:


if ("Hello World" =~ /World/) {
print "It matches\n";
} else {
print "It doesn't match\n";
}
Існують корисні варіації на цю тему. Зміст збігу можна обернути на протилежний за допомогою оператору "!~":


if ("Hello World" !~ /World/) {
print "It doesn't match\n";
} else {
print "It matches\n";
}
Літеральний ланцюжок регулярного виразу можна замінити на змінну:


$greeting = "World";
if ("Hello World" =~ /$greeting/) {
print "It matches\n";
} else {
print "It doesn't match\n";
}
Якщо ви порівнюєте супроти змінної $_, то "$_ =~" частину можна опустити:


$_ = "Hello World";
if (/World/) {
print "It matches\n";
} else {
print "It doesn't match\n";
}
І, нарешті, стандартні обмежувачі регулярного виразу "//" можна поміняти на довільні, якщо додати 'm' (скорочення від 'match') попереду:


"Hello World" =~ m!World!; # обмежувач '!'
"Hello World" =~ m{World}; # парні обмежувачі '{}'
"/usr/bin/perl" =~ m"/perl"; # лапки як обмежувач,
# '/' в ролі звичайного знаку
"/World/", "m!World!" і "m{World}", усі представляють ту саму річ. Коли, наприклад, лапки використовуються як обмежувач, правий слеш '/' перетворюється у звичайний знак і можна використати в регулярному виразі без якихось перешкод.

Давайте розглянемо, як різноманітні вирази спробують збігтися з "Hello World":


"Hello World" =~ /world/; # не сходиться за регістром
"Hello World" =~ /o W/; # зійдеться, ' ' є звичайним символом
"Hello World" =~ /oW/; # не зійдеться
"Hello World" =~ /World /; # не зійдеться, немає ' ' наприкінці
Перший вираз "world" не збігається оскільки регулярні вирази чутливі до регістру. Другий зійдеться тому, що підланцюжок 'o W' можна знайти в "Hello World". Символ пробілу ' ' розглядається в регулярних виразах як звичайний знак, і він необхідний для цього співпадання. Саме відсутність пробілу спричинила неспівпадання в третьому прикладі з 'oW'. Четвертий приклад 'World ' не зійшовся оскільки додано зайвий пробіл після слова, відсутній в ланцюжкові. Мораль полягає в тому, що регулярний вираз повинен точно зійтися з частиною ланцюжка, щоб вираз виявився істинним.

Якщо регулярний вираз сходиться в декількох місцях усередині ланцюжка, perl(1) завжди намагатиметься здійснити збіг у найпершому можливому положенні:


"Hello World" =~ /o/; # збігається з 'o' з 'Hello'
"That hat is red" =~ /hat/; # збігається з 'hat' з 'That'
Стосовно символьного порівняння, існує ще декілька речей, які вам слід знати. Перш за все, не всі символи можна використовувати "такими якими вони є" при порівнянні. Деякі знаки, названі метасимволами (або метазнаками), резервовано для власний нотацій регулярних виразів. Метазнаками являються


{}[]()^$.|*+?\
Значення кожного з них буде пояснено далі, але поки-що важливо просто знати, що співпадання з буквальним знаком, що відповідає метасимволу, можна добитися, коли додати обернений слеш попереду:


"2+2=4" =~ /2+2/; # не збігається, + є метасимволом
"2+2=4" =~ /2\+2/; # збігається, \+ розглянуто як звичайний +
"The interval is [0,1)." =~ /[0,1)./ # синтаксична помилка!
"The interval is [0,1)." =~ /\[0,1\)\./ # збігається
"/usr/bin/perl" =~ /\/usr\/bin\/perl/; # збігається
В останньому прикладі, правий слеш також екрановано зворотнім, оскільки саме слеш використовується як обмежувач регулярного виразу. Однак, це може призвести до захворювання на СПЗ (синдром похилих зубочисток), тому частіше розбірливішим буде змінити обмежувачі.


"/usr/bin/perl" =~ m!/usr/bin/perl!; # легше прочитати
Знак зворотнього слешу, самий являється метасимволом, тож його також потрібно екранувати:


'C:\WIN32' =~ /C:\\WIN/; # збігається
На додачу до метазнаків, існують деякі символи ASCII, котрі на мають друковних відповідників і відображаються натомість як керівні (екрановані) послідовності. Поширеними прикладами являються "\t", що позначає табуляцію, "\n" - знак нового рядка, "\r" - повернення каретки, "\a" - дзвоник тощо. Якщо ваш ланцюжок краще представити як послідовність довільних байтів, вісімкові керівні послідовності, наприклад "\033", або шістнадцяткові, як от "\x1B", можуть виявитись натуральнішим представленням ваших байтів. Ось декілька прикладів екранованих символів:


"1000\t2000" =~ m(0\t2) # збігається
"1000\n2000" =~ /0\n20/ # збігається
"1000\t2000" =~ /\000\t2/ # не збігається, "0" не тотожно "\000"
"cat" =~ /\143\x61\x74/ # збігається, але це досить дивний спосіб
# написання слова "cat"
Якщо ви вже побули трохи з Perl, вся ця дискусія про керівні послідовності може видатись вам знайомою. Подібні керівні послідовності використовуються в ланцюжках, внесених у подвійні лапки, і насправді, регулярні вирази Perl в більшості саме так і розглядаються, як такі, що включено в подвійні лапки. Це означає, що з таким самим успіхом можна вживати змінні в регулярних виразах. Схоже то того як це відбувається в ланцюжках у подвійних лапках, значення змінних буде розкрито до того як регулярний вираз обчислюється для порівнювальних цілей. Тож ми маємо:


$foo = 'house';
'housecat' =~ /$foo/; # збігається
'cathouse' =~ /cat$foo/; # збігається
'housecat' =~ /${foo}cat/; # збігається
Досі, все гаразд. Зі знаннями, отриманими вище, ви вже можете здійснювати пошуки із майже будь-яким літеральним ланцюжком регулярного виразу, який вам спаде на думку. Наступне, це дуже проста емуляція програми grep(1) Юніксу:


% cat > simple_grep
#!/usr/bin/perl
$regexp = shift;
while (<>) {
print if /$regexp/;
}
^D

% chmod +x simple_grep

% simple_grep abba /usr/dict/words
Babbage
cabbage
cabbages
sabbath
Sabbathize
Sabbathizes
sabbatical
scabbard
scabbards
Цю програму легко зрозуміти. "#!/usr/bin/perl", це стандартний спосіб виклику програми perl(1) з оболонки. "$regexp = shift;" зберігає перший аргумент командного рядка як регулярний вираз, що використовуватиметься, тоді як решта аргументів командного рядка вважатимуться назвами файлів. "while (<>)" здійснює циклічне проходження через всі рядки файлів. Для кожного рядка, "print if /$regexp/;" виводить його, якщо регулярний вираз збігся з даним рядком. У цьому коді, обидва, "print" і "$regexp" приховано використовують стандартну змінну $_.

Усі вищенаведені регулярні вирази збігаються з будь-яким місцем в ланцюжкові, де знайдено відповідність шаблонові. Іноді, проте, ми би хотіли вказати де саме очікується збіг з регулярним виразом. Щоб добитися цього, нам необхідно використати опорні (закріплювальні) метасимволи "^" і "$". Закріплювач "^" означає збіг напочатку ланцюжка, тоді як "$" - наприкінці, тобто перед знаком нового рядка. Ось приклади їхнього використання:


"housekeeper" =~ /keeper/; # збігається
"housekeeper" =~ /^keeper/; # не збігається
"housekeeper" =~ /keeper$/; # збігається
"housekeeper\n" =~ /keeper$/; # збігається
Другий регулярний вираз не збігся тому що "^" змушує "keeper" сходитись тільки напочатку ланцюжка, але "housekeeper" містить "keeper" посередині. Напротивагу, третій регулярний вираз зійшовся оскільки "$" вказує, що "keeper" повинно знаходитись наприкінці, як воно і є.

Коли вживаються обидва, "^" і "$" одночасно, регулярний вираз повинен зійтися з обидвома, початком і кінцем ланцюжка, іншими словами, вираз намагається збігтися з цілим ланцюжком. Розгляньмо


"keeper" =~ /^keep$/; # не збігається
"keeper" =~ /^keeper$/; # зійдеться
"" =~ /^$/; # ^$ збігається з порожнім ланцюжком
Перший регулярний вираз не зійшовся через те що ланцюжок включає більше ніж просто "keep". Оскільки другий і третій вирази повністю відповідають ланцюжкові, вони співпали. Використання обох, "^" із "$" в регулярних виразах викликають порівняння з цілим ланцюжком, тож ви отримаєте повний контроль щодо того які ланцюжки співпадають, а які - ні. Скажімо ви шукаєте приятеля під назвою "bert", самого по собі в ланцюжку:


"dogbert" =~ /bert/; # збігається, але не те, що вам потрібно

"dilbert" =~ /^bert/; # не збігається, але...
"bertram" =~ /^bert/; # збігається, але ще не те

"bertram" =~ /^bert$/; # не збігається, добре
"dilbert" =~ /^bert$/; # не збігається, добре
"bert" =~ /^bert$/; # збігається, чудово
Звичайно, у випадку літеральних ланцюжків, легше би було скористатися просто перевіркою еквівалентності ланцюжків "$string eq 'bert'", що одночасно є ефективнішим. Регулярні вирази з "^...$", стануть по-справжньому корисними, якщо ми додамо потужніші знаряддя, описані нижче.

Використання символьних класів

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

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


/cat/; # зійдеться з 'cat'
/[bcr]at/; # зійдеться з 'bat, 'cat', або 'rat'
/item[0123456789]/; # зійдеться з 'item0' ... аж до 'item9'
"abc" =~ /[cab]/; # зійдеться з 'a' в ланцюжкові
В останньому твердженні, навіть якщо 'c' стоїть першою в наборі класу, найпершим місцем, де регулярний вираз може зійтися з ланцюжком, є 'a'.


/[yY][eE][sS]/; # зійдеться 'yes' у регістро-незалежний спосіб
# 'yes', 'Yes', 'YES' тощо
Цей регулярний вираз відображає поширене завдання: порівняння, не чутливе до регістру. Perl надає спосіб уникнути всіх цих квадратних дужок, якщо додати модифікатор 'i' до регулярного виразу. Таким чином, "/[yY][eE][sS]/;" можна записати наново як просто "/yes/i;". Літера 'i' походить від case-insensitive і являється одним з модифікаторів регулярних виразів. Ми зустрінемо решту модифікаторів згодом у цьому посібнику.

Як ми побачили в попередньому підрозділі, існують звичайні знаки, що представляють самих себе, і спеціальні знаки, які вимагають оберненого слешу "\" для власного представлення. Те саме справджується і для класу символів, але набір звичайних і спеціальних символів відрізняється всередині класу від того, що використовується поза його межами. Спеціальними для класу символів являються "-]\^$". "]" є спеціальним, оскільки він позначає кінець символьного класу. "$" позначає скалярну змінну, а "\" використовується для керівних послідовностей. Ось приклад оперування спеціальними "]$\":


/[\]c]def/; # збігається з ']def', або 'cdef'
$x = 'bcr';
/[$x]at/; # збігається з 'bat', 'cat', або 'rat'
/[\$x]at/; # збігається з '$at', або 'xat'
/[\\$x]at/; # збігається з '\at', 'bat, 'cat', або 'rat'
Останні два, трохи складніші. В "[\$x]", зворотній слеш екранує знак $, тож клас символів містить два члени "$" і "x". У "[\\$x]", зворотній слеш самий екрановано, отже $x розглянуто як змінну і розкрито так, ніби вона знаходилась у подвійних лапках.

Спеціальний символ '-' діє як оператор діапазону всередині класу знаків, тож набір суміжних знаків можна записати як інтервал. З діапазонами, незграбні "[0123456789]" і "[abc...xyz]" стають лаконічними "[0-9]" і "[a-z]". Декілька прикладів:


/item[0-9]/; # збігається з 'item0' аж до 'item9'
/[0-9bx-z]aa/; # збігається з '0aa', ..., '9aa',
# 'baa', 'xaa', 'yaa', or 'zaa'
/[0-9a-fA-F]/; # збігається з шістнадцятковим числом
/[0-9a-zA-Z_]/; # збігається зі знаком, який може бути
# частиною "слова" (знаки, що складають
# назви змінних Perl)
Якщо риска '-' стоїть як перший, або останній символ класу, вона вважатиметься звичайним знаком; "[-ab]", "[ab-]" і "[a\-b]", усі еквівалентні.

Спеціальний символ "^" як перший знак класу позначає заперечувальний клас, що збігатиметься з будь-яким символом окрім тих, що внесено в цей клас. Обидва, "[...]" і "[^...]" повинні збігтися з якимось символом, інакше порівнювання зазнає невдачі. Таким чином,


/[^a]at/; # не зійдеться з 'aat', або 'at', зате з рештою,
# як от 'bat', 'cat, '0at', '%at' тощо.
/[^0-9]/; # збігається з нечисловим символом
/[a^]at/; # сходиться з 'aat', або '^at'; тут '^' є звичайним
# символом
Але навіть "[0-9]" може набриднути писати по багато разів, тож заради економії друкування і покращення читаємості, Perl включає декілька скорочень для спільних класів символів:


позначає цифру, тотожно [0-9]


позначає пробіл, тотожно [\ \t\r\n\f]


позначає символ, який може бути частиною слова (алфавітно-числовий, або _), тотожно [0-9a-zA-Z_]


протилежний \d; будь-який символ окрім цифри, тотожно [^0-9]


протилежний \s; будь-який символ окрім пробілу, тотожно [^\s]


протилежний \w; будь-який символ окрім тих, що можуть складати слово, [^\w]

\&.

(крапка) збігається з будь-яким знаком окрім \n

Скорочення "\d\s\w\D\S\W" можна вживати всередині і поза межами класу знаків. Ось декілька прикладів використання:


/\d\d:\d\d:\d\d/; # збігається з форматом часу гг:хх:сс
/[\d\s]/; # збігається з будь-якою цифрою, або пробілом
/\w\W\w/; # збігається зі знаком, що складатиме слово
# із наступним знаком, що не є частиною слова із
# наступним знаком слова
/..rt/; # збігається з будь-якими двома знаками і 'rt'
/end\./; # збігається з 'end.'
/end[.]/; # те саме, збігається з 'end.'
Оскільки крапка являється метасимволом, її необхідно екранувати для збігу зі звичайною крапкою. Через те, що "\d" і "\w", наприклад, це набори символів, буде неправильно думати про "[^\d\w]" як еквівалент "[\D\W]"; насправді, "[^\d\w]" тотожно "[^\w]", що, в свою чергу, є тим самим що й "[\W]". Пригадайте правила Де-Моргана.

Корисним опорним символом для базових регулярних виразів являється обмежувач слова "\b". Він збігається з межею між символом слова і таким, що не може належати слову, тобто з серединою "\w\W" і "\W\w":


$x = "Housecat catenates house and cat";
$x =~ /cat/; # збігається з cat у 'housecat'
$x =~ /\bcat/; # збігається cat у 'catenates'
$x =~ /cat\b/; # збігається з cat у 'housecat'
$x =~ /\bcat\b/; # збігається 'cat' наприкінці ланцюжка
Зверніть увагу на останній приклад - кінець ланцюжка вважається обмежувачем слова.

Ви можливо питаєте себе, чому '.' збігається з усім, окрім "\n", чому не з усіма символами? Причина криється в тому, що іноді хочеться зіставити зразок з рядком, ігноруючи символ нового рядка. Наприклад, тоді як ланцюжок "\n" означає один рядок, але ми хотіли би думати про це як про "порожньо". Таким чином,


"" =~ /^$/; # збігається
"\n" =~ /^$/; # збігається, "\n" ігнорується

"" =~ /./; # не зійшлося; вимагає символу
"" =~ /^.$/; # не зійшлося; вимагає символу
"\n" =~ /^.$/; # не зійшлося; вимагає символу відмінного від "\n"
"a" =~ /^.$/; # збіглося
"a\n" =~ /^.$/; # збіглося; ігнорує "\n"
Це поводження зручне тим, що нам, як правило, вигідно ігнорувати символи нового рядка під час відліку і зіставлення зі знаками рядка. Проте, іноді нам хотілося би звернути увагу на знаки нового рядка. Більше того, - заставити "^" із "$" позначати початок і кінець рядка, але всередині ланцюжка. Perl дозволяє на вибрати між тим, чи ігнорувати символ нового рядка, чи звертати на нього увагу через використання модифікаторів "//s" і "//m". Літери "s" і "m" є скороченнями від "single line" (один рядок) і "multiline" (багато рядків) і вони відповідають за те, чи ланцюжок розглядається як неперервний, чи як такий, що складається з набору рядків. Ці два модифікатори впливають на як інтерпретується регулярний вираз: 1) як визначено клас знаків '.'; 2) де можуть збігатися опорні "^" і "$". Ось чотири можливі комбінації:

модифікатори відсутні (//)

: стандартне поводження. '.' збігається з будь-яким знаком за винятком "\n". "^" збігається тільки з початком ланцюжка, а "$" - з кінцем, або кінцевим положенням перед знаком нового рядка.

модифікатор s (//s)

: вважатиме ланцюжок як такий, що складається з одного неперервного рядка. '.' збігається з будь-яким знаком, навіть "\n". "^" збігається тільки з початком ланцюжка, а "$" - з кінцем, або кінцевим положенням попереду знаку нового рядка.

модифікатор m (//m)

: розгляне ланцюжок як набір рядків. '.' збігається з будь-яким знаком окрім "\n". "^" і "$" спроможні зійтися з початком і кінцем будь-якого рядка в ланцюжкові.

обидва модифікатори (//sm)

: розгляне ланцюжок як неперервний рядок, але збереже можливість виявлення багатьох рядків-складових. '.' збігається з будь-яким символом, навіть "\n". Проте, "^" і "$" спроможні зійтися з початком і кінцем будь-якого рядка в ланцюжкові.

Ось декілька прикладів "//s" і "//m" в дії:


$x = "There once was a girl\nWho programmed in Perl\n";

$x =~ /^Who/; # не збігається, "Who" не на початку ланцюжка
$x =~ /^Who/s; # не збігається, "Who" не на початку ланцюжка
$x =~ /^Who/m; # збігається, "Who" на початку другого рядка
$x =~ /^Who/sm; # збігається, "Who" на початку другого рядка

$x =~ /girl.Who/; # не збігається, "." не зійшлася з "\n"
$x =~ /girl.Who/s; # збігається, "." зійшлася з "\n"
$x =~ /girl.Who/m; # не збігається, "." не зійшлася "\n"
$x =~ /girl.Who/sm; # збігається, "." зійшлася з "\n"
У більшості випадків, стандартного поводження вистачає, але "//s" і "//m" часом також виявляються дуже корисними. При використанні "//m", початок ланцюжка збіжиться ще з "\A", а кінець - з опорними "\Z" (збігається з обома, кінцем і попереднім знаком нового рядка), і "\z" (збігається тільки з кінцем):


$x =~ /^Who/m; # збігається, "Who" на початку другого рядка
$x =~ /\AWho/m; # не збігається, "Who", це не початок ланцюжка

$x =~ /girl$/m; # збігається, "girl" вкінці першого рядка
$x =~ /girl\Z/m; # не збігається, "girl" не в кінці ланцюжка

$x =~ /Perl\Z/m; # збігається, "Perl" перед новим рядком вкінці
$x =~ /Perl\z/m; # не збігається, "Perl", це не кінець ланцюжка
Ми вже засвоїли, як створити вибір між класами знаків регулярного виразу. Як щодо вибору між словами, або символьними ланцюжками? Саме про це піде мова в наступному розділі.

Порівняння з тим, або іншим

Іноді нам хотілося би, щоб наш регулярний вираз міг збігтися з різними словами або ланцюжковими послідовностями. Цього можна добитися за допомогою метасимволу альтернативи "|". Для збігу з "dog", або "cat", наприклад, ми складемо регулярний вираз "dog|cat". Як і раніше, Perl намагатиметься знайти збіг у найпершому можливому положенні в ланцюжкові. В кожній позиції ланцюжка, двигун регулярних виразів спробує спочатку зіставити ланцюжок з "dog", а якщо це не зійшлося, тоді з "cat". Якщо порівняння з "cat" також зазнало невдачі, perl перейде до наступної позиції в ланцюжкові. Ось декілька прикладів:


"cats and dogs" =~ /cat|dog|bird/; # збігається з "cat"
"cats and dogs" =~ /dog|cat|bird/; # збігається з "cat"
Незважаючи на те, що "dog" стоїть першим з можливих альтернатив у другому прикладі, "cat" спроможний зійтися першим усередині ланцюжка.


"cats" =~ /c|ca|cat|cats/; # збігається з "c"
"cats" =~ /cats|cat|ca|c/; # збігається з "cats"
Тут усі альтернативи збігаються у перший позиції ланцюжка, тож перша з них буде тією, що спричинила збіг. Якщо деякі з альтернатив, це скорочення інших, покладіть найдовші напочатку, щоб дати їм можливість співпасти.


"cab" =~ /a|b|c/ # збігається з "c"
# /a|b|c/ == /[abc]/
Останній приклад демонструє, що класи символів тотожні символьним альтернативам.

Групування об'єктів і ієрархічний збіг

Альтернативи ланцюжків надають регулярному виразу певний вибір між ланцюжками, що можуть зійтися, але цього недостатньо. Справа в тім, що кожна альтернатива - це цілий регулярний вираз, тоді як нам іноді потрібні альтернативи тільки для частини регулярного виразу. Для прикладу, уявімо, що ми хочемо знайти слова housecats і housekeepers. Регулярний вираз "housecat|housekeeper" доб'ється цього, але він неефективний, оскільки нам довелося надрукувати слово "house" двічі. Було би гарно, мати якусь частину регулярного виразу сталою, як от "house", а якусь як альтернативну, наприклад "cat|keeper".

Метасимволи групування "()" вирішать цю проблему. Групування дозволяє розглянути частину регулярного виразу як одне ціле. Тобто, частини регулярного виразу об'єднано разом шляхом включення їх у дужки. Таким чином, ми можемо розв'язати задачу з "housecat|housekeeper" регулярним виразом "house(cat|keeper)". Останнє означає порівняння з "house", за яким може слідувати, або "cat", або "keeper". Декілька прикладів:


/(a|b)b/; # збігається з 'ab', або 'bb' /(ac|b)b/; # збігається з 'acb', або 'bb' /(^a|b)c/; # збігається з 'ac' напочатку, або 'bc' деінде /(a|[bc])d/; # збігається з 'ad', 'bd', або 'cd' /house(cat|)/; # збігається з 'housecat', або 'house' /house(cat(s|)|)/; # збігається з 'housecats', 'housecat', або # 'house'. Зверніть увагу на гніздування груп. /(19|20|)\d\d/; # збігається з роками 19xx, 20xx, або з проблемою # Y2K - xx "20" =~ /(19|20|)\d\d/; # збігається з нульовою альтернативою # '()\d\d', оскільки '20\d\d' не зійшлося
Альтернативи поводяться так само в групах, як і поза ними - береться крайня ліва альтернатива, що дозволила регулярному виразові зійтися. Тож в останньому прикладі, "20" зійдеться з другою альтернативою, але нічого не залишилось для збігу з наступними двома цифрами "\d\d". Perl перейде до наступної порожньої альтернативи, і це спрацює, оскільки "20" - це якраз дві цифри.

Процес порівняння з однією з альтернатив, і, якщо вона не збіглася, переходу до іншої, називається зворотнім висліджуванням. Цей термін походить від уявлення, що порівнювання з шаблоном - це ніби блукання лісом. Маючи різноманітні стежки і розгалуження, і досліджуючи їх по-черзі, вам доводиться іноді повертатися назад до того самого місця, якщо шлях виявився тупиковим. Щоб бути конкретнішим, ось покроковий аналіз того, що робить Perl, коли намагається зіставити з шаблоном:


"abcde" =~ /(abd|abc)(df|d|de)/;
0 Розпочинаємо з першої літери ланцюжка - 'a'.

1 Пробуємо першу альтернативу з першої групи 'abd'.

2 Зіставляємо 'a', за якою слідує 'b'. Досі, все гаразд.

3 \&'d' регулярного виразу не збігається з 'c' ланцюжка - тупик. Повертаємося назад і звертаємося до другої альтернативи з першої групи - 'abc'.

4 Зіставляємо з 'a', з якою слідує 'b', а потім 'c'. Успішно, ми задовольнили першу групу. Встановлюємо $1 до 'abc'.

5 Переходимо до наступної групи і звертаємося до першої альтернативи - 'df'.

6 Збіг із 'd'.

7 \&'f' регулярного виразу не збігається з ланцюжком, тож це тупик. Повертаємося назад на один символ і звертаємося до другої альтернативи з другої групи - 'd'.

8 \&'d' збігається. Другу групу задовільнено також, тож встановимо $2 до 'd'.


9 Ми дісталися кінця регулярного виразу, а отже на цім усе закінчиться. Ми добилися збігу з 'abcd' з ланцюжка "abcde".

Можна зробити ще декілька зауважень щодо цього аналізу. Перше, - третя альтернатива з другої групи, 'de', також збігається, але ми зупинилися у певному положенні до того, як дістатися до неї. Альтернативний вираз ліворуч переміг. Друге, це те, що нам вдалося добитися збігу з першою позицією ланцюжка, з 'a'. Якби цього не відбулося, perl всерівно перейшов би до другої позиції, 'b', у спробі нового порівняння. Тільки після того, як всі можливі шляхи і всі можливі позиції вичерпано, perl здасться і оголосить "$string =~ /(abd|abc)(df|d|de)/;" як хибний.

Навіть при такій кількості роботи, порівняння з шаблонами відбувається надзвичайно швидко. Щоб досягти цієї ефективності, perl під час виконання, компілює регулярний вираз у компактну послідовність операційних кодів, що можуть розміститися всередині процесорного кешу. Тож, під час роботи програми, ці операційні коди можуть виконуватись на повній швидкості, здійснюючи миттєвий пошук.

Здобуття частин, що збіглися

Метасимволи групування "()" одночасно служать іншій, повністю відмінній функції - вони дозволяють екстракцію частин ланцюжка, що збіглися. Це дуже зручно, щоб дізнатися, що співпало, а також для обробки тексту загалом. Для кожної групи, частина, що збіглася з текстом зберігається в спеціальних змінних $1, $2, і.т.д. Їх можна використати як звичайні змінні:


# видобути годину, хвилину, секунду
if ($time =~ /(\d\d):(\d\d):(\d\d)/) { # збіг з форматом
$hours = $1; # гг:хх:сс
$minutes = $2;
$seconds = $3;
}
Тепер ми знаємо, що у скалярному контексті, "$time =~ /(\d\d):(\d\d):(\d\d)/" поверне істинне або хибне значення. Зате у контексті списку повертається список значень, що збіглися "($1,$2,$3)". Таким чином, ми могли би написати попередній код компактніше, як


# видобути годину, хвилину, секунду
($hours, $minutes, $seconds) = ($time =~ /(\d\d):(\d\d):(\d\d)/);
Якщо групи регулярного виразу гніздовано, $1 отримає групу з крайньою лівою відкриваючою дужкою, $2 - з наступною відкриваючою дужкою, і.т.д. Ось наприклад, складніший регулярний вираз із відповідними змінними, вказаними нижче:


/(ab(cd|ef)((gi)|j))/;
1 2 34
тож, що станеться, якщо регулярний вираз зійдеться, тобто $2 міститиме 'cd' або 'ef'? Для зручності, perl встановить $+ до ланцюжка зі змінної $1, $2, ... із найбільшим номером, і якій було присвоєно якесь значення (і, подібно до цього, $^N - до значення $1, $2, ... , якій останньою було надано якесь значення; тобто, $1, $2, ... , пов'язаної з правою закриваючою дужкою збігу).

Тісно пов'язаними зі змінними збігу $1, $2, ... є зворотні посилання "\1", "\2", ... . Зворотні посилання, це просто змінні збігу, які можна використати всередині регулярного виразу. Це справді гарна риса - те, що зійдеться пізніше з регулярним виразом може залежати від того, що зійшлося раніше. Скажімо, ми хочемо знайти повторні слова в тексті, як скажімо 'the the'. Наступний регулярний вираз знаходить дубльовані слова з трьох літер із пробілом між ними:


/(\w\w\w)\s\1/;
Групування присвоїть значення \1, тож ту саму послідовність із трьох літер використано в обох частинах збігу. Ось приклад знаходження слів, частини яких повторюються:


% simple_grep '^(\w\w\w\w|\w\w\w|\w\w|\w)\1$' /usr/dict/words
beriberi
booboo
coco
mama
murmur
papa
Вищенаведений регулярний вираз включає одну групу, яка може складатися з чотирьох-літерних слів, трьох-літерних слів тощо, і застосовує "\1" для знаходження повтору. Незважаючи на те, що $1 і "\1" означають ту саму річ, потрібно бути уважним, щоб вживати $1, $2, ... тільки поза межами регулярного виразу, а зворотні посилання "\1", "\2", ... тільки всередині регулярного виразу; якщо не так, то це може призвести до несподіваних і/або невизначених результатів.

На додаток до цього, Perl 5.6.0 передбачає масиви "@-" та "@+", що містять позиції збігів. "$-[0]" утримує положення початку цілого збігу, тоді як "$+[0]" - положення кінця збігу. Відповідно, "$-[n]" - положення початку $n-ного збігу, а "$+[n]" - положення кінця. Якщо $n має невизначене значення, тоді "$-[n]" із "$+[n]" так само. Таким чином, наступний код


$x = "Mmm...donut, thought Homer";
$x =~ /^(Mmm|Yech)\.\.\.(donut|peas)/; # matches
foreach $expr (1..$#-) {
print "Match $expr: '${$expr}' at position ($-[$expr],$+[$expr])\n";
}
виведе


Match 1: 'Mmm' at position (0,3)
Match 2: 'donut' at position (6,11)
Це дозволяє дізнатися, що саме збіглося з ланцюжком, навіть якщо не було угруповувань у регулярному виразі. perl встановить $` до частини ланцюжка до збігу, $& - до частини, що збіглася, і $' - до частини ланцюжка після збігу. Ось приклад:


$x = "the cat caught the mouse";
$x =~ /cat/; # $` = 'the ', $& = 'cat', $' = ' caught the mouse'
$x =~ /the/; # $` = '', $& = 'the', $' = ' cat caught the mouse'
В другому порівнянні, ми отримали "$` = ''" оскільки регулярний вираз збігся, починаючи з першого символу ланцюжка, не дійшовши до другого 'the'. Важливо зауважити, що $` і $' дещо уповільнюють роботу регулярного виразу, але $& не настільки. оскільки якщо ці змінні використано в одній частині програми, то вони генеруються для всіх регулярних виразів програми. Тож, якщо важлива швидкість, цих змінних слід уникати. Але якщо вони потрібні, скористайтеся натомість з "@-" і "@+":


$` тотожно substr( $x, 0, $-[0] )
$& тотожно substr( $x, $-[0], $+[0]-$-[0] )
$' тотожно substr( $x, $+[0] )

Збіг з повтореннями

Приклади вище мають одну неприємну слабість. Ми порівнювали тільки зі словами з 3-ох літер, або 4-ох, або навіть менше. Але нам, можливо, хотілося би порівняти зі словами або частинами слова довільної довжини, без необхідності складати марудні "\w\w\w\w|\w\w\w|\w\w|\w".

Саме для цього було створено метасимволи-квантори, такі як "?", "*", "+" і "{}". Вони дозволяють нам вказати число повторень частини регулярного виразу, що збіглася. Квантори стоять одразу за символом, класом символів, або вказаної нами групою. Вони мають наступні значення: .IP o "a?" зійдеться з 1 або 0 повтореннями 'а' .IP o "a*" зійдеться з 0 або більше повтореннями 'а' .IP o "a+" зійдеться з 1 або більше повтореннями 'а' .IP o "a{n,m}" зійдеться з принаймні n, але не більше m повтореннями 'a' .IP o "a{n,}" зійдеться з принаймні n або більше повтореннями 'a' .IP o "a{n}" зійдеться з точно n повтореннями 'a'

Ось декілька прикладів:


/[a-z]+\s+\d*/; # збігається зі словом в нижньому регістрі, принаймні
# декілька пробілів, і довільною кількістю чисел
/(\w+)\s+\1/; # збігається з повтореними словами довільної довжини
/y(es)?/i; # збігається з 'y', 'Y', або регістронезалежним 'yes'
$year =~ /\d{2,4}/; # впевнимося, що $year містить принаймні 2, але
# не більше за 4 числа
$year =~ /\d{4}|\d{2}/; # покращений варіант попереднього;
# відкидає трьохзначні числа
$year =~ /\d{2}(\d{2})?/; # те саме, але повертає $1

% simple_grep '^(\w+)\1$' /usr/dict/words # набагато простіше
beriberi
booboo
coco
mama
murmur
papa
При використанні цих кванторів, perl намагатиметься здійснити збіг з найбільшою частиною ланцюжка, одночасно дозволяючи, щоб регулярний вираз мав успіх. Таким чином, маючи "/a?.../", perl спробує знайти ланцюжок з "а", а якщо не вдалося, тоді без літери "a". З квантором "*" ми отримаємо наступне:


$x = "the cat in the hat";
$x =~ /^(.*)(cat)(.*)$/; # збігається з
# $1 = 'the '
# $2 = 'cat'
# $3 = ' in the hat'
Що відповідає тому, що ми й очікували. Порівнювання зі зразком знаходить єдиний "cat" у ланцюжкові, й чіпляється за нього. Зате зверніть увагу на наступний приклад:


$x =~ /^(.*)(at)(.*)$/; # збігається з
# $1 = 'the cat in the h'
# $2 = 'at'
# $3 = '' (0 співпадань)
Хтось міг би подумати, що спочатку відбудеться збіг з "at" з "cat", і порівнювання зупиниться на цьому, але це не дасть найдовший можливий ланцюжок для першого квантору ".*". Натомість, перший квантор ".*" захватить якнайбільше ланцюжка, все ще дозволяючи збіг регулярного виразу. В цьому конкретному прикладі, це означає послідовність "at", як остання "at" в ланцюжку. Інший важливий принцип, проілюстрований тут, полягає в тому, що коли присутні два або більше елементів у регулярному виразі, крайній лівий квантор, якщо такий існує, захватить якнайбільше ланцюжка, залишаючи решті регулярного виразу змагатися за рештки ланцюжка. Таким чином, в нашому прикладі, ".*" захватить більшість ланцюжка, тоді як другий квантор ".*" отримає порожній ланцюжок. Квантори, що захвачують якнайбільше ланцюжка називаються кванторами максимального збігу або жадібними кванторами.

Коли регулярний вираз може збігтися з ланцюжком у декілька різноманітних способів, ми можемо скористатися з нищенаведених правил, щоб передбачити, як саме відбудеться збіг: .IP o Правило 0: В цілому, регулярний вираз намагатиметься зійтися з найпершою можливою позицією в ланцюжку. .IP o Правило 1: В альтернативних виразах на зразок "a|b|c...", буде використано крайню ліву альтернативу, яка дозволяє збігові цілого регулярного виразу. .IP o Правило 2: Квантори максимального збігу "?", "*", "+" та "{n,m}", загалом, зійдуться з найбільшою можливою частиною ланцюжка, дозволяючи одночасно збіг решти регулярного виразу. .IP o Правило 3: Якщо присутні два або більше елементи регулярного виразу, крайній лівий квантор максимального збігу зійдеться з найбільшою можливою частиною ланцюжка, дозволяючи одночасно збіг решти регулярного виразу. Наступний лівий квантор максимального збігу, так само, зійдеться з найбільшою можливою частиною ланцюжка, дозволяючи одночасно збіг решти регулярного виразу. І так далі, доти, доки всі елементи регулярного виразу задовільнено.

Як ми вже впевнилися, Правило 0 переважить решту - регулярний вираз зійдеться з найпершою можливою позицією в ланцюжку, решта правил визначаючи, як саме зійдеться.

Ось приклад цих правил у дії:


$x = "The programming republic of Perl";
$x =~ /^(.+)(e|r)(.*)$/; # збігається з
# $1 = 'The programming republic of Pe'
# $2 = 'r'
# $3 = 'l'
Цей регулярний вираз починає свій збіг у найпершій можливій позиції 'T'. Можна би було припустити, що "e", знаходячись ліворуч в альтернативі, повинна спричинити збіг, але "r" видасть найдовший ланцюжок для першого квантору.


$x =~ /(m{1,2})(.*)$/; # збігається з
# $1 = 'mm'
# $2 = 'ing republic of Perl'
Тут, найперший можливий збіг, це з 'm' з "programming". "m{1,2}" являється першим квантором, тож він отримає максимальний збіг із "mm".


$x =~ /.*(m{1,2})(.*)$/; # збігається з
# $1 = 'm'
# $2 = 'ing republic of Perl'
У цьому прикладі, регулярний вираз збігається з самого початку ланцюжка. Перший квантор ".*" захвачує якнайбільше, залишаючи тільки одну 'm' для другого квантору "m{1,2}".


$x =~ /(.?)(m{1,2})(.*)$/; # збігається з
# $1 = 'a'
# $2 = 'mm'
# $3 = 'ing republic of Perl'
Тут, ".?" забере свій максимальний один символ ('a' з "programming") у найпершому можливому положенні в ланцюжку (суміжному з наступною літерою "m", на яку вказує "m{1,2}"), залишаючи "m{1,2}" можливість збігтися з обома "mm". І, нарешті,


"aXXXb" =~ /(X*)/; # збігається з $1 = ''
оскільки цей вираз може збігтися з нульовою кількістю 'X' у ланцюжкові. Якщо вам конче потрібен з збіг з, принаймні, одним 'X', скористайтеся з "X+", а не "X*".

Іноді жадібність не є бажаною. Час од часу, ви хотіли би, щоб квантори збігалися з мінімальною частиною ланцюжка, замість максимальної. Для цієї цілі, Лері Уол створив мінімальний збіг, або нежадібні квантори "??", "*?", "*?" та "{}?". Це ті самі квантори, із доданим "?". Вони мають наступне значення:


* "a??" збігається з 'a' 0 або 1 раз; спробує спочатку 0, а потім 1.


* "a*?" збігається з 'a' 0 або більше разів, тобто будь-яку кількість разів, але по-можливості найменшу.


* "a+?" збігається з 'a' 1 або більше разів, тобто, щонайменше 1, але по-можливості найменшу кількість разів.


* "a{n,m}?" збігається з 'a', принаймні "n" разів, але не більше за "m" разів, але по-можливості найменшу кількість разів.


* "a{n,}?" збігається з 'a', принаймні "n" разів, але по-можливості найменшу кількість разів.


* "a{n}?" збігається з 'a' точно "n" разів; оскільки це точний збіг з "n"-нною кількістю, "a{n}?" еквівалентний "a{n}", що заперечує послідовності нотації.

Давайте повернемося до прикладів вище, але з використання мінімальних кванторів:


$x = "The programming republic of Perl";
$x =~ /^(.+?)(e|r)(.*)$/; # збігається з
# $1 = 'Th'
# $2 = 'e'
# $3 = ' programming republic of Perl'
Мінімальним ланцюжком, що дозволить збігові обох, початку ланцюжка "^" і альтернативі, є "Th", з подальшою альтернативою, що зійдеться з "e". Другий квантор ".*" тепер може захватити решту виразу.


$x =~ /(m{1,2}?)(.*?)$/; # збігається з
# $1 = 'm'
# $2 = 'ming republic of Perl'
Першим положенням, в якому можливий збіг цього регулярного виразу, являється перша 'm' у слові "programming". У цьому місці, мінімальний "m{1,2}?" збігається тільки з одним 'm'. Незважаючи на те, що другий квантор ".*?" віддав би перевагу не зійтися з жодним символом, він обмежений дією вказівника кінця ланцюжка "$", тож збігатиметься з рештою ланцюжка.


$x =~ /(.*?)(m{1,2}?)(.*)$/; # збігається з
# $1 = 'The progra'
# $2 = 'm'
# $3 = 'ming republic of Perl'
У цьому регулярному виразі, ви можете очікувати, що мінімальний квантор ".*" зійдеться з порожнім ланцюжком, оскільки він не обмежений опорним "^". Але тут спрацьовує Правило 0. Оскільки регулярний вираз може збігтися, починаючи з найпершої позиції ланцюжка, він зійдеться з самого початку. Таким чином, перший квантор вимушений зійтися з усім аж до першої літери "m". Другий мінімальний квантор збігається тільки з однією "m", а третій квантор отримає решту ланцюжка.


$x =~ /(.??)(m{1,2})(.*)$/; # збігається з
# $1 = 'a'
# $2 = 'mm'
# $3 = 'ing republic of Perl'
Так само, як і в попередньому регулярному виразі, перший квантор ".??" може зійтися з найпершим (суміжним з "m") символом 'a', тож він саме з ним і зійдеться. Другий квантор є жадібним, тож зійдеться з обома "mm", тоді як третій - з рештою ланцюжка.

Ми можемо змінити Правило 3 вище, щоб воно враховувало нежадібні квантори:


* Правило 3: Якщо присутні два або більше елементи регулярного виразу, крайній лівий квантор максимального (мінімального) збігу зійдеться з найбільшою (найменшою) можливою частиною ланцюжка, дозволяючи одночасно збіг решти регулярного виразу. Наступний лівий квантор максимального (мінімального) збігу, так само, зійдеться з найбільшою (найменшою) можливою частиною ланцюжка, дозволяючи одночасно збіг решти регулярного виразу. І так далі, доти, доки всі елементи регулярного виразу задовільнено.

Так само як символьні альтернативи, квантори підлягають зворотньому висліджуванню. Ось покроковий аналіз наступного прикладу


$x = "the cat in the hat";
$x =~ /^(.*)(at)(.*)$/; # збігається з
# $1 = 'the cat in the h'
# $2 = 'at'
# $3 = '' (0 співпадань)

* Розпочинає порівнювання з першої літери 't'.

1 Перший квантор '.*' початково збігається з цілим ланцюжком 'the cat in the hat'.

2 \&'a', як складова регулярного виразу 'at' не збігається з кінцем ланцюжка, повернутися на один символ назад.

3 Оскільки 'a', як складова регулярного виразу 'at' не збігається з останньою літерою 't', повернутися назад ще на одне положення.

4 Тепер 'a' та 't' зійдуться з ланцюжком.

5 Перейти до третього елементу '.*' регулярного виразу. Оскільки ми вже знаходимося наприкінці ланцюжка, '.*' може зійтися 0 раз; присвоїть порожній ланцюжок $3.

6 Все, ми закінчили!

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


/(a|b+)*/;
Проблема полягає в гніздованих невизначених кванторах. Існує багато способів поділу ланцюжка довжиною n між "+" та "*": однією з послідовностей може бути "b+" довжиною n, або два повторення з першим "b+" довжиною k, а другого довжиною n-k, або m повторень, біти чиїх складають довжину n тощо. Насправді, існує експоненційне число способів поділу ланцюжка, як функції довжини. Регулярному виразу може пощастити, і він знайде збіг у ранній стадії порівнювання, але якщо збігу немає, perl спробує всі можливі варіанти до того як поступитися. Тож, будьте обережними з гніздованими "*", "{n,m}" та "+". Книга "Оволодіння регулярними виразами", автор Jeffrey Friedl, включає чудове обговорення щодо цього та інших питань ефективності регулярних виразів.

Побудова регулярного виразу

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

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

Наступним кроком є розбиття завдання на частини на менші задачі, що легше перетворити на регулярний вираз.

Найпростіше, це описати зразок цілих. Цілі складаються з послідовності з чисел, з необов'язковим знаком попереду. Цілі числа можна представити як "\d+", а знак як "[+-]". Таким чином, регулярний вираз для цілого складатиметься з


/[+-]?\d+/; # збігається з цілими
Число ж плаваючою точкою може мати знак, цілу частину, десяткову точку, дробову частину та показник степеня (експоненту). Одна чи більше цих частин являються необов'язковими, тож нам доведеться перевірити різноманітні можливості. Чинними числами з плаваючою точкою можуть бути 123., 0.345, .34, -1e6, 25.4E-72 тощо. Подібно до прикладу з цілими, необов'язковий знак можна зазначити як "[+-]?". Ми бачимо, що якщо немає показника степеня, числа з плаваючою точкою повинні включати десяткову крапку, в протилежному випадку вони будуть цілими. Ми можемо спокуситися на позначення даної частини як "\d*\.\d*", але це зійдеться також з однією крапкою, що не являється числом. Таким чином, три випадки чисел з плаваючою точкою без експоненти можна записати як


/[+-]?\d+\./; # 1., 321. тощо
/[+-]?\.\d+/; # .1, .234 тощо
/[+-]?\d+\.\d+/; # 1.0, 30.56 тощо
Їх можна об'єднати в єдиний регулярний вираз з трьома альтернативами:


/[+-]?(\d+\.\d+|\d+\.|\.\d+)/; # число з плаваючою точкою без
# експоненти
У цьому альтернативному виразі, важливо розмістити '\d+\.\d+' попереду '\d+\.'. Якби '\d+\.' стояв першим, регулярний вираз радо би збігався з ним, й ігнорував дробову частину числа.

Тепер, розглянемо числа з плаваючою точкою з експонентою. Основним зауваженням є тут те, що обидва, цілі числа та числа з плаваючою точкою можуть знаходитися попереду показника степеня. Експонента, так само як знак числа, можна "від'єднати від мантиси", тобто не є обов'язковою. Тепер стає ясною загальна форма регулярного виразу:


/^(необов'язк. знак)(ціле|число з пл. тчк.)(необов'язк. експонента)$/;
Експонента може складатися з літери "e" або "E", за якою слідує ціле. Тож регулярним виразом для експоненти буде


/[eE][+-]?\d+/; # експонента
Об'єднавши всі частини, ми отримаємо регулярний вираз, що збігається з будь-якими десятковими числами:


/^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/; # ура!
За допомогою довгих регулярних виразів на кшталт цього можна справляти враження своїх друзів, але важко розшифрувати пізніше. Для таких складних виразів, модифікатор "//x" виявиться неоціненним. Він дозволяє додавати майже довільні пропускові знаки та коментарі до регулярного виразу, без впливу на зміст. Використавши модифікатор, можна розписати наш концентрований вираз у приємнішу форму


/^
[+-]? # спершу, знайти необов'язковий знак
( # знайти ціле або числ. з пл. точкою:
\d+\.\d+ # мантиса, що має вигляд a.b
|\d+\. # мантиса, що має вигляд a.
|\.\d+ # мантиса, що має вигляд .b
|\d+ # ціле, яке виглядає як a
)
([eE][+-]?\d+)? # нарешті, необов'язкова експонента
$/x;
Якщо пропускові знаки, в більшості випадків, не беруться до уваги, як включити символ пропуску до розгорнутого регулярного виразу? Відповідь - екранувати його зворотнім слешом, або представити як клас символів "[ ]". Те саме стосується гратки, використайте "\#" або "[#]". Наприклад, Perl дозволяє пробіл між знаком і мантисою або цілим, тож ми можемо додати це до нашого регулярного виразу


/^
[+-]?\ * # знайти необов'язковий знак *з пробілом*
( # знайти ціле або числ. з пл. точкою:
\d+\.\d+ # мантиса, що має вигляд a.b
|\d+\. # мантиса, що має вигляд a.
|\.\d+ # мантиса, що має вигляд .b
|\d+ # ціле, яке виглядає як a
)
([eE][+-]?\d+)? # нарешті, необов'язкова експонента
$/x;
В такій формі представлення, легше побачити, що можна спростити альтернативні вирази. Альтернативи 1, 2 та 4, всі починаються з "\d+", тож його можна відокремити:


/^
[+-]?\ * # знайти необов'язковий знак з пробілом
( # знайти ціле або числ. з пл. точкою:
\d+ # *спільне число*
(
\.\d* # мантиса, що має вигляд a.b
)? # ? подбає про цілі, що виглядають як a
|\.\d+ # мантиса, що має вигляд .b
)
([eE][+-]?\d+)? # нарешті, необов'язкова експонента
$/x;
або, якщо в компактній формі:


/^[+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?$/;
Щоб підвести підсумок, ми побудували регулярний вираз шляхом


* детального вияснення задачі,
* розбиття її на менші частини,
* переклад менших частин у регулярні вирази,
* поєднання регулярних виразів,
* оптимізації отриманого складного регулярного виразу.

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

Використання регулярних виразів у Perl

Остання тема Частини 1 стисло розглянула, як регулярні вирази використовуються в Perl-програмах. Але де саме їхнє місце в синтаксисі Perl?

Ми вже ознайомились із порівнювальним оператором в своїй стандартній "/регулярний вираз/" і з довільним обмежувачем "m!регулярний вираз" формі. Ми застосували оператор-зв'язку "=~" і заперечувальну його форму "!~" для перевірки збігів з ланцюжком. Пов'язані з порівнювальним оператором, нами було розглянуто однорядковий "//s", багаторядковий "//m", регістронезалежний "//i" та розширений "//x" модифікатори.

Існує ще декілька речей, які слід знати щодо порівнювальних операторів. Перш за все, ми вже вказали раніше, що змінні в регулярних виразах замінюються їхніми значеннями до того як розглянуто регулярний вираз:


$pattern = 'Seuss';
while (<>) {
print if /$pattern/;
}
Це виведе ті рядки, що містять в собі слово "Seuss". Проте, це не настільки ефективно, наскільки могло би бути, оскільки perl повинен оцінити $pattern кожний раз при проходженні через цикл. Якщо $pattern не змінюватиметься протягом виконання скрипта, можна додати модифікатор "//o", який наказує perl здійснити тільки одне розкриття змінної:


#!/usr/bin/perl
# Вдосконалений simple_grep
$regexp = shift;
while (<>) {
print if /$regexp/o; # набагато швидше
}
У такому разі, якщо ви поміняєте $pattern після того як відбулося перше розкриття цієї змінної, perl проігнорує це. Якщо ви не бажаєте ніякого розкриття взагалі, скористайтеся з спеціального обмежувача "m''":


@pattern = ('Seuss');
while (<>) {
print if m'@pattern'; # збіжиться з буквальним '@pattern',
} # а не 'Seuss'
"m''" діє так само як одинарні лапки скрізь; будь-які інші обмежувачі, що можна використати з "m" діють, ніби регулярний вираз всередині включено в подвійні лапки. Якщо регулярний вираз оцінено як порожній ланцюжок, буде використано регулярний вираз останнього вдалого збігу. Таким чином, ми отримаємо


"dog" =~ /d/; # 'd' збігається
"dogbert =~ //; # також збігається з 'd' завдяки попередньому /d/
Останні два модифікатори "//g" та "//c" стосуються численних збігів. Модифікатор "//q" позначає глобальний збіг, і дозволяє збіг із шаблоном стільки раз скільки завгодно всередині рядка. В скалярному контексті, послідовний виклик супроти ланцюжка заставить "//g" переходити від збігу до іншого, слідкуючи за його положенням під час переміщення. Ви можете отримати чи встановити позицію за допомогою функції "pos()".

Використання "//g" продемонстровано в наступному прикладі. Припустімо ми маємо ланцюжок, що складається зі слів, розділених пробілами. Якщо ми знаємо скільки в ланцюжкові слів, то можемо добути ці слова за допомогою угруповування:


$x = "cat dog house"; # 3 слова
$x =~ /^\s*(\w+)\s+(\w+)\s+(\w+)\s*$/; # збігається з
# $1 = 'cat'
# $2 = 'dog'
# $3 = 'house'
Але як поступити, якщо ми маємо невизначену кількість слів? Саме для цього було створено "//g". Щоб здобути всі слова, створіть простий регулярний вираз "(\w)", і циклічно пройдіться через всі збіги за допомогою "/(\w+)/g":


while ($x =~ /(\w+)/g) {
print "Word is $1, ends at position ", pos $x, "\n";
}
що виведе


Word is cat, ends at position 3
Word is dog, ends at position 7
Word is house, ends at position 13
Невдалий збіг, або зміна ланцюжка призначення перезаладує позицію. Якщо ви не бажаєте відновлення позиції після невдалого збігу, додайте "//c", як от "/регулярний вираз/gc". Поточна позиція в ланцюжку пов'язана з самим ланцюжком, а не регулярним виразом. Це означає, що різні ланцюжки видадуть різні позиції, і їхні відповідні позиції можна встановити або бути прочитаними незалежно.

В контексті переліку, "//g" повертає список груп, що збіглися, або якщо не використовувалося групування - список збігів із цілим регулярним виразом. Тож, якщо потрібні би були самі слова, то ми могли би звернутися до


@words = ($x =~ /(\w+)/g); # збігається з
# $word[0] = 'cat'
# $word[1] = 'dog'
# $word[2] = 'house'
Тісно пов'язаним з модифікатором "//g" є опорний символ "\G". Опорне "\G" збігається з тим місцем, де закінчився попередній збіг "//g". "\G" дозволяє нам легко втілити контекстно-залежний пошук:


$metric = 1; # використання метричної системи
...
$x = <FILE>; # прочитає виміри
$x =~ /^([+-]?\d+)\s*/g; # отримає розміри
$weight = $1;
if ($metric) { # перевірка на помилки
print "Units error!" unless $x =~ /\Gkg\./g;
}
else {
print "Units error!" unless $x =~ /\Glbs\./g;
}
$x =~ /\G\s+(widget|sprocket)/g; # продовження обробки
Поєднання "//g" з "\G" дозволяє нам обробку ланцюжка невеликими частинами, використовуючи довільну логіку Perl для вирішення того, як діяти далі. На сьогодні, опорне "\G" повністю підтримується тільки тоді, коли позначає початок зразка.

"\G" також незамінний при обробці регулярним виразом записів фіксованої довжини. Припустімо, ми має послідовність кодованої ділянки ДНК, закодованої за допомогою літер, як от "ATCGTTGAAT...", і ми хочемо знайти завершальні ланки "TGA". В кодованій ділянці, ланки складаються з 3-літерних послідовностей, тож ми можемо уявити ланцюжок ДНК як ряд 3-літерних записів. Спрощений регулярний вираз


# якщо розширити, то це повинно бути
# "ATC GTT GAA TGC AAA TGA CAT GAC"
$dna = "ATCGTTGAATGCAAATGACATGAC";
$dna =~ /TGA/;
не працює так як слід; він може збігтися з "TGA", але немає гарантії, що збіг відповідатиме ланці, а не збіжиться десь посередині двох, як скажімо "GTT GAA" (тобто "...GTTGAA..."). Кращим рішенням буде


while ($dna =~ /(\w\w\w)*?TGA/g) { # завважте мінімальний *?
print "Got a TGA stop codon at position ", pos $dna, "\n";
}
що виведе


Got a TGA stop codon at position 18
Got a TGA stop codon at position 23
де положення 18 є вірним, а 23 не знати звідки взялося. Що ж сталося?

Відповідь полягає в тому, що наш регулярний вираз добре працює доти, доки не пройде останній дійсний збіг. Після цього, регулярному виразові не вдасться збігтися з синхронізованим "TGA" і він почне порівнювати по одному символові за раз, що не є тим, чого ми добивалися. Вирішенням буде застосувати опорне "\G", щоб збіги відбувалися відповідно до положень ланок:


while ($dna =~ /\G(\w\w\w)*?TGA/g) {
print "Got a TGA stop codon at position ", pos $dna, "\n";
}
Це виведе


Got a TGA stop codon at position 18
що являється правильною відповіддю. Цей приклад демонструє, що важливо не тільки порівняти з бажаним, але й відкинути небажане.

Пошук із заміною

У Perl, регулярні вирази також відіграють важливу роль в операціях по пошуку та заміні. Пошук-заміна здійснюється за допомогою оператору "s///". Змінні збігу $1, $2, ... одразу доступні для використання у виразах заміни. Глобальний модифікатор "s///g" дозволяє пошук із заміною всіх збігів з регулярним виразом всередині ланцюжка:


$x = "I batted 4 for 4";
$x =~ s/4/four/; # не замінює всього:
# $x міститиме "I batted four for 4"
$x = "I batted 4 for 4";
$x =~ s/4/four/g; # міняє скрізь:
# $x містить "I batted four for four"
Якщо би вам хотілося поміняти 'рагулярний' на 'регулярний' у цьому тексті, то могли би скористатися з наступної простенької програми:


% cat > simple_replace
#!/usr/bin/perl
$regexp = shift;
$replacement = shift;
while (<>) {
s/$regexp/$replacement/go;
print;
}
^D

% simple_replace рагулярний регулярний perlretut.pod
У simple_replace ми використали модифікатор "s///g" для заміни всіх випадків $regexp на кожному рядкові, а також модифікатор "s///o" для одноразової компіляції регулярного виразу. Подібно до програми simple_grep, обидва твердження "print" і "s/$regexp/$replacement/go" використовують стандартну $_.

Модифікатор "s///e" призначений спеціально для операцій пошуку-заміни. "s///e" додає "eval{...}" навколо ланцюжка заміни, тож обчислений результат візьме місце частини ланцюжка, що збіглася. "s///e" корисний, якщо необхідно здійснити певне обчислення в процесі заміни тексту. Так, наступний приклад здійснює відлік частоти символів рядка:


$x = "Bill the cat";
$x =~ s/(.)/$chars{$1}++;$1/eg; # остання $1 замінює символ
# на самого себе
print "frequency of '$_' is $chars{$_}\n"
foreach (sort {$chars{$b} <=> $chars{$a}} keys %chars);
Це надрукує


frequency of ' ' is 2
frequency of 't' is 2
frequency of 'l' is 2
frequency of 'B' is 1
frequency of 'c' is 1
frequency of 'e' is 1
frequency of 'h' is 1
frequency of 'i' is 1
frequency of 'a' is 1
Подібно до порівнювального оператору "m//", з "s///" також можна вживати інші обмежувачі, такі як "s!!!" та "s{}{}", або навіть "s{}//" тощо. Якщо застосовано одинарні лапки "s'''", тоді регулярний вираз і ланцюжок заміни розглядаються буквально, беж жодних розкриттів. "s///" в контексті переліку повертає те саме значення, що й у скалярному, тобто кількість збігів.

Оператор split

Функція "split" дозволяє використання порівнювального оператору "m//" для поділу ланцюжка.


split /регулярний вираз/, ланцюжок, ліміт;
розділить "ланцюжок" на список підланцюжків, і поверне цей список. Регулярний вираз використовується для збігу з послідовністю символів, згідно з якою буде розділено "ланцюжок". Якщо додано аргумент "ліміт", він обмежить поділ до не більше ніж вказаної кількості ланцюжків. Так, наприклад, для поділу ланцюжка на слова, використайте


$x = "Calvin and Hobbes";
@words = split /\s+/, $x; # $word[0] = 'Calvin'
# $word[1] = 'and'
# $word[2] = 'Hobbes'
Якщо застосовано порожній регулярний вираз, він завжди збігатиметься, і ланцюжок розділено на окремі символи. Коли регулярний вираз містить угуповування, тоді отриманий список включатиме також пілдланцюжки, що збіглися з групою. Наприклад;


$x = "/usr/bin/perl";
@dirs = split m!/!, $x; # $dirs[0] = ''
# $dirs[1] = 'usr'
# $dirs[2] = 'bin'
# $dirs[3] = 'perl'

@parts = split m!(/)!, $x; # $parts[0] = ''
# $parts[1] = '/'
# $parts[2] = 'usr'
# $parts[3] = '/'
# $parts[4] = 'bin'
# $parts[5] = '/'
# $parts[6] = 'perl'
Оскільки перший символ $x збігся з регулярним виразом, "split" додала порожній перший елемент до списку.

Якщо ви дочитали до цього місця, поздоровляємо! Тепер ви володієте основними знаннями про використання регулярних для вирішення широкого спектру задач по обробці тексту. Якщо ви вперше читаєте цей посібник, чому би вам не зупинитися тут на який час, щоб побавитися з регулярними виразами. Частина 2 торкається таємніших аспектів регулярних виразів, які, звичайно, не потрібні одразу напочатку.

Частина 2: потужніші знаряддя

Гаразд, ви засвоїли основи регулярних виразів, але бажаєте знати більше. Якби порівняння з регулярними виразами можна би було порівняти з походом у гори, тоді знаряддя, розглянуті в Частині 1 були би подібними до карт та компасів - основних речей, які ми завжди вживаємо під час походу. Більшість з того, що описано в Розділі 2 більше схоже на пістолети-ракетниці та сателітарні телефони. Вони не вживаються занадто часто під час сходження на гору, але коли ви застрягли, можуть виявитися безцінними.

Наступне, це складніші, менш використовувані, іноді дивні риси регулярних виразів perl. У Частині 2, ми припускаємо, що ви впевнені в основах, і зосередимось на нових рисах.


Додатково про символи, ланцюжки та класи символів

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

Перш за все, це екрановані послідовності, які перетворюють символи або ланцюжки з верхнього до нижнього регістру та навпаки. "\l" (англійська "л") та "\u" переводять наступний символ до нижнього або верхнього регістру, відповідно:


$x = "perl";
$string =~ /\u$x/; # збіжиться з 'Perl' у $string
$x = "M(rs?|s)\\."; # зверніть увагу на подвійний слеш
$string =~ /\l$x/; # збіжиться з 'mr.', 'mrs.' та 'ms.'
"\L" із "\U" переведуть цілий підланцюжок, обмежований "\L" або "\U" та "\E" до нижнього або верхнього регістру:


$x = "This word is in lower case:\L SHOUT\E";
$x =~ /shout/; # збігається
$x = "I STILL KEYPUNCH CARDS FOR MY 360";
$x =~ /\Ukeypunch/; # збігається зі словом KEYPUNCH
Якщо "\E" відсутнє, перетворення регістру відбувається до кінця ланцюжка. Регулярні вирази на кшталт "\L\u$word" або "\u\L$word" переводять першу літеру $word у верхній регістр, а решту - в нижній.

Керівні символи можна екранувати за допомогою "\c", таким чином символ Control-Z збіжиться з "\cZ". Керівна послідовність "\Q"..."\E" екранує, тобто захищає, більшість символів, що не належать алфавітові. Наприклад,


$x = "\QThat !^*&%~& cat!";
$x =~ /\Q!^*&%~&\E/; # перевіряє на лайливі знаки
"\Q" не звільняє "$" та "@", тож розкриття змінних залишатиметься в силі.

Починаючи з версії 5.6.0, регулярні вирази perl спроможні обробляти не тільки стандартні символи набору ASCII. Тепер Perl підтримує Юнікод, стандартний для кодування набору символів для багатьох мов світу. Юнікод здійснює це, дозволяючи довжині символів перевищувати один байт. Як користується кодуванням UTF-8, в якому символи ASCII все ще кодовано як один байт, але знаки більші за значенням за "chr(127)" можуть бути збереженими як два або більше байтів.

Яки це має стосунок до регулярних виразів? Добре, користувачі не обов'язково повинні знати щось про внутрішнє представлення ланцюжків. Але що вони повинні знати, так це 1) як представити Юнікод-символи в регулярному виразі 2) коли порівнювальний оператор розглядатиме текст пошуку як послідовність байтів (старий спосіб) або як послідовність Юнікод-символів (новий спосіб). Відповіддю для 1) є те, що Юнікод-символи більші за "chr(127)" можна представити як "\x{hex}" запис, де "hex", це шістнадцяткове ціле:


/\x{263a}/; # збіг зі смайликом Юнікоду :)
Юнікод-символи в діапазоні 128-255 позначаються як шістнадцяткові числа, включені у фігурні дужки: "\x{ab}". Зауважте, що це відрізняється від "\xab", що являється просто шістнадцятковим байтом без жодного Юнікодного значення.

ПРИМІТКА: в Perl 5.6.0 потрібно було вказати "use utf8" для використання Юнікоду. Цього більше не потрібно, за винятком, коли самий Perl-скрипт знаходиться в кодуванні UTF-8, тоді необхідна вказівка "use utf8".

Збагнути шістнадцяткову послідовність Юнікод-символу чиєгось регулярного виразу майже так само весело, як програмувати в машинному коді. Іншим способом вказівки Юнікодового символу, це застосувати іменну екрановану послідовність "\N{name}", де "name", це Юнікод-назва символу, яку вказано в Юнікодовому стандарті. Так, наприклад, якщо би ми хотіли відтворити або знайти збіг з астрологічним знаком Меркурія, то могли би вказати


use charnames ":full"; # директива по використанню іменних символів
# з повними Юнікод-назвами
$x = "abc\N{MERCURY}def";
$x =~ /\N{MERCURY}/; # збігається
Можна також використати короткі назви, або обмежити імена до певного алфавіту:


use charnames ':full';
print "\N{GREEK SMALL LETTER SIGMA} is called sigma.\n";

use charnames ":short";
print "\N{greek:Sigma} is an upper-case sigma.\n";

use charnames qw(greek);
print "\N{sigma} is Greek sigma\n";
Список повних назв можна знайти в Names.txt у каталозі lib/perl5/5.X.X/unicore.

Відповідь на вимогу 2). Починаючи з Perl 5.6.0, якщо регулярний вираз включає Юнікод-символи, ланцюжок для пошуку також вважається як такий, що складається з символів Юнікоду. В протилежному випадку, ланцюжок вважається послідовністю байтів. Коли пошук у ланцюжку здійснюється як по послідовності Юнікод-символів, але вимагається збіг із одним байтом, ми можемо скористатися з екранованої послідовності "\C". "\C" являється класом символів, подібний до ".", за винятком того, що він збігається з будь-яким байтом у діапазоні 0-255. Тож,


use charnames ":full"; # використання повних назв іменних символів
# Юнікоду
$x = "a";
$x =~ /\C/; # збігається з 'a', з'їдає один байт
$x = "";
$x =~ /\C/; # не збігається, немає байтів для збігу
$x = "\N{MERCURY}"; # двохбайтовий символ Юнікоду
$x =~ /\C/; # збігається, але це небезпечно!
Останній регулярний вираз збігається, але це небезпечно, оскільки положення символів більше не синхронізоване з положенням байтів ланцюжка. Це спричинить попередження \&'Malformed UTF-8 character'. "\C" найкраще використовувати для порівнянь із бінарними даними, в ланцюжках, де бінарні дані змішані з символами Юнікоду.

Давайте тепер розглянемо решту символьних класів. Схоже до іменованих символів Юнікоду, існують також класи символів Юнікоду, представлені як екранована послідовність "\p{назва}". Відповідно існують класи "\P{назва}", які являються запереченням "\p{назви}". Так, наприклад, для збігу зі знаками нижнього та вернхнього регістру, нам потрібно


use charnames ":full"; # використання повних назв іменних символів
# Юнікоду
$x = "BOB";
$x =~ /^\p{IsUpper}/; # збігається; клас символів верхнього
# регістру
$x =~ /^\P{IsUpper}/; # не збігається; клас усіх символів, за
# винятком верхнього регістру
$x =~ /^\p{IsLower}/; # не збігається; клас символів нижнього
# регістру
$x =~ /^\P{IsLower}/; # збігається; клас усіх символів, за
# винятком нижнього регістру
Нижче наведено відповідність між названими класами символів Perl і традиційними класами Юнікоду:


Назва класу Назва класу, або регулярний вираз
Perl Юнікод

IsAlpha /^[LM]/
IsAlnum /^[LMN]/
IsASCII $code <= 127
IsCntrl /^C/
IsBlank $code =~ /^(0020|0009)$/ || /^Z[^lp]/
IsDigit Nd
IsGraph /^([LMNPS]|Co)/
IsLower Ll
IsPrint /^([LMNPS]|Co|Zs)/
IsPunct /^P/
IsSpace /^Z/||($code =~ /^(0009|000A|000B|000C|000D)$/
IsSpacePerl /^Z/||($code =~ /^(0009|000A|000C|000D|0085|2028|2029)$/
IsUpper /^L[ut]/
IsWord /^[LMN]/ || $code eq "005F"
IsXDigit $code =~ /^00(3[0-9]|[46][1-6])$/
Можна також використати офіційні Юнікод-назви класів із "\p" та "\P", як от "\p{L}" для літер Юнікоду, або "\p{Lu}" для літер верхнього регістру, або "\P{Nd}" для не-цифрових символів. Якщо "назва" складається тільки з однієї літери, фігурні дужки не обов'язкові. Наприклад, "\pM" являється класом символів дла 'позначок' Юнікоду, наприклад знаку наголосу. Для повного списку подивіться perlunicode(1).

Юнікод також розділено на різноманітні набори знаків, які можна перевірити за допомогою "\p{набір}" (належить наборові) та "\P{набір}" (не належить наборові), наприклад "\p{Latin}", "\p{Greek}, або "\P{Katakana}". Для повного списку, дивіться perlunicode(1).

"\X" являється скороченням для послідовності класу символів, яка включає 'комбінавані символьні послідовності' Юнікоду. 'Комбінована симмвольна послідовність', це така, що включає основний символ, за яким слідує певна кількість комбінованих символів. Прикладом комбінованого символу являється знак наголосу. Якщо скористатися з повних назв Юнікоду, то "A + COMBINING RING" являється комбінованою послідовністю знаків, що складається з основного символу "A", і комбінованого кружечка "COMBINING RING", що відповідає Датському A з кружечком, як от у слові Angstrom. "\X" еквівалентне "\PM\pM*}", тобто, не-позначка, за якою слідує одна або більше позначок.

Для найостаннішої вичерпної інформації щодо Юнікоду, зверніться до майданчика Юнікодового Консорціуму http://www.unicode.org/.

Так, ніби всіх цих символьних класів не було досить, Perl також визначив класи символів у стилі POSIX. Вони мають форму "[:назва:]", де "назва" являється назвою класу POSIX. Цими класами є "alpha", "alnum", "ascii", "cntrl", "digit", "graph", "lower", "print", "punct", "space", "upper" і "xdigit", і два розширення - "word" (розширення Perl, що відповідає "\w") та "blank" (GNU-розширення). Якщо вживається "utf8", тоді ці класи означено так само, як і Юнікод-класи: "[:upper:]" тотожно "\p{IsUpper}" і так далі. Класи символів POSIX, однак, не вимагають використання "utf8". Класи "[:digit:]", "[:word:]" і "[:space:]" відповідають звичним класам символів "\d", "\w" і "\s". Для заперечення класу символів POSIX, додайте "^" попереду назви, таким чином "[:^digit:]" відповідає "\D", а під час дії "utf8" - "\P{IsDigit}". Класи символів Юнікоду та POSIX можна вживати подібно до "\d", за винятком того, що класи символів POSIX можна використовувати лише всередині класу символів:


/\s+[abc[:digit:]xyz]\s*/; # збіг із a,b,c,x,y,z, або цифрою
/^=item\s[[:digit:]]/; # збіг з '=item', за яким слідує
# пробіловий знак і цифра
use charnames ":full";
/\s+[abc\p{IsDigit}xyz]\s+/; # збіг a,b,c,x,y,z, або цифрою
/^=item\s\p{IsDigit}/; # збіг з '=item', за яким слідує
# пробіловий знак і цифра
Нарешті!... Це все про решту символів і символьні класи.

Компіляція та збереження регулярних виразів

У Частині 1 ми обговорювали модифікатор "//o", який компілює регулярний вираз. Це означає, що компільований регулярний вираз являється певною структурою даних, яку можна зберегти один раз для подальшого багаторазового використання. Оператор залапковування регулярних виразів "qr//" здійснює те саме: "qr/ланцюжок/" компілює "ланцюжок" як регулярний вираз, і перетворює результат у таку форму, що його можна присвоїти змінній:


$reg = qr/foo+bar?/; # reg міститиме компільований регулярний
# вираз
Після цього $reg можна використати як регулярний вираз:


$x = "fooooba";
$x =~ $reg; # збігається, так само як /foo+bar?/
$x =~ /$reg/; # те саме, просто альтернативна форма запису
$reg також можна об'єднати з більшим регулярним виразом:


$x =~ /(abc)?$reg/; # також збігається
Подібно до порівнювального оператору, із залапковуванням регулярних виразів можна використати різноманітні обмежувачі, тобто "qr!!", "qr{}" або "qr~~" тощо. Одинарні лапки "qr''", так само, запобігають інтерполяції (розкриттю).

Попередньо-компільовані регулярні вирази корисні для створення динамічних збігів, які не потрібно перекомпільовувати кожний раз під час їхнього використання. Завдяки попередньо-компільованим регулярним виразам, можна розширити нашу програму "simple_grep" таким чином, щоб вона знаходила збіги з багатьма шаблонами:


% cat > multi_grep
#!/usr/bin/perl
# multi_grep - збіг з будь-яким <числом> регулярних виразів
# використання:
# multi_grep <число> рег.вираз1 рег.вираз2 ... файл1 файл2...

$number = shift;
$regexp[$_] = shift foreach (0..$number-1);
@compiled = map qr/$_/, @regexp;
while ($line = <>) {
foreach $pattern (@compiled) {
if ($line =~ /$pattern/) {
print $line;
last; # знайдено збіг, тож переходимо до наступного
# рядка
}
}
}
^D

% multi_grep 2 last for multi_grep
$regexp[$_] = shift foreach (0..$number-1);
foreach $pattern (@compiled) {
last;
Збереження попередньо-компільованих регулярних виразів у масив @compiled дозвляє нам просто циклічно пройтися по регулярних виразах, без необхідності компіляції, таким чином виграючи в гнучкості, без втрати швидкості виконання програми.

Включення коментарів і модифікаторів у регулярні вирази

Починаючи з цього підрозділу, ми почнемо обговорення набору розширених шаблонів регулярних виразів Perl. Вони являються додатком до традиційного синтаксису регулярних виразів і забезпечують потужними новими знаряддями пошуку за шаблоном. Ми вже бачили деякі розширення, такі як оператори мінімального збігу "??", "*?", "+?", "{n,m}?" i "{n,}?". Решта розширень матиме форму "(?символ...)", де "символ" вказуватиме тип розширення.

Першим являється включені коментарі "(?#текст)". Останнє вносить коментарі всередину регулярних виразів, без впливу на їхнє значення. Коментарі не повинні включати закриваючої дужки всередині "тексту". Ось приклад:


/(?# Match an integer:)[+-]?\d+/;
Цей тип коментаря, правда, в більшості випадків витіснено необмеженими коментарями, які дозволяє модифікатор "//x".

Модифікатори "//i", "//m", "//s" й "//x" можна натомість включити всередину регулярного виразу, якщо вказати їх як "(?i)", "(?m)", "(?s)" або "(?x)". Наприклад:


/(?i)yes/; # регістронезалежний збіг із 'yes'
/yes/i; # те саме
/(?x)( # версія вільної форми для шаблону пошуку цілого
[+-]? # збіг з необов'язковим знаком
\d+ # збіг з послідовністю цифр
)
/x;
Включення модифікаторів може мати дві переваги в порівнянні зі звичайними модифікаторами. Включення дозволяє особливий набір модифікаторів для окремого регулярного виразу. Це здорово для порівнянь з масивом регулярних виразів, які повинні мати різні модифікатори:


$pattern[0] = '(?i)doctor';
$pattern[1] = 'Johnson';
...
while (<>) {
foreach $patt (@pattern) {
print if /$patt/;
}
}
Другою перевагою є те, що включені модифікатори діють тільки стосовно групи, в якій знаходиться регулярний вираз і самий модифікатор. Тож, угруповування може використовуватися для локалізації ефекту модифікатору:


/Answer: ((?i)yes)/; # збіг із 'Answer: yes', 'Answer: YES' тощо
Включені модифікатори також можуть вимкнути вже діючі, якщо вказати, наприклад, "(?-i)". Модифікатори можна об'єднувати в один вираз, тож "(?s-i)", скажімо, вмикає однорядковий режим і вимикає нечутливість до регістру.

Незахоплюючі групи

Як ми вказали в Частині 1, угруповування "()" має дві різні функції: 1) скупчення елементів регулярного виразу в єдине ціле, і 2) здобуття, або захват підланцюжків, що збіглися з регулярними виразами групи. Незахоплюючі групи, що позначаються як "(?:рег_вираз)", дозволяють, щоб регулярний вираз розглядався як єдине ціле, але не добувають підланцюжків для присвоєння змінним $1, $2 і т.д. Обидві, захоплюючі та незахоплюючі групи можуть співіснувати в одному регулярному виразові. Оскільки здобуття підланцюжків не відбувається, незахоплюючі групи працюють швидше за захоплюючі. Незахоплюючі групи також зручні для вирішення того, які частини регулярного виразу зберегти в змінних співпадання:


# збіг із числом, $1-$4 встановлюються, але нам потрібна тільки $1
/([+-]?\ *(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)/;

# швидший збіг із числом, встановлюється тільки $1
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)/;

# збіг із числом, $1 - ціла частина, $2 - показник степеня
/([+-]?\ *(?:\d+(?:\.\d*)?|\.\d+)(?:[eE]([+-]?\d+))?)/;
Незахоплюючі групи також зручні для видалення непотрібних елементів, отриманих після операції split:


$x = '12a34b5';
@num = split /(a|b)/, $x; # @num = ('12','a','34','b','5')
@num = split /(?:a|b)/, $x; # @num = ('12','34','5')
Можна також включити модивікатори всередину незахоплюючих груп: "(?i-m:рег_вираз)" являється незахоплюючою групою, що збіжиться з регулярним виразом у регістронезалежний спосіб, вимкнувши багаторядковий режим.

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

Цей розділ розгляне твердження заглядання вперед по тескту та назад. Спершу, трохи підгрунтя.

У регулярних виразах Perl, більшість елементів виразу "з'їдають" певний водрізок ланцюжка, коли вони збігаються. Наприклад, елемент регулярного виразу "[abc}]" проковтне один символ ланцюжка, якщо якийсь із символів співпаде, в тому розумінні, що perl переміститься до наступного положення символу в ланцюжкові після збігу. Проте, існують певні елементи, що не ковтають символи (переміщають позицію всередині ланцюжка) під час збігу. З-поміж прикладів, з якими ми знайомі, можна вказати на опорні символи. Так, опорний "^" збігається з початком рядка, але не з'їдає символів. Те саме можна сказати і про опорний межі слова "\b", який збігається, якщо символ ліворуч являється символ, що може належати слову, а символ праворуч - ні. Опорні символи являються прикладом виразів "нульової довжини". Нульової, оскільки вони не споживають жодних символів, а просто перевіряють певні властивості ланцюжка. Якщо пам'ятаєте нашу аналогію з блуканням у лісі, то більшість регулярних виразів можна порівняти з пересуванням по стежці, зате опорні символи - з зупинкою для перевірки нашого середовища. Якщо середовище нас влаштовує, ми йдемо далі, якщо ні - повертаємося назад.

Перевірка середовища включає в себе заглядання вперед по стежці, заглядання назад, або обидва. "^" дивиться назад, щоб упевнитися, що позаду немає жодних символів. "$" заглядає вперед, щоб переконатися, що попереду немає символів. "\b" перевіряє в обох напрямках, щоб дізнатися, чи символи з обох боків відрізняються в своїй належності до слова.

Перевірка попереду і позад являється узагальненням концепції опорних символів. Вона складається з виразів нульової довжини, що вказують, які символи ми шукаємо. Вираз перевірки попереду позначається як "(?=рег_вираз)", а позаду - "(?<=сталий_рег_вираз)". Ось декілька прикладів:


$x = "I catch the housecat 'Tom-cat' with catnip";
$x =~ /cat(?=\s+)/; # збігається з 'cat' із 'housecat'
@catwords = ($x =~ /(?<=\s)cat\w+/g); # збігається з
# $catwords[0] = 'catch'
# $catwords[1] = 'catnip'
$x =~ /\bcat\b/; # збігається з 'cat' із 'Tom-cat'
$x =~ /(?<=\s)cat(?=\s)/; # не збігається; немає окремого 'cat'
# посеред $x
Зверніть увагу, що дужки в "(?=рег_вираз)" і "(?<=рег_вираз)" є незахоплюючими, оскільки це вирази нульової довжини. Отже, в другому регулярному виразі, захваченими підланцюжками являються ті, що відповідають цілому регулярному виразові. Попередній "(?=рег_вираз)" може співставлятися з довільними регулярними виразами, зате задній "(?<=сталий_рег_вираз)" працює тільки з регулярними виразами сталої довжини, тобто зі сталою кількістю знаків. Таким чином, "(?<=(ab|bc))", це правильно, а "(?<=(ab)*)" - ні. Заперечувальними версіями попереднього і заднього пошуку є "(?!рег_вираз)" і "(?<!сталий_рег_вираз)". Вони справджуються, якщо регулярний вираз не збігається:


$x = "foobar";
$x =~ /foo(?!bar)/; # не збігається, 'bar' слідує за 'foo'
$x =~ /foo(?!baz)/; # збігається, 'baz' не слідує за 'foo'
$x =~ /(?<!\s)foo/; # збігається, \s відсутнє попереду 'foo'
"\C" не підтримується разом із зворотнім пошуком, завдячуючи вже доволі заплутаному визначенню "\C", яке стане ще складнішим, якщо рухатись у зворотньому напрямку.

Незалежні підвирази для запобігання зворотньому вистежуванню

Останні декілька розширених регулярних вирази у цьому посібнику ще експериментальні, як для Perl 5.6.0. Можете бавитись із ними, використати в якомусь коді, але не покладайтеся на них у робочих програмах.

Незалежні підвирази, це регулярні вирази в контексті більших регулярних виразів, але які діють незалежно від основного виразу. Тобто, вони споживають стільки ланцюжка, скільки їм заманеться, не зважаючи на можливість збігу більшого регулярного виразу. Незалежні підвирази представлені як "(?>рег_вираз)". Ми можемо продемонструвати їхнє поводження, якщо звернемося спершу до звичайного регулярного виразу:


$x = "ab";
$x =~ /a*ab/; # збігається
Це очевидно збігається, але в процесі збігу, підвираз "a*" спочатку захватив "a". Ця дія не дозволяє збігові цілого регулярного виразу, тож після після повернення назад, "a*" віддав назад "a" та збігся з порожнім ланцюжком (нульовою кількістю "а"). Тут, те із чим збігся "a*" залежить від того, з чим збігається решта регулярного виразу.

Тепер порівняйте це з незалежним підвиразом:


$x =~ /(?>a*)ab/; # не збігається!
Незалежний підвираз "(?>a*)" не зважає на решту регулярного виразу, тож коли він бачить "a", то захвачує його. Після такого, решта регулярного виразу "ab" не може вже збігтися. Оскільки "(?>a*)" є незалежним, повернення назад не відбувається, і незалежний підвираз не віддає свого "a". Таким чином, збіг регулярного виразу як цілого зазнає невдачі. Щось подібне ми спостерігаємо і у випадку повністю незалежних регулярних виразів:


$x = "ab";
$x =~ /a*/g; # збігається, з'їдає 'a'
$x =~ /\Gab/g; # не збігається, 'a' вже недоступна
Тут, "//g" та "\G" взаємодіють разом, щоб передати ланцюжок від одного регулярного виразу іншому. Регулярні вирази з незалежними підвиразами дуже схожі на цей приклад за своєю дією, а саме - передачу ланцюжка незалежному підвиразові, після чого повернення його назад основному виразу.

Здадтність незалежних підвиразів уникати повернення назад (зворотнього вистежування) може виявитися досить корисною. Зкажімо, ми хотіли би знайти збіг із не-порожнім ланцюжком, включеним у дужки двох рівнів. У такому разі, збіжиться наступний регулярний вираз:


$x = "abc(de(fg)h"; # неврівноважені дужки
$x =~ /\( ( [^()]+ | \([^()]*\) )+ \)/x;
Цей регулярний вираз збігається з відкриваючою дужкою, однією або більше копіями альтернативних виразів, і закриваючою дужкою. В цьоми виразі існують два розгалуження: перша альтернатива "[^()]+" збігаючись із підланцюжком без дужок, тоді як друга "\([^()]*\)" - з ланцюжком, включеним у дужки. Проблема з останнім регулярним виразом полягає у тому, що він "патологічний" - включає гніздовані необмежені квантори типу "(a+|b)+". Ми розглянули в Частині 1, як гніздовані квантори на зразок цих, можуть забрати експоненціно багато часу для виконання, якщо збіг не відбувся. Щоб запобігти зайвій втраті часу на перевірку, нам необхідно в якусь мить перешкодити поверненню назад (зворотньому вистежуванню). Цього можна досягти, якщо вказати внутрішній квантор як незалежний підвираз:


$x =~ /\( ( (?>[^()]+) | \([^()]*\) )+ \)/x;
Так, "(?>[^()]+)" запобігає невдалому розбиттю ланцюжка, захвачуючи найбільшу можливу частину ланцюжка та зберігаючи її. В такому разі, невдалі збіги зазнають поразки набагато швидше.

Умовні вирази

Умовні вирази являються своєрідною формою тверджень if-else, що дозволяє вибрати, з яким шаблоном порівнювати, в залежності від певної умови. Існують два типи умовних виразів: "(?(умова)так-шаблон)" i "(?(умова)так-шаблон|ні-шаблон)". "(?(умова)так-шаблон)" аналогічний Perl-твердженню 'if () {}'. Якщо "умова" справджується, відбудеться порівняння з "так-шаблоном". Коли "умова" хибна, "так-шаблон" пропускається і perl перейде до наступного елементу регулярного виразу. Другу форму можна порівняти з твердженням 'if () {} else {}'. Якщо "умова" дійсна, матиме місце порівняння з "так-шаблоном", у протилежному випадку - з "ні-шаблоном".

Сама "умова" може також мати дві форми. Перша - це просто ціле, включене в дужки "(ціле)". Ця умова ційсна, якщо попереднє зворотнє звертання "\ціле" збіглося раніше. Друга форма, це просто нульовий вираз заглядання вперед або назад, або кодоване твердження (розглянуте в наступному розділі).

Форма з цілим, як умова, дозволяє нам гнучкий вибір порівняння з шаблоном, у залежності від того, що збіглося раніше з регулярним виразом. Це, як правило, шукає слова, що мають форму "$x$x" або "$x$y$y$x":


% simple_grep '^(\w+)(\w+)?(?(2)\2\1|\1)$' /usr/dict/words
beriberi
coco
couscous
deed
...
toot
toto
tutu
"Умова" із загляданням назад дозволяє, наряду зі зворотнім посиланням, попередньому порівнянню впливати на подальший збіг. Наприклад,


/[ATGC]+(?(?<=AA)G|C)$/;
збігається з такою послідовністю ДНК, яка попередньо закінчується на "AAG", або якоюсь інщою базовою комбінацією та "C". Зверніть увагу на "(?(?<=AA)G|C)" форму, а не "(?((?<=AA))G|C)"; для заглядання наперед, назад, або виразів із кодом, дужки навколо умови необов'язкові.

Трохи чаклункства: виконання Perl-коду зсередини регулярного виразу

Звично, регулярні вирази являються частиною виразів Perl. Вирази обчислення коду, натомість, дозволяють обернути це навпаки, через те, що дають змогу довільному Perl-кодові бути частиною регулярного виразу. Вирази обчислення коду позначаються як "(?{код})", де "код" являється ланцюжком, що складається з тверджень Perl.

Вирази з кодом, це твердження нульової довжини, і значення, яке вони повертають, залежить від середовища. Існують дві можливості: або вираз із кодом використовується як умова, всередині виразу умови, як от "(?(умова)...)", або ні. Якщо вираз із кодом являється умовою, код обчислюється, і результат (тобто, результат виконання останньої дії) застосовується для визначення істини або хибності умови. Якщо вираз із кодом не використовується як умова, таке твердження завжди обчислюється як істинне, і результа розміщенно в спеціальну змінну $^R. Змінна $^R може вживатися пізніше у виразах із кодом регулярного виразу. Ось декілька нехитрих прикладів:


$x = "abcdef";
$x =~ /abc(?{print "Hi Mom!";})def/; # збігається,
# виводить 'Hi Mom!'
$x =~ /aaa(?{print "Hi Mom!";})def/; # немає збігу,
# жодного виводу
Але розгляньте уважніше наступні приклади:


$x =~ /abc(?{print "Hi Mom!";})ddd/; # не збігається,
# не виводить 'Hi Mom!'
# але чому ні?
Це зрозуміло. Виводу немає, оскільки "ddd" не збігається з ланцюжком призначення. Але в цьому прикладі


$x =~ /abc(?{print "Hi Mom!";})[d]dd/; # не збігається,
# АЛЕ виведе 'Hi Mom!'
Дивно... Що ж тут сталося? Якщо ви були уважними, то знаєте, що останній приклад тотожний попередньому - включення літери d у клас символів не повинно вплинути а те із чим вон збігається. Тож, чому перший вираз нічого не виведе, а другий - так?

Відповідь криється в оптимізаціях, які здійснює двигун регулярних виразів. У першому випадку, все, що двигун бачить, так це звичайні символи (за винятком конструкції "?{}", звичайно). Він достатньо розумний, щоб збагнути відсутність ланцюжка 'ddd' у ланцюжкові призначення перед тим як, власне, обробити шаблон. Але в другому випадку, нас змусили думати, що наш шаблон складніший, ніж він являється насправді. Він погляне на це, побачить клас символів, і вирішить, що необхідно буде здійснити перевірку шаблоном, щоб дізнатися, чи збігається текст, чи ні. В процесі, він стріне твердження print, до того, як виявити, що збігу немає.

Щоб ознайомитись із тим, як двигун здійснює оптимізацію, подивіться розділ "Вказівки та зневадження" нижче.

Додаткові приклади з "?{}":


$x =~ /(?{print "Hi Mom!";})/; # збігається,
# виведе 'Hi Mom!'
$x =~ /(?{$c = 1;})(?{print "$c";})/; # збігається,
# виведе '1'
$x =~ /(?{$c = 1;})(?{print "$^R";})/; # збігається,
# виведе '1'
Чаклункство, згадуване в назві розділу, полягає в зворотньому вистежуванні регулярним виразом у процесі пошуку збігів. Якщо регулярний вираз у процесі зворотнього вистежування зустріне вираз із кодом, і якщо змінні, використовувані всередині локалізовано за допомогою "local", нові значення змінних, набуті завдяки виразові з кодом, залишаються сталими! Таким чином, якби ми хотіли підрахувати, скільки разів якийсь символ всередині групи збігся, то могли б написати щось на зразок


$x = "aaaa";
$count = 0; # ініціалізація відліку повторень 'a'
$c = "bob"; # перевірка, чи $c перезаписано
$x =~ /(?{local $c = 0;}) # ініціалізація відліку
( a # порівняння з 'a'
(?{local $c = $c + 1;}) # приріст відліку
)* # довільна кількість разів, але
aa # обов'язковий збіг із 'aa' вкінці
(?{$count = $c;}) # копіює локальну $c до $count
/x;
print "'a' count is $count, \$c variable is '$c'\n";
Це виведе


'a' count is 2, $c variable is 'bob'
Якщо б ми поміняли " (?{local $c = $c + 1;})" на (?{$c = $c + 1;})", поміняне значення змінної не було би скасоване під час вистежування, тож ми отримали б


'a' count is 4, $c variable is 'bob'
Зверніть увагу, що тільки зміну значення локалізованих змінних скасовано. Інші побічні ефекти виконання виразу із кодом залишаються сталими. Таким чином,


$x = "aaaa";
$x =~ /(a(?{print "Yow\n";}))*aa/;
виведе


Yow
Yow
Yow
Yow
Результат $^R автоматично локалізовано, тож він поводитиметься належним чином під час вистежування.

Наступний приклад вживає код в умовному виразі для знаходження збігів з англійським або німецьким артиклем 'the' або 'die' ('das, der'):


$lang = 'DE'; # використати німецьку
...
$text = "das";
print "matched\n"
if $text =~ /(?(?{
$lang eq 'EN'; # перевірка, чи мова не є англійською
})
the | # якщо так, тоді шукати 'the'
(die|das|der) # у протилежному випадку - 'die|das|der'
)
/xi;
Зверніть увагу, що синтаксис тут складається з "(?(?{...})так-вираз|ні-вираз)", не "(?((?{...}))так-вираз|ні-вираз)". Інакше кажучи, у випадку з виразом із кодом відпадає необхідність додаткових дужок навколо виразу-умови.

Якщо ви спробуєте використати вирази з кодом з інтерпольованими змінними, Perl може вас дещо здивувати:


$bar = 5;
$pat = '(?{ 1 })';
/foo(?{ $bar })bar/; # компілюється, $bar не інтерпольовано
/foo(?{ 1 })$bar/; # помилка компіляції!
/foo${pat}bar/; # помилка компіляції!

$pat = qr/(?{ $foo = 1 })/; # попередня компіляція регулярного
# виразу
/foo${pat}bar/; # компілюється як слід
Якщо регулярний вираз включає (1) вираз із кодом і інтерпольовані змінні, або (2) змінну, що інтерполюється у вираз із кодом, perl вважатиме такий регулярний вираз невірним. Однак, якщо вираз із кодом попередньо компільовано в змінній, інтерполяція матиме успіх. Питання: чому виникає ця помилка?

Причина полягає в тому, що інтерполяція змінних у поєднанні з виразами з кодом утворюють загрозу безпеці системи. Така комбінація небезпечна, оскільки багато програмістів, що пишуть пошукові двигуни, часто беруть користувацький ввід і вносять його безпосередньо до регулярного виразу:


$regexp = <>; # читає регулярний вираз, вказаний користувачем
$chomp $regexp; # видаляє в ньому знак нового рядка
$text =~ /$regexp/; # пошук серед $text виразу $regexp
Якщо змінна $regexp міститиме вираз із кодом, користувач зможе виконати довільний Perl-код. Наприклад, якийсь жартівник може виконати завдяки кодові в регулярному виразі "system('rm -rf *');", щоб видалити всі ваші файли. В такий спосіб, комбінація з інтерполяції та виразів із кодом "заражає" ваш регулярний вираз. Тож, стандартно, використання обох, інтерполяції та виразів із кодом, у тому самому регулярному виразі не дозволяється. Коли ж ви не хвилюєтесь щодо злонамірених користувачів, існує можливість обійти перевірку на безпечнісь, якщо викликати "use re 'eval'" :


use re 'eval'; # відкидаємо обережність
$bar = 5;
$pat = '(?{ 1 })';
/foo(?{ 1 })$bar/; # компілюється
/foo${pat}bar/; # також компілюється
Іншою формою виразів із кодом є вираз шаблонного коду. Вираз шаблонного коду схожий на звичайний вираз із кодом за винятком того, що результат обчислення коду розглянуто як регулярний вираз і здійснюється негайне порівняння із текстом. Ось простий приклад:


$length = 5;
$char = 'a';
$x = 'aaaaabb';
$x =~ /(??{$char x $length})/x; # збігається, з 5-а літер 'a'
Останній приклад включає обидва вирази, звичайний і шаблонного коду. Він може виявити, чи бінарний ланцюжок 1101010010001... містить фібоначчі-послідовність одиниць, як от 0,1,1,2,3,5,... :


$s0 = 0; $s1 = 1; # initial conditions
$x = "1101010010001000001";
print "It is a Fibonacci sequence\n"
if $x =~ /^1 # збіг із початковою '1'
(
(??{'0' x $s0}) # збіг із $s0 кількістю '0'
1 # а потім '1'
(?{
$largest = $s0; # найбільша послідовність досі
$s2 = $s1 + $s0; # обчислити наступний член
$s0 = $s1; # фібоначчі-послідовності
$s1 = $s2;
})
)+ # repeat as needed
$ # that is all there is
/x;
print "Largest sequence matched was $largest\n";
Це виведе


It is a Fibonacci sequence
Largest sequence matched was 5
Хе-хе! Спробуйте це зі звичайним набором пакету регулярних виразів...

Зверніть увагу, що змінні $s0 та $s1 не інтерпольовано під час компіляції регулярного виразу, як це стається зі звичайними змінними поза виразів із кодом. Скоріше, вирази із кодом обчислюються, коли perl стикається з ними під час пошуку співпадань.

Той самий вираз, але без модифікатору "//x" виглядатиме як


/^1((??{'0'x$s0})1(?{$largest=$s0;$s2=$s1+$s0$s0=$s1;$s1=$s2;}))+$/;
і є хорошим стартом для початківця конкурсу Заплутаних Perl-скриптів (Obfuscated Perl Contest) :-) Працюючи з кодом і умовими виразами, розширена форма регулярних виразів є майже необхідною для створення та зневадження регулярних виразів.

Вказівки та зневадження

Щодо відлагодження, існує декілька вказівок (прагм) для керування та зневадження регулярних виразів Perl. Ми вже зіткнулися з однією в попередньому розділі, "use re 'eval';", яка дозволяє співіснування інтерполяції змінних і виразів із кодом у тому самому регулярному виразові. Іншими вказівками є


use re 'taint';
$tainted = <>;
@parts = ($tainted =~ /(\w+)\s+(\w+)/; # @parts тепер "заражена"
Вказівка "taint" спричиняє до того, що будь-який підланцюжок, що збігся із "зараженою" змінною, буде також "зараженим". Звично, це не так, оскільки регулярні вирази, як правило, використовуються, щоб добути безпечні біти із "зараженої" змінної. Скористайтеся з "taint", коли вас не цікавлять безпечні біти, натомість ви здійснюєте якісь інші обчислення. Обидві вказівки "taint" і "eval" мають обмежену лексичну зону дії, що означає, що їхня дія розповсюджується тільки всередині блоку коду, де вони знаходяться.


use re 'debug';
/^(.*)$/s; # виведе інформацію для відлагодження

use re 'debugcolor';
/^(.*)$/s; # виведе інформацію для відлагодження в кольорі
Глобальні вказівки "debug" і "debugcolor" дозволять отримати докладну інформацію про компіляцію регулярного виразу та його виконання. "debugcolor" аналогічна "debug" за винятком того, що перша спричинить кольоровий вивід інформації для відладки на терміналах, які підтримують кольорові послідовності termcap(5). Ось приклад виводу:


% perl -e 'use re "debug"; "abc" =~ /a*b+c/;'
Compiling REx `a*b+c'
size 9 first at 1
1: STAR(4)
2: EXACT <a>(0)
4: PLUS(7)
5: EXACT <b>(0)
7: EXACT <c>(9)
9: END(0)
floating `bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx `a*b+c' against `abc'...
Found floating substr `bc' at offset 1...
Guessed: match at offset 0
Matching REx `a*b+c' against `abc'
Setting an EVAL scope, savestack=3
0 <> <abc> | 1: STAR
EXACT <a> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
1 <a> <bc> | 4: PLUS
EXACT <b> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
2 <ab> <c> | 7: EXACT <c>
3 <abc> <> | 9: END
Match successful!
Freeing REx: `a*b+c'
Якщо ви добралися до цього місця посібника, то, напевне, здогадуєтесь, що означають окремі частини цього виводу. В першій частині


Compiling REx `a*b+c'
size 9 first at 1
1: STAR(4)
2: EXACT <a>(0)
4: PLUS(7)
5: EXACT <b>(0)
7: EXACT <c>(9)
9: END(0)
описано стадію компіляції. STAR(4) означає присутність об'єкту із зірочкою '*' (в даному випадку це 'a'), і якщо він зійдеться, перейти до рядка 4, тобто PLUS(7). У рядках посередині осписано певну евристику та оптимізації, що відбудуться перед порівнянням із шаблоном:


floating `bc' at 0..2147483647 (checking floating) minlen 2
Guessing start of match, REx `a*b+c' against `abc'...
Found floating substr `bc' at offset 1...
Guessed: match at offset 0
Після цього виконується саме порівняння, опис якого включено в останні рядки:


Matching REx `a*b+c' against `abc'
Setting an EVAL scope, savestack=3
0 <> <abc> | 1: STAR
EXACT <a> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
1 <a> <bc> | 4: PLUS
EXACT <b> can match 1 times out of 32767...
Setting an EVAL scope, savestack=3
2 <ab> <c> | 7: EXACT <c>
3 <abc> <> | 9: END
Match successful!
Freeing REx: `a*b+c'
Кожний етап має форму "n <x> <y>", де "<x>" відповідає частині ланцюжка, яка збіглася, a "<y>" - частині, яка ще ні. "| 1: STAR" вказує, що perl знаходиться в рядкові 1 n компіляційного списку вище. Подивіться також "Зневадження регулярних виразів" сторінки perldebguts(1) для докладнішого опису.

Альтернативною методою зневадження рягулярних виразів є включення команди "print" всередині регулярного виразу. Це дозволяє покроковий опис вистежування у випадку альтернатив:


"that this" =~ m@(?{print "Start at position ", pos, "\n";}) t(?{print "t1\n";}) h(?{print "h1\n";}) i(?{print "i1\n";}) s(?{print "s1\n";}) | t(?{print "t2\n";}) h(?{print "h2\n";}) a(?{print "a2\n";}) t(?{print "t2\n";}) (?{print "Done at position ", pos, "\n";}) @x;
що видасть


Start at position 0
t1
h1
t2
h2
a2
t2
Done at position 4

Вади

Вирази з кодом, умовні вирази та незалежні вирази являються експериментальними. Не вживайте їх у виробничому коді. Поки-що.

Дивіться також

Цей просто посібник. Для повного опису регулярних виразів, подивіться до сторінки-довідника регулярних виразів perlre(1).

Для додаткової інформації про оператори порівняння "//m" і заміни "s///", ознайомтеся із розділом "Оператори типу залапковування регулярних виразів" сторінки perlop(1). Стосовно оператору "split", знайдіть "split" у perlfunc(1).

Чудовим, загальним ресурсом про регулярні вирази є книжка Mastering Regular Expressions автора Jeffrey Friedl (видання O'Reilly, ISBN 1556592-257-3).

Переклав українською Віталій Цибуляк.

2007-10-27-16:31 © 2005-2007 DLOU, GNU FDL