table of contents
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/; # зійдеться
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";
}
"Hello World" =~ m!World!; # обмежувач '!'
"Hello World" =~ m{World}; # парні обмежувачі '{}'
"/usr/bin/perl" =~ m"/perl"; # лапки як обмежувач,
# '/' в ролі звичайного знаку
Давайте розглянемо, як різноманітні вирази спробують збігтися з "Hello World":
"Hello World" =~ /world/; # не сходиться за регістром
"Hello World" =~ /o W/; # зійдеться, ' ' є звичайним символом
"Hello World" =~ /oW/; # не зійдеться
"Hello World" =~ /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/; # збігається
"1000\t2000" =~ m(0\t2) # збігається
"1000\n2000" =~ /0\n20/ # збігається
"1000\t2000" =~ /\000\t2/ # не збігається, "0" не тотожно "\000"
"cat" =~ /\143\x61\x74/ # збігається, але це досить дивний спосіб
# написання слова "cat"
$foo = 'house';
'housecat' =~ /$foo/; # збігається
'cathouse' =~ /cat$foo/; # збігається
'housecat' =~ /${foo}cat/; # збігається
% 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
Усі вищенаведені регулярні вирази збігаються з будь-яким місцем в ланцюжкові, де знайдено відповідність шаблонові. Іноді, проте, ми би хотіли вказати де саме очікується збіг з регулярним виразом. Щоб добитися цього, нам необхідно використати опорні (закріплювальні) метасимволи "^" і "$". Закріплювач "^" означає збіг напочатку ланцюжка, тоді як "$" - наприкінці, тобто перед знаком нового рядка. Ось приклади їхнього використання:
"housekeeper" =~ /keeper/; # збігається
"housekeeper" =~ /^keeper/; # не збігається
"housekeeper" =~ /keeper$/; # збігається
"housekeeper\n" =~ /keeper$/; # збігається
Коли вживаються обидва, "^" і "$" одночасно, регулярний вираз повинен зійтися з обидвома, початком і кінцем ланцюжка, іншими словами, вираз намагається збігтися з цілим ланцюжком. Розгляньмо
"keeper" =~ /^keep$/; # не збігається
"keeper" =~ /^keeper$/; # зійдеться
"" =~ /^$/; # ^$ збігається з порожнім ланцюжком
"dogbert" =~ /bert/; # збігається, але не те, що вам потрібно
"dilbert" =~ /^bert/; # не збігається, але...
"bertram" =~ /^bert/; # збігається, але ще не те
"bertram" =~ /^bert$/; # не збігається, добре
"dilbert" =~ /^bert$/; # не збігається, добре
"bert" =~ /^bert$/; # збігається, чудово
Використання символьних класів ¶
Хоч ми вже можемо здійснити багато речей з регулярними виразами з літеральних ланцюжків, розглянутих вище, ми тільки ледве торкнулися можливостей техніки регулярних виразів. В наступних розділах ми ознайомимо читача з концепціями (і відповідними нотаціями з метасимволів), які дозволяють регулярним виразам представляти не тільки послідовності з одиничних знаків, але цілих класів символів.
Однією з таких концепцій саме являється символьний клас. Клас символів передбачає набір можливих знаків, замість тільки одного. Класи символів позначаються квадратними дужками "[...]", що оточують список можливих знаків для збігу. Ось декілька прикладів:
/cat/; # зійдеться з 'cat'
/[bcr]at/; # зійдеться з 'bat, 'cat', або 'rat'
/item[0123456789]/; # зійдеться з 'item0' ... аж до 'item9'
"abc" =~ /[cab]/; # зійдеться з 'a' в ланцюжкові
/[yY][eE][sS]/; # зійдеться 'yes' у регістро-незалежний спосіб
# 'yes', 'Yes', 'YES' тощо
Як ми побачили в попередньому підрозділі, існують звичайні знаки, що представляють самих себе, і спеціальні знаки, які вимагають оберненого слешу "\" для власного представлення. Те саме справджується і для класу символів, але набір звичайних і спеціальних символів відрізняється всередині класу від того, що використовується поза його межами. Спеціальними для класу символів являються "-]\^$". "]" є спеціальним, оскільки він позначає кінець символьного класу. "$" позначає скалярну змінну, а "\" використовується для керівних послідовностей. Ось приклад оперування спеціальними "]$\":
/[\]c]def/; # збігається з ']def', або 'cdef'
$x = 'bcr';
/[$x]at/; # збігається з 'bat', 'cat', або 'rat'
/[\$x]at/; # збігається з '$at', або 'xat'
/[\\$x]at/; # збігається з '\at', 'bat, 'cat', або 'rat'
Спеціальний символ '-' діє як оператор діапазону всередині класу знаків, тож набір суміжних знаків можна записати як інтервал. З діапазонами, незграбні "[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)
Спеціальний символ "^" як перший знак класу позначає заперечувальний клас, що збігатиметься з будь-яким символом окрім тих, що внесено в цей клас. Обидва, "[...]" і "[^...]" повинні збігтися з якимось символом, інакше порівнювання зазнає невдачі. Таким чином,
/[^a]at/; # не зійдеться з 'aat', або 'at', зате з рештою,
# як от 'bat', 'cat, '0at', '%at' тощо.
/[^0-9]/; # збігається з нечисловим символом
/[a^]at/; # сходиться з 'aat', або '^at'; тут '^' є звичайним
# символом
- \d
-
позначає цифру, тотожно [0-9] - \s
-
позначає пробіл, тотожно [\ \t\r\n\f] - \w
-
позначає символ, який може бути частиною слова (алфавітно-числовий, або _), тотожно [0-9a-zA-Z_] - \D
-
протилежний \d; будь-який символ окрім цифри, тотожно [^0-9] - \S
-
протилежний \s; будь-який символ окрім пробілу, тотожно [^\s] - \W
-
протилежний \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.'
Корисним опорним символом для базових регулярних виразів являється обмежувач слова "\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"
- модифікатори відсутні (//)
-
: стандартне поводження. '.' збігається з будь-яким знаком за винятком "\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"
$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"
"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' не зійшлося
Процес порівняння з однією з альтернатив, і, якщо вона не збіглася, переходу до іншої, називається зворотнім висліджуванням. Цей термін походить від уявлення, що порівнювання з шаблоном - це ніби блукання лісом. Маючи різноманітні стежки і розгалуження, і досліджуючи їх по-черзі, вам доводиться іноді повертатися назад до того самого місця, якщо шлях виявився тупиковим. Щоб бути конкретнішим, ось покроковий аналіз того, що робить Perl, коли намагається зіставити з шаблоном:
"abcde" =~ /(abd|abc)(df|d|de)/;
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'.
Можна зробити ще декілька зауважень щодо цього аналізу. Перше, - третя альтернатива з другої групи, '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;
}
# видобути годину, хвилину, секунду
($hours, $minutes, $seconds) = ($time =~ /(\d\d):(\d\d):(\d\d)/);
/(ab(cd|ef)((gi)|j))/;
1 2 34
Тісно пов'язаними зі змінними збігу $1, $2, ... є зворотні посилання "\1", "\2", ... . Зворотні посилання, це просто змінні збігу, які можна використати всередині регулярного виразу. Це справді гарна риса - те, що зійдеться пізніше з регулярним виразом може залежати від того, що зійшлося раніше. Скажімо, ми хочемо знайти повторні слова в тексті, як скажімо 'the the'. Наступний регулярний вираз знаходить дубльовані слова з трьох літер із пробілом між ними:
/(\w\w\w)\s\1/;
% simple_grep '^(\w\w\w\w|\w\w\w|\w\w|\w)\1$' /usr/dict/words
beriberi
booboo
coco
mama
murmur
papa
На додаток до цього, 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)
$x = "the cat caught the mouse";
$x =~ /cat/; # $` = 'the ', $& = 'cat', $' = ' caught the mouse'
$x =~ /the/; # $` = '', $& = 'the', $' = ' cat caught the mouse'
$` тотожно 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
$x = "the cat in the hat";
$x =~ /^(.*)(cat)(.*)$/; # збігається з
# $1 = 'the '
# $2 = 'cat'
# $3 = ' in the hat'
$x =~ /^(.*)(at)(.*)$/; # збігається з
# $1 = 'the cat in the h'
# $2 = 'at'
# $3 = '' (0 співпадань)
Коли регулярний вираз може збігтися з ланцюжком у декілька різноманітних способів, ми можемо скористатися з нищенаведених правил, щоб передбачити, як саме відбудеться збіг: .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'
$x =~ /(m{1,2})(.*)$/; # збігається з
# $1 = 'mm'
# $2 = 'ing republic of Perl'
$x =~ /.*(m{1,2})(.*)$/; # збігається з
# $1 = 'm'
# $2 = 'ing republic of Perl'
$x =~ /(.?)(m{1,2})(.*)$/; # збігається з
# $1 = 'a'
# $2 = 'mm'
# $3 = 'ing republic of Perl'
"aXXXb" =~ /(X*)/; # збігається з $1 = ''
Іноді жадібність не є бажаною. Час од часу, ви хотіли би, щоб квантори збігалися з мінімальною частиною ланцюжка, замість максимальної. Для цієї цілі, Лері Уол створив мінімальний збіг, або нежадібні квантори "??", "*?", "*?" та "{}?". Це ті самі квантори, із доданим "?". Вони мають наступне значення:
* "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'
$x =~ /(m{1,2}?)(.*?)$/; # збігається з
# $1 = 'm'
# $2 = 'ming republic of Perl'
$x =~ /(.*?)(m{1,2}?)(.*)$/; # збігається з
# $1 = 'The progra'
# $2 = 'm'
# $3 = 'ming republic of Perl'
$x =~ /(.??)(m{1,2})(.*)$/; # збігається з
# $1 = 'a'
# $2 = 'mm'
# $3 = 'ing republic of Perl'
Ми можемо змінити Правило 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+)*/;
Побудова регулярного виразу ¶
На даному етапі, ми ознайомилися зі всіма основними поняттями про регулярні вирази, тож давайте звернемося до конкретніших прикладів побудови регулярних виразів. Ми почнемо з виразу, що збігається з числами.
Першим кроком щодо побудови регулярного виразу являється вирішення того, із чим саме наш вираз повинен зійтися, і що виключити. В нашому випадку, ми хочемо збігу як з цілими, так і з числами з плаваючою точкою, і відкинення будь-яких ланцюжків, що не являються числами.
Наступним кроком є розбиття завдання на частини на менші задачі, що легше перетворити на регулярний вираз.
Найпростіше, це описати зразок цілих. Цілі складаються з послідовності з чисел, з необов'язковим знаком попереду. Цілі числа можна представити як "\d+", а знак як "[+-]". Таким чином, регулярний вираз для цілого складатиметься з
/[+-]?\d+/; # збігається з цілими
/[+-]?\d+\./; # 1., 321. тощо
/[+-]?\.\d+/; # .1, .234 тощо
/[+-]?\d+\.\d+/; # 1.0, 30.56 тощо
/[+-]?(\d+\.\d+|\d+\.|\.\d+)/; # число з плаваючою точкою без
# експоненти
Тепер, розглянемо числа з плаваючою точкою з експонентою. Основним зауваженням є тут те, що обидва, цілі числа та числа з плаваючою точкою можуть знаходитися попереду показника степеня. Експонента, так само як знак числа, можна "від'єднати від мантиси", тобто не є обов'язковою. Тепер стає ясною загальна форма регулярного виразу:
/^(необов'язк. знак)(ціле|число з пл. тчк.)(необов'язк. експонента)$/;
/[eE][+-]?\d+/; # експонента
/^[+-]?(\d+\.\d+|\d+\.|\.\d+|\d+)([eE][+-]?\d+)?$/; # ура!
/^
[+-]? # спершу, знайти необов'язковий знак
( # знайти ціле або числ. з пл. точкою:
\d+\.\d+ # мантиса, що має вигляд a.b
|\d+\. # мантиса, що має вигляд a.
|\.\d+ # мантиса, що має вигляд .b
|\d+ # ціле, яке виглядає як a
)
([eE][+-]?\d+)? # нарешті, необов'язкова експонента
$/x;
/^
[+-]?\ * # знайти необов'язковий знак *з пробілом*
( # знайти ціле або числ. з пл. точкою:
\d+\.\d+ # мантиса, що має вигляд a.b
|\d+\. # мантиса, що має вигляд a.
|\.\d+ # мантиса, що має вигляд .b
|\d+ # ціле, яке виглядає як a
)
([eE][+-]?\d+)? # нарешті, необов'язкова експонента
$/x;
/^
[+-]?\ * # знайти необов'язковий знак з пробілом
( # знайти ціле або числ. з пл. точкою:
\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/;
}
#!/usr/bin/perl
# Вдосконалений simple_grep
$regexp = shift;
while (<>) {
print if /$regexp/o; # набагато швидше
}
@pattern = ('Seuss');
while (<>) {
print if m'@pattern'; # збіжиться з буквальним '@pattern',
} # а не 'Seuss'
"dog" =~ /d/; # 'd' збігається
"dogbert =~ //; # також збігається з 'd' завдяки попередньому /d/
Використання "//g" продемонстровано в наступному прикладі. Припустімо ми маємо ланцюжок, що складається зі слів, розділених пробілами. Якщо ми знаємо скільки в ланцюжкові слів, то можемо добути ці слова за допомогою угруповування:
$x = "cat dog house"; # 3 слова
$x =~ /^\s*(\w+)\s+(\w+)\s+(\w+)\s*$/; # збігається з
# $1 = 'cat'
# $2 = 'dog'
# $3 = 'house'
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
В контексті переліку, "//g" повертає список груп, що збіглися, або якщо не використовувалося групування - список збігів із цілим регулярним виразом. Тож, якщо потрібні би були самі слова, то ми могли би звернутися до
@words = ($x =~ /(\w+)/g); # збігається з
# $word[0] = 'cat'
# $word[1] = 'dog'
# $word[2] = 'house'
$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" також незамінний при обробці регулярним виразом записів фіксованої довжини. Припустімо, ми має послідовність кодованої ділянки ДНК, закодованої за допомогою літер, як от "ATCGTTGAAT...", і ми хочемо знайти завершальні ланки "TGA". В кодованій ділянці, ланки складаються з 3-літерних послідовностей, тож ми можемо уявити ланцюжок ДНК як ряд 3-літерних записів. Спрощений регулярний вираз
# якщо розширити, то це повинно бути
# "ATC GTT GAA TGC AAA TGA CAT GAC"
$dna = "ATCGTTGAATGCAAATGACATGAC";
$dna =~ /TGA/;
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
Відповідь полягає в тому, що наш регулярний вираз добре працює доти, доки не пройде останній дійсний збіг. Після цього, регулярному виразові не вдасться збігтися з синхронізованим "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
Модифікатор "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
Оператор 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'
Якщо ви дочитали до цього місця, поздоровляємо! Тепер ви володієте основними знаннями про використання регулярних для вирішення широкого спектру задач по обробці тексту. Якщо ви вперше читаєте цей посібник, чому би вам не зупинитися тут на який час, щоб побавитися з регулярними виразами. Частина 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.'
$x = "This word is in lower case:\L SHOUT\E";
$x =~ /shout/; # збігається
$x = "I STILL KEYPUNCH CARDS FOR MY 360";
$x =~ /\Ukeypunch/; # збігається зі словом KEYPUNCH
Керівні символи можна екранувати за допомогою "\c", таким чином символ Control-Z збіжиться з "\cZ". Керівна послідовність "\Q"..."\E" екранує, тобто захищає, більшість символів, що не належать алфавітові. Наприклад,
$x = "\QThat !^*&%~& cat!";
$x =~ /\Q!^*&%~&\E/; # перевіряє на лайливі знаки
Починаючи з версії 5.6.0, регулярні вирази perl спроможні обробляти не тільки стандартні символи набору ASCII. Тепер Perl підтримує Юнікод, стандартний для кодування набору символів для багатьох мов світу. Юнікод здійснює це, дозволяючи довжині символів перевищувати один байт. Як користується кодуванням UTF-8, в якому символи ASCII все ще кодовано як один байт, але знаки більші за значенням за "chr(127)" можуть бути збереженими як два або більше байтів.
Яки це має стосунок до регулярних виразів? Добре, користувачі не обов'язково повинні знати щось про внутрішнє представлення ланцюжків. Але що вони повинні знати, так це 1) як представити Юнікод-символи в регулярному виразі 2) коли порівнювальний оператор розглядатиме текст пошуку як послідовність байтів (старий спосіб) або як послідовність Юнікод-символів (новий спосіб). Відповіддю для 1) є те, що Юнікод-символи більші за "chr(127)" можна представити як "\x{hex}" запис, де "hex", це шістнадцяткове ціле:
/\x{263a}/; # збіг зі смайликом Юнікоду :)
ПРИМІТКА: в 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";
Відповідь на вимогу 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/; # збігається, але це небезпечно!
Давайте тепер розглянемо решту символьних класів. Схоже до іменованих символів Юнікоду, існують також класи символів Юнікоду, представлені як екранована послідовність "\p{назва}". Відповідно існують класи "\P{назва}", які являються запереченням "\p{назви}". Так, наприклад, для збігу зі знаками нижнього та вернхнього регістру, нам потрібно
use charnames ":full"; # використання повних назв іменних символів
# Юнікоду
$x = "BOB";
$x =~ /^\p{IsUpper}/; # збігається; клас символів верхнього
# регістру
$x =~ /^\P{IsUpper}/; # не збігається; клас усіх символів, за
# винятком верхнього регістру
$x =~ /^\p{IsLower}/; # не збігається; клас символів нижнього
# регістру
$x =~ /^\P{IsLower}/; # збігається; клас усіх символів, за
# винятком нижнього регістру
Назва класу Назва класу, або регулярний вираз
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{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 міститиме компільований регулярний
# вираз
$x = "fooooba";
$x =~ $reg; # збігається, так само як /foo+bar?/
$x =~ /$reg/; # те саме, просто альтернативна форма запису
$x =~ /(abc)?$reg/; # також збігається
Попередньо-компільовані регулярні вирази корисні для створення динамічних збігів, які не потрібно перекомпільовувати кожний раз під час їхнього використання. Завдяки попередньо-компільованим регулярним виразам, можна розширити нашу програму "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;
Включення коментарів і модифікаторів у регулярні вирази ¶
Починаючи з цього підрозділу, ми почнемо обговорення набору розширених шаблонів регулярних виразів Perl. Вони являються додатком до традиційного синтаксису регулярних виразів і забезпечують потужними новими знаряддями пошуку за шаблоном. Ми вже бачили деякі розширення, такі як оператори мінімального збігу "??", "*?", "+?", "{n,m}?" i "{n,}?". Решта розширень матиме форму "(?символ...)", де "символ" вказуватиме тип розширення.
Першим являється включені коментарі "(?#текст)". Останнє вносить коментарі всередину регулярних виразів, без впливу на їхнє значення. Коментарі не повинні включати закриваючої дужки всередині "тексту". Ось приклад:
/(?# Match an integer:)[+-]?\d+/;
Модифікатори "//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' тощо
Незахоплюючі групи ¶
Як ми вказали в Частині 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+))?)/;
$x = '12a34b5';
@num = split /(a|b)/, $x; # @num = ('12','a','34','b','5')
@num = split /(?:a|b)/, $x; # @num = ('12','34','5')
Заглядання вперед і назад ¶
Цей розділ розгляне твердження заглядання вперед по тескту та назад. Спершу, трохи підгрунтя.
У регулярних виразах 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
$x = "foobar";
$x =~ /foo(?!bar)/; # не збігається, 'bar' слідує за 'foo'
$x =~ /foo(?!baz)/; # збігається, 'baz' не слідує за 'foo'
$x =~ /(?<!\s)foo/; # збігається, \s відсутнє попереду 'foo'
Незалежні підвирази для запобігання зворотньому вистежуванню ¶
Останні декілька розширених регулярних вирази у цьому посібнику ще експериментальні, як для Perl 5.6.0. Можете бавитись із ними, використати в якомусь коді, але не покладайтеся на них у робочих програмах.
Незалежні підвирази, це регулярні вирази в контексті більших регулярних виразів, але які діють незалежно від основного виразу. Тобто, вони споживають стільки ланцюжка, скільки їм заманеться, не зважаючи на можливість збігу більшого регулярного виразу. Незалежні підвирази представлені як "(?>рег_вираз)". Ми можемо продемонструвати їхнє поводження, якщо звернемося спершу до звичайного регулярного виразу:
$x = "ab";
$x =~ /a*ab/; # збігається
Тепер порівняйте це з незалежним підвиразом:
$x =~ /(?>a*)ab/; # не збігається!
$x = "ab";
$x =~ /a*/g; # збігається, з'їдає 'a'
$x =~ /\Gab/g; # не збігається, 'a' вже недоступна
Здадтність незалежних підвиразів уникати повернення назад (зворотнього вистежування) може виявитися досить корисною. Зкажімо, ми хотіли би знайти збіг із не-порожнім ланцюжком, включеним у дужки двох рівнів. У такому разі, збіжиться наступний регулярний вираз:
$x = "abc(de(fg)h"; # неврівноважені дужки
$x =~ /\( ( [^()]+ | \([^()]*\) )+ \)/x;
$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)$/;
Трохи чаклункства: виконання 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!'
# але чому ні?
$x =~ /abc(?{print "Hi Mom!";})[d]dd/; # не збігається,
# АЛЕ виведе 'Hi Mom!'
Відповідь криється в оптимізаціях, які здійснює двигун регулярних виразів. У першому випадку, все, що двигун бачить, так це звичайні символи (за винятком конструкції "?{}", звичайно). Він достатньо розумний, щоб збагнути відсутність ланцюжка 'ddd' у ланцюжкові призначення перед тим як, власне, обробити шаблон. Але в другому випадку, нас змусили думати, що наш шаблон складніший, ніж він являється насправді. Він погляне на це, побачить клас символів, і вирішить, що необхідно буде здійснити перевірку шаблоном, щоб дізнатися, чи збігається текст, чи ні. В процесі, він стріне твердження print, до того, як виявити, що збігу немає.
Щоб ознайомитись із тим, як двигун здійснює оптимізацію, подивіться розділ "Вказівки та зневадження" нижче.
Додаткові приклади з "?{}":
$x =~ /(?{print "Hi Mom!";})/; # збігається,
# виведе 'Hi Mom!'
$x =~ /(?{$c = 1;})(?{print "$c";})/; # збігається,
# виведе '1'
$x =~ /(?{$c = 1;})(?{print "$^R";})/; # збігається,
# виведе '1'
$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'
'a' count is 4, $c variable is 'bob'
$x = "aaaa";
$x =~ /(a(?{print "Yow\n";}))*aa/;
Yow
Yow
Yow
Yow
Наступний приклад вживає код в умовному виразі для знаходження збігів з англійським або німецьким артиклем '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/; # компілюється як слід
Причина полягає в тому, що інтерполяція змінних у поєднанні з виразами з кодом утворюють загрозу безпеці системи. Така комбінація небезпечна, оскільки багато програмістів, що пишуть пошукові двигуни, часто беруть користувацький ввід і вносять його безпосередньо до регулярного виразу:
$regexp = <>; # читає регулярний вираз, вказаний користувачем
$chomp $regexp; # видаляє в ньому знак нового рядка
$text =~ /$regexp/; # пошук серед $text виразу $regexp
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'
$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. Ми вже зіткнулися з однією в попередньому розділі, "use re 'eval';", яка дозволяє співіснування інтерполяції змінних і виразів із кодом у тому самому регулярному виразові. Іншими вказівками є
use re 'taint';
$tainted = <>;
@parts = ($tainted =~ /(\w+)\s+(\w+)/; # @parts тепер "заражена"
use re 'debug';
/^(.*)$/s; # виведе інформацію для відлагодження
use re 'debugcolor';
/^(.*)$/s; # виведе інформацію для відлагодження в кольорі
% 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)
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'
Альтернативною методою зневадження рягулярних виразів є включення команди "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 |