Назва

perlrequick - ознайомлення з регулярними виразами Perl

Опис

Ця сторінка охоплює основи розуміння, створення і використання регулярних виразів ('рег. виразів') у Perl.

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

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

       "Hello World" =~ /World/;  # зійдеться

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

Вирази на зразок цього корисні для перевірки умов:

       print "It matches\n" if "Hello World" =~ /World/;

Зміст порівняння можна обернути на протилежний за допомогою оператору "!~":

       print "It doesn't match\n" if "Hello World" !~ /World/;

Літеральний ланцюжок регулярного виразу можна замінити на змінну:

       $greeting = "World";
       print "It matches\n" if "Hello World" =~ /$greeting/;

Якщо ви порівнюєте із $, то "$ =~" частину можна опустити:

       $_ = "Hello World";
       print "It matches\n" if /World/;
   print "It matches\n" if /World/; 

Нарешті, стандартні обмежувачі рег. виразу "//" можна поміняти на довільні, якщо додати 'm' (скорочення від 'match') напочатку:

       "Hello World" =~ m!World!;   # обмежувач '!'
       "Hello World" =~ m{World};   # парні обмежувачі '{}'
       "/usr/bin/perl" =~ m"/perl"; # лапки як обмежувач,
                                    # '/' в ролі звичайного знаку

Регулярні вирази повинні точно співпасти з частиною ланцюжка, щоб вираз виявився істинним:

        "Hello World" =~ /world/;  # не сходиться за регістром
        "Hello World" =~ /o W/;    # зійдеться, ' ' є звичайним символом
        "Hello World" =~ /World /; # не зійдеться, немає ' ' наприкінці

perl завжди спробує здійснити співпадання в найпершому можливому місці ланцюжка:

       "Hello World" =~ /o/;       # збіглося з 'o' в 'Hello'
       "That hat is red" =~ /hat/; # зійшлося з 'hat' з 'That'

Не всі символи можна використати "такими, якими вони є" при порівнянні. Деякі знаки, названі метазнаками, резервовано для власних нотацій регулярних виразів. Метазнаками являються

       {}[]()^$.|*+?\

Співпадання із самим спеціальним знаком можна добитися, якщо попереду додати зворотній слеш:

   "2+2=4" =~ /2+2/;       # не збігається, + є метазнаком     
   "2+2=4" =~   /2\+2/;    # зійдеться, \+ розглядається як
                           # звичний +     
   'C:\WIN32' =~ /C:\\WIN/;                # зійдеться    
   "/usr/bin/perl" =~ /\/usr\/bin\/perl/;  # зійдеться 

В останньому виразі, правий слеш '/' так само екрановано, оскільки він використовується як обмежувач регулярного виразу.

Недруковні символи ASCII представлені керівними послідовностями. Спільними прикладами є "\t", що позначає табуляцію, "\n" - знак нового рядка, "\r" - повернення каретки тощо. Довільні байти можна представити вісімковими керівними послідовностями, наприклад "\033", або шістнадцятковими, як от "\x1B":

       "1000\t2000" =~ m(0\t2)    # зійдеться
       "cat" =~ /\143\x61\x74/    # зійдеться, хоч це й дивний спосіб
                                  # написання слова "cat" (кішка)

Регулярні вирази, в основному, розглядаються як привнесені в подвійні лапки ланцюжки, тож розкриття змінних працюватиме:

       $foo = 'house';
       'cathouse' =~ /cat$foo/;   # зійдеться
       'housecat' =~ /${foo}cat/; # зійдеться

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

       "housekeeper" =~ /keeper/;         # зійдеться
       "housekeeper" =~ /^keeper/;        # не зійдеться
       "housekeeper" =~ /keeper$/;        # сходиться
       "housekeeper\n" =~ /keeper$/;      # сходиться
       "housekeeper" =~ /^housekeeper$/;  # сходиться

Класи знаків

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

       /cat/;            # зійдеться з 'cat'
       /[bcr]at/;        # зійдеться з 'bat', 'cat' або 'rat'
       "abc" =~ /[cab]/; # зійдеться через 'a'

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

        /[yY][eE][sS]/; # зіставити з 'yes' у регістро-незалежний спосіб
                        # 'yes', 'Yes', 'YES' тощо
        /yes/i;         # так само зіставити з 'yes', не залежачи від
                        # регістру

В останньому прикладі показано порівнювання із зразком за використанням модифікатору 'i' (insensitive), що скасовує чутливість до регістру.

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

       /[\]c]def/; # збігається з ']def' or 'cdef'
       $x = 'bcr';
       /[$x]at/;   # збігається з 'bat, 'cat' і 'rat'
       /[\$x]at/;  # зійдеться з '$at' і 'xat'
       /[\\$x]at/; # зійдеться з '\at', 'bat, 'cat' і 'rat'

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

        /item[0-9]/;    # збігається з 'item0' аж до 'item9'
        /[0-9a-fA-F]/;  # зійдеться з шістнадцятковим числом

Знак '-' як перший або останній символ класу розглядається як звичайний знак.

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

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

Perl включає декілька скорочень для загальних класів знаків:

\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 =~ /\bcat/;  # збігається з cat у 'catenates'
       $x =~ /cat\b/;  # збігається з cat у 'housecat'
       $x =~ /\bcat\b/;  # збігається з 'cat' наприкінці ланцюжка

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

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

Ми можемо порівняти з різноманітними символьними ланцюжками за допомогою метасимволу альтернативи '|'. Щоб збіглося із "dog" або "cat", ми створимо рег. вираз "dog|cat". Як і раніше, Perl намагатиметься знайти збіг у найпершому можливому місці ланцюжка. В кожній позиції perl спочатку пробує зіставити з першою альтернативою "dog". Якщо "dog" не співпав, perl спробує тоді другу альтернативу "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"

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

Групування речей і ієрархічне порівнювання

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

       /(a|b)b/;   # збігається з 'ab' або 'bb'
       /(^a|b)c/;  # збігається 'ac' на початку ланцюжка і 'bc' деінде
       /house(cat|)/;  # збігається з або 'housecat', або 'house'
       /house(cat(s|)|)/;  # збігається з або 'housecats', або 'housecat',
                           # або 'house'. Зверніть увагу, що групи можуть
                           # гніздитися
       "20" =~ /(19|20|)\d\d/;  # збігається з нульовою альтернативою
                                # '()\d\d', оскільки '20\d\d' не співпадає

Звертання до збігів

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

       # добути години, хвилини, секунди
       $time =~ /(\d\d):(\d\d):(\d\d)/;  # збігається з форматом гг:хх:сс
       $hours = $1;
       $minutes = $2;
       $seconds = $3

У першому контексті, збіг з "/рег. виразом/" з групуванням поверне список значень, що зійшлися "($1, $2,...)". Ми можемо змінити це на

       ($hours, $minutes, $second) = ($time =~ /(\d\d):(\d\d):(\d\d)/);

Якщо групування в регулярному виразі гніздовано, $1 отримає групу з крайньою лівою відкриваючою дужкою, $2 з наступною відкриваючою дужкою тощо. Ось, наприклад, складний регулярний вираз і відповідні змінні, вказані нижче:

       /(ab(cd|ef)((gi)|j))/;
        1  2      34

Зі змінними збігів $1, $2, ... пов'язані зворотні посилання (backrerefence) "\1", "\2", ... Зворотні посилання - це змінні співпадань, які можна використати всередині регулярних виразів:

       /(\w\w\w)\s\1/; # знайти послідовності на кшталт 'the the'
                       # в ланцюжкові

$1, $2, ... повинні використовуватись тільки поза межами регулярного виразу, тоді як "\1", "\2", ... - тільки всередині.

Повторення збігів

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

"a?" : збігтися з 'a' 1 або 0 разів

"a*" : збігтися з 'а' 0 або більше разів, тобто будь-яку кількість разів

"a+" : збігтися з 'a' 1 або більше разів, тобто щонайменше один раз

"a{n,m}" : збігтися щонайменше "n" раз, але не більше ніж "m" раз

"a{n,}" : збігтися щонайменше "n" або більше разів

"a{n}" : збігтися точно "n" раз

З наступними прикладами:

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

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

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

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

Інші порівняння

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

Маючи код

       $pattern = 'Seuss';
       while (<>) {
           print if /$pattern/;
       }

perl повинен оцінити $pattern (зразок) щоразу при проходженні через цикл. Якщо $pattern не міняється, використайте модифікатор "//o" для одноразового розкриття змінної. Якщо ви не бажаєте жодного розкриття змінних, скористайтеся спеціального роздільника "m_": _

       @pattern = ('Seuss');
       m/@pattern/; # збігається з 'Seuss'
       m'@pattern'; # збігається з буквальним ланцюжком '@pattern'

Глобальний модифікатор "//g" дозволяє регулярному виразу зійтися всередині ланцюжка стільки разів скільки можливо. В скалярному контексті, послідовні співпадання всередині ланцюжка спричинять перехід "//g" від одного збігу до іншого, запам'ятовуючи при цьому позицію в ланцюжкові. Ви можете отримати або встановити позицію за допомогою функції pos(). Наприклад,

       $x = "cat dog house"; # 3 words
       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'

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

Пошук із заміною відбувається за схемою "s/рег. вираз/текст заміни/модифікатори". Текст заміни змістить те, що співпало з рег. виразом в ланцюжку. Оператор "=~" також застосовується тут для того, щоб пов'язати ланцюжок із дією "s///". Якщо порівнюється супроти $, то вираз "$ =~" можна опустити. При вдалім порівнянні, "s///" повертає число здійснених замін, у протилежному випадку - хибно. Ось деякі приклади:

       $x = "Time to feed the cat!";
       $x =~ s/cat/hacker/;   # $x міститиме "Time to feed the hacker!"
       $y = "'quoted words'";
       $y =~ s/^'(.*)'$/$1/;  # видалити одинарні лапки,
                              # $y міститиме "quoted words"

Із оператором "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"

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

       # обернути всі слова в ланцюжку
       $x = "the cat in the hat";
       $x =~ s/(\w+)/reverse $1/ge;  # $x міститиме "eht tac ni eht tah"

       # перетворити відсотки на десяткові числа
       $x = "A 39% hit rate";
       $x =~ s!(\d+)%!$1/100!e;      # $x міститиме "A 0.39 hit rate"

Останній приклад демонструє, що "s///" може використати інші обмежувачі, такі як "s!!!" і "s{}{}", або навіть "s{}//". Якщо застосувати одинарні лапки "s", то рег. вираз і заміна вважатимуться ланцюжками, включеними в одинарні лапки.

Оператор split

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

       $x = "Calvin and Hobbes";
       @word = split /\s+/, $x;  # $word[0] = 'Calvin'
                                 # $word[1] = 'and'
                                 # $word[2] = 'Hobbes'

А щоб добути розділений комою список чисел:

       $x = "1.618,2.718,   3.142";
       @const = split /,\s*/, $x;  # $const[0] = '1.618'
                                   # $const[1] = '2.718'
                                   # $const[2] = '3.142'

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

       $x = "/usr/bin";
       @parts = split m!(/)!, $x;  # $parts[0] = 
                                   # $parts[1] = '/'
                                   # $parts[2] = 'usr'
                                   # $parts[3] = '/'
                                   # $parts[4] = 'bin'

Позаяк перший знак $x збігся з регулярним виразом, split додала порожній перший елемент до списку.

Вади

Жодних.

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

Ця сторінка являється лише швидким введенням для початківців. Для глибшого ознайомлення з регулярними виразами зверніться до perlretut(1) і довідника perlre(1).

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