Сервісний центр VPSGroup ремонт комп'ютерної техніки, заправка картриджів, ремонт оргтехніки, Київ, Виставковий центр, Васильківська, 55

PowerShell і регулярні вирази (частина 2).

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

квантіфікатор

Як ви пам'ятаєте з попередньої частини, за допомогою символьних класів можна описати те, які саме символи треба шукати, але не можна вказати їх кількість.Так символьний клас [a-z] описує одну будь-яку букву латинського алфавіту, відповідно для опису більшої кількості букв потрібно конструкція [a-z] [a-z] ... [a-z]. Для прикладу виведемо системні процеси з ім'ям, що складається з трьох символів:

Get-Process | where {$ _. ProcessName -match "^ [a-z] [a-z] [a-z] $"}

Навіть для трьох символів це виглядає неакуратно і громіздко, однак дану конструкцію можна значно скоротити.Для цього в регулярних виразах існують спеціальні кількісні модифікатори (квантіфікатори). Квантіфікатор ставиться праворуч від символу (або класу) і вказує їх необхідну кількість. Наприклад квантіфікатор {3} означає 3 символу, відповідно попередній приклад з використанням квантіфікаторов буде виглядати так:

Get-Process | where {$ _.ProcessName -match "^ [az] {3} $"}



Вказувати точне число зовсім не обов'язково. Квантіфікатори дозволяють вказувати діапазон в форматі {x, y}, де х - мінімально необхідне, а у - максимально можлива кількість символів. Наприклад виведемо системні процеси з ім'ям, що містять у назві від 1 до 4 символів:

Get-Process | where {$ _.ProcessName -match "^ [az] {1,4} $"}



Максимальне значення можна опустити, задавши тільки мінімальне, наприклад {3,} означає "3 і більше символів". Для найбільш загальних квантіфікаторов є скорочене написання:

+ (плюс) - один або більше символів, еквівалент {1,};
* (зірочка) - будь-яка кількість символів або повна їх відсутність, еквівалент { 0,};
? (Знак питання) - один символ або відсутність символу, еквівалент {0,1}.

Зверніть увагу, що, на відміну від групових символів, квантіфікатори в регулярних виразах не використовуються самі по собі, а завжди мати перевагу над символу або групі, наприклад (.?) - один будь-який символ, (. *) - будь-яку кількість будь-яких символів. Наступною командою виведемо процеси з ім'ям, що починається і закінчуються на s, між якими може бути будь-яка кількість символів:

Get-Process | where {$ _.ProcessName -match "^ s. * S $"}



І ще, варто пам'ятати про необов'язковості квантіфікаторов, у яких в якості мінімального значення стоїть нуль (напр.? і *). Це означає, що регулярні вирази з їх використанням для позитивного результату не потребують хоча-б в одному збігу, вони збігаються навіть при відсутності символів.Наприклад вираження ".?", ". *" Або "[az] *" збігаються з чим завгодно, включаючи порожній рядок.



Оператори replace і split

Робота з регулярними виразами може включати в себе не тільки пошук, але і обробку знайденого. Для цих цілей в PowerShell є оператори replace і split, які можуть не тільки аналізувати рядки, а й виробляти в них деякі зміни.

Оператор replace знаходить частину рядка, яка підходить під регулярний вираз, і замінює її. Для прикладу запитаємо список файлів в директорії і помістимо його в змінну, а потім виведемо ім'я одного з файлів:

$ files = Get-ChildItem C: \ Files \ PS Books
$ files [1] .Name

А тепер візьмемо отримане ім'я і поміняємо в ньому "PowerShell" на "CMD":

$ files [1].Name -replace "PowerShell", "CMD"

Ще одна особливість replace в тому, що можна не вказувати рядок заміни. У цьому випадку буде проведена заміна знайденого об'єкта на порожнє місце, або просто видалення. Наприклад так ми видалимо слово "PowerShell" з назви файлу:

$ files [1] .Name -replace "PowerShell"



Оператор split рабівает вихідну рядок на частини і повертає масив рядків.Кордон, по якій проводиться розбиття, вказується за допомогою регулярного виразу. Наприклад, візьмемо багатостраждальний файл і розділимо його ім'я, використовуючи як роздільник пробіл:

$ files [1] .Name -split "\ s"

При необхідності через кому можна вказати максимальну кількість частин, на які можна розбити рядок:

$ files [1].Name -split "\ s", 2



Примітка. Так само як і match, оператори replace і split не залежить від регістру символів. Для цього у них є чутливі до регістру версії creplace і csplit.

Лінь і жадібність

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

"Greedy and Lazy Quantifiers" -replace "L. *"

Як бачите, в даному випадку квантіфікатор * відпрацював максимуму, захопивши частину рядка Lazy Quantifiers, т.е. все що слід після L і до кінця рядка. Оскільки такий підхід не завжди прийнятний, існує можливість обмежити квантіфікатор необхідним мінімумом. Для цього є "ледачі" версії квантіфікаторов, одержувані за допомогою додавання знаку, напр. * ?, + ?, ??, {1,10}?Трохи змінимо попередню команду:

"Greedy and Lazy Quantifiers" -replace "L. *?"

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

"Greedy and Lazy Quantifiers" -replace "L.*? \ S "

В цьому прикладі квантіфікатор змушений захопити мінімум, але до найближчого пробілу.



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

За допомогою круглих дужок символи в регулярних виразах можна об'єднувати в групи. Групи можна використовувати для впорядкування і угруповання, до них можна застосовувати квантіфікатори, наприклад:

"Test grouping in regular expressions."-Match" (\ w + \ s) {4} (\ w +) \. $ "

Однак основне призначення груп - це захоплення вмісту. Принцип захоплення полягає в тому, що частина рядка, яка збіглася з виразом всередині групи, поміщається в спеціальну змінну $ Matches. Ця змінна є масивом, в якому знаходитиметься вміст всіх груп, що входять у вираз.Для прикладу знайдемо в рядку два останніх слова, розділені пропуском:

"Test grouping in regular expressions." -Match "(\ w +) \ s (\ w +) \. $"

Якщо тепер вивести вміст змінної $ Matches, то під індексом 0 там знаходиться вся збіглася рядок цілком, під індексом 1 - вміст першої групи, під індексом 2 - вміст другої групи і т.д. Що особливо важливо, можна звертатися до кожного елементу окремо, наприклад:

$ Matches [2]

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



Групи можуть бути вкладеними одна в іншу, наприклад так:

"Test grouping in regular expressions. "-match" ((\ w +) \ s (\ w +)) \.$ "

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



При необхідності групі можна привласнити ім'я, щоб було зручніше до неї звертатися.Іменовані групи мають синтаксис (? Subexpression) або (? `Name` subexpression), де name - ім'я групи, а subexpression - регулярний вираз. Ім'я групи не повинно починатися з цифри і містити знаків пунктуації. Наприклад, попередній вираз з використанням іменованих груп буде виглядати так:

"Test grouping in regular expressions."-Match" (? `First` \ w +) \ s (?` Second` \ w +) \. $ "

Звертатися до іменованих груп можна як по їх номеру, так і по імені, наприклад:

$ Matches [ 'first`]

або так:

$ Matches.first



Зворотні посилання

Ще однією особливістю груп є те, що всередині регулярного виразу на них можна посилатися за допомогою конструкцій, посилаються як на зворотне посиланнями (backreference).У деяких випадках це дуже зручно, наприклад для пошуку повторюваних елементів в рядку. Посилатися на неіменовані групи можна за допомогою конструкції \ n, де n є порядковим номером групи в регулярному виразі. Для прикладу знайдемо повторювані слова в рядку:

"Test grouping in regular regular expressions."-Match" (\ w +) \ s (\ 1) "

Тут в першу групу потрапляє слово regular, посилання на яке (\ 1) потім використовується в другій групі.



Оскільки подібна запис використовується не тільки в зворотні посилання, але і для позначення вісімкових escape-кодів, то існують такі правила:

• Вирази від \ 1 до \ 9 завжди інтерпретуються як зворотні посилання, а не як восьмеричні числа;
• Вирази від \ 10 і більше вважаються зворотними посиланнями в тому випадку, якщо є зворотнє посилання, відповідна даним номером.В іншому випадку вираз інтерпретується як восьмеричний код;
• Якщо перша цифра многоразрядного вираження 8 або 9 (напр. \ 85), то вираз інтерпретується як літерал.

Позбутися від неоднозначності можна за допомогою іменованих посилань типу \ k або \ k`name`, де name - ім'я іменованої групи.Такі посилання однозначно вказують на групу і їх неможливо сплутати з вісімковими символами. З їх допомогою вираз зміниться наступним чином:

"Test grouping in regular regular expressions." -Match "(? \ W +) \ s (\ k)"



Незахвативающіе групи

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

"Test grouping in regular expressions." -Match "(?: \ W + \ s) (\ w +) \. $"

Як видно з прикладу, оскільки перша група є незахвативающей, в змінну $ Matches потрапило тільки вміст другої групи.



Практичний приклад

І на завершення статті невеличкий приклад з практики. Припустимо, є лог веб-сервера IIS, з якого мені необхідно дістати відповіді сервера на запити за адресою www.site.ru. Для зручності вивантажимо вміст файлу в змінну, а потім подивимося формат запису на прикладі однієї з рядків:

$ file = Get-Content C: \ Files \ err.log
$ file [5]

Як бачите, в рядку йде ім'я сайту, а відразу за ним код відповіді сервера. Виведемо необхідну інформацію такою командою:

$ file | where {$ _ -match "(www \ .site \ .ru) \ s ([\ d] {3})"} | foreach {$ Matches [0]}



На цьому прикладі завершимо другу частину захоплюючого