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

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

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

Альтернатива

Символ | (Вертикальна риса) в регулярних виразах означає «або» і дозволяє вибрати один з декількох варіантів.Наприклад, конструкція a | b | c означає "або a або b або c", де a b і c є альтернативами. Якщо взяти вираз, розглянуте в першій частині:

Get-Service | where {$ _. Name -match "v [ds] s"}

то за допомогою альтернатив його можна записати так:

Get-Service | where {$ _. Name -match "v (d | s) s"}

або так:

Get-Service | where {$ _.Name -match "vds | vss"}

Примітка. Не варто порівнювати символьні класи і альтернативи. Хоча в попередньому прикладі конструкції [ds] і (d | s) видають однаковий результат, в загальному випадку це абсолютно різні речі. Символьний клас описує тільки один символ, тоді як в якості альтернативи може виступати регулярний вираз необмеженої довжини і складності.



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

Get-Service | where {$ _.Name -match "vds" -or $ _. Name -match "vss"}



важливий момент при використанні альтернатив - це їх порядок. Для прикладу візьмемо вираз:

"one, two, three" -match "(one) | (two) | (three)"

Якщо опустити тонкощі, то перевірка проводиться так - береться перше слово в рядку (one) і порівнюється по черзі з кожною з альтернатив (one, two і three).Якщо збіг, як в нашому прикладі, знайдено, то видається позитивний результат, інакше процес повторюється для наступного слова (two) і так до знаходження відповідності або закінчення рядка. Оскільки в нашому прикладі слово one стоїть на початку рядка і альтернатива one знаходиться на першому місці, то пошук завершується максимально швидко.Тепер візьмемо такий вислів:

"one, two, three" -match "(three) | (two) | (one)"

Як бачите, тепер пошук піде трохи повільніше, оскільки one порівнюється спочатку з three, потім з two і тільки потім з one. А якщо взяти такий вислів:

"one, two, three" -match "(five) | (four) | (three)"

то процес буде йти ще довше, оскільки спочатку піде порівняння one з five, four і three, потім то-же для two вже потім для three.



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

Альтернатива на підставі умови

Альтернативу можна організувати так, що вибір між альтернативними варіантами буде здійснюватися в залежності від деякої умови. Так конструкція (? If (then | else)) означає, що спочатку перевіряється регулярний вираз if, якщо воно істинне - то виконується перевірка вираження then, інакше перевіряється вираз else.

Для прикладу візьмемо вираз, яке шукає в рядку IP або Mac-адреса:

"192.168.0.1" -match «(? (^ \ D {1,3} \. ) ((\ d {1,3} \.) {3} \ d {1,3}) | (\ w + -) {5} (\ w +))

Спочатку йде умова (? (^ \ d {1,3} \.), в якому перевіряється наявність на початку рядка від 1 до 3 цифр і точки (початок IP-адреси). Якщо збіг знайдено, то шукаємо IP-адреса за допомогою виразу (\ d {1,3} \.) {3} \ d {1,3}), інакше шукаємо Mac-адресу виразом (\ w + -) {5} (\ w +).



Примітка. Нагадаю, що IP представляє з себе 4 групи символів між якими ставиться крапка, в кожній групі від 1 до 3 цифр (напр. 192.168.0.1). Mac-адреса складається з 12 символів (букв або цифр), як правило згрупованих по 2 і розділених дефісом (напр.1A-2B-3C-4D-5E-6F).

Альтернатива на підставі захопленої групи

В якості умови може виступати захоплена група. Синтаксис виглядає як (? (Name) then | else)) або (? (Number) then | else)), де name і number - відповідно ім'я або номер захопленої групи. Якщо ім'я \ номер групи відповідає захопленої групі то виконується вираз then, інакше виконується вираз else.

Трохи змінимо попередній вираз, як умову використовуємо іменовану групу mac:

"1A-2B-3C-4D-5E-6F" -match «(? ^ \ W {2 } -)) (? (mac)) (\ w {2} -) {5} \ w {2} | (\ d {1,3} \.?) {4} "

конструкція (? ^ \ w {2} -) перевіряє, що на початку рядка йдуть 2 символи і дефіс (початок Mac-адреси) і поміщає результат в групу mac. Якщо група є, то вираз (\ w {2} -) {5} \ w {2} шукає повний Mac-адресу, інакше використовується вираз (\ d {1,3} \.?) {4} для пошуку IP-адреси.



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

"1A-2B-3C-4D-5E-6F" -match «(^ \ w {2} -)) (? (1)) (\ w {2} -) {5} \ w {2} | (\ d {1,3} \.?) {4} "



Позиційна перевірка

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

• Позитивна випереджальна перевірка (? = ...) - має збігтися справа;
• Позитивна ретроспективна перевірка (?

Для прикладу використовуємо позитивну випереджальну перевірку для пошуку слова, праворуч від якого йде слово four:

"one two three four five" -match "(\ b \ w +) \ s (? = four) \ b"

А тепер за допомогою позитивної ретроспективної перевірки знайдемо слово, зліва від якого є слово two:

"one two three four five" -match "(?

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

"unique", "unit", "you" -match "\ b (?! Un) \ w + \ b"



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

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

Для зручності помістимо вміст файлу в змінну $ ip і почнемо пошук. Cначала спробуємо такий вислів:

$ ip | where {$ _ -match "[0-9] * \. [0-9] * \.[0-9] * \. [0-9] * "}

Конструкцію [0-9] можна замінити на \ d, вийде трохи коротше:

$ ip | where {$ _ -match "\ d * \. \ d * \. \ d * \. \ d *"}



На перший погляд ніби все вірно, але в результаті отримуємо не тільки нормальні IP-адреси, а й купу незрозумілих значень. Тут варто згадати про те, що квантіфікатор * означає "будь-яку кількість збігів або відсутність збігів", т.е. у натуральному вираженні \ d * \. \ d * \. \ d * \. \ d * необхідними є тільки точки, все інше необов'язково.

Приберемо необов'язковість, замінивши квантіфікатор * на +, і про всяк випадок зазначимо початок і кінець шуканого рядка:

$ ip | where {$ _ -match "^ \ d + \. \ d + \. \ d + \. \ d + $"}



Зовсім невідповідні рядки типу ...? відсіялися, але все одно в результаті повно сміття.Оскільки кожне число може містити від 1 до 3 цифр, спробуємо більш точно обмежити кількість символів, замінивши квантіфікатор + на {1,3}:

$ ip | where {$ _ -match "^ \ d {1,3} \. \ d {1,3} \. \ d {1,3} \. \ d {1,3} $"}

І ще трохи скоротимо вираз:

$ ip | where {$ _ -match "^ (\ d {1,3} \.) {3} \ d {1,3} $"}

або так:

$ ip | where {$ _ -match "^ (\ d {1,3} \.?) {4} $ "}



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

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

• Якщо число складається з 1 або 2 цифр, то максимальне можливе число це 99, т.е приналежність до діапазону 0-255 перевіряти не потрібно. Перевірити цю умову можна виразом \ d | \ d \ d;
• Якщо число починається з 0 або 1, то воно явно належить до інтервалу 0-199 і теж не потребує перевірки. Підсумовуючи з першою умовою, отримуємо вираз \ d | \ d \ d | [01] \ d \ d;
• Число, що складається з трьох цифр і починається з 2 допустимо в тому випадку, якщо воно не більше 255, отже , якщо друга цифра менше 5, то число правильне, а якщо дорівнює 5, то третя цифра повинна бути менше 6.Висловити це можна як 2 [0-4] \ d | 25 [0-5].

Об'єднавши всі вимоги, отримуємо вираз \ d | \ d \ d | [01] \ d \ d | 2 [0-4] \ d | 25 [0-5]. Скоротимо його, об'єднавши перші три альтернативи, в результаті вийде вираз [01]? \ D \ d? | 2 [0-4] | 25 [0-5], яке описує число від 0 до 255. Залишається зробити висновок його в дужки і підставити у вираз замість \ d {1,3}:

$ ip | where {$ _ -match "^ (([01]? \ d \ d? | 2 [0-4] | 25 [0-5]) \.) {3} ([01]? \ D \ d? | 2 [0-4] | 25 [0-5]) $ "}



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

$ ip | where {$ _ -match "^ (?! (0 + \.?) {4}) (([01]? \ d \ d? | 2 [0-4] | 25 [0-5]) \. ) {3} ([01]? \ d \ d? | 2 [0-4] | 25 [0-5]) $ "}



Ось тепер в результаті тільки валідниє IP-адреси, однак вираз вийшло досить складним.Чи варто прагнути за точністю і ускладнювати вираз або піти на компроміс - все залежить від конкретної ситуації.

Висновок

В в своїх статтях я описав лише базові можливості регулярних виразів. На цьому мої знання вичерпалися