PowerShell і регулярні вирази (частина 1).
Регулярні вирази (Regular Expressions, RegExp) - це спеціалізований мова для пошуку і обробки тексту. Основою регулярних виразів є керуючі символи (метасимволи), а саме регулярний вираз по суті є шаблоном, що визначає правила пошуку.
Визначення вийшло не дуже виразне, тому почнемо з більш простого і зрозумілого - символів підстановки (wildcards).Навіть якщо ви не маєте уявлення про регулярні вирази, то напевно використовували для пошуку файлів конструкцію виду * .txt. Тут символ зірочка (*) є метасимволом і означає "будь-яку кількість будь-яких символів", (.txt) - це звичайні (літеральние) символи, а вся конструкція в цілому описує файли з будь-яким ім'ям і розширенням txt.
Регулярні вирази також складаються зі звичайних і метасимволов і працюють за тим же принципом, але мають набагато більше можливостей і, відповідно, більш складний синтаксис. Так попередній приклад (* .txt) за допомогою регекспов буде виглядати так (. * \. Txt).
Регулярні вирази в різній мірі підтримуються в різних мовах \ середовищах програмування і навіть в деяких текстових редакторах.PowerShell базується на .NET, відповідно в ньому доступні практично всі можливості .NET, що стосуються регулярних виразів.
Оператор match
В прикладах ми будемо використовувати оператор match, який є одним з основних інструментів для роботи з регулярними виразами в PowerShell.Він порівнює текст зліва з регулярним виразом справа, і якщо текст підходить під вираз, повертає True, якщо не підходить - False:
"PowerShell" -match "power" -> True
"Shell" -match "power" -> False
Якщо ж оператору згодувати кілька рядків, то він поверне ті, які підходять під заданий вираз:
"Power", "Shell" -match " power "-> Power
Важливо розуміти, що при перевірці регулярного виразу немає необхідності в повному збігу тексту.Для позитивного результату оператору потрібно лише наявність відповідних символів в тексті, наприклад:
"PowerShell" -match "rsh" -> True
За допомогою оператора match можна обробляти не тільки текстові дані, але і будь-які інші об'єкти. Як приклад отримаємо список служб і виберемо ті з них, в назві яких є "event":
Get-Service | where {$ _.Name -match "event"}
чутливі до регістру
Залежність від регістру символів - важливий момент, який необхідно враховувати при написанні регулярних виразів. У загальному випадку регулярні вирази чутливі до регістру, проте в PowerShell за замовчуванням це не так. Наприклад, такий вираз завершиться успішно, не дивлячись на невідповідність в регістрі:
"PowerShell" -match "power" -> True
Якщо необхідно враховувати регістр символів, то можна приставити до оператора порівняння букву С (від Case Sensitive).Одержаний оператор cmatch регістрозавісім, відповідно при його використанні вираз вже не буде відповідати дійсності:
"PowerShell" -cmatch "power" -> False
Крім того, є можливість включати до регістру засобами самих регулярних виразів, де в якості перемикачів використовуються конструкції (? i) і (? -i).При використанні оператора match (? -I) включає залежність від регістра, а (? I) відключає. Наприклад:
"PowerShell" -match "(? -I) power" -> False
"PowerShell" -match "(? I) power" -> True
Плюс використання (? i) і (? -i) в тому, що з їх допомогою можна задавати чутливі до регістру не для всього висловлювання, а для його частини.Наприклад:
"PowerShell" -match "power (? - i) Shell" -> True
"PowerShell" -match "power (? - i) shell" -> False
Використовуючи чутливі до регістру вираження необхідно враховувати, що при зміні регістра результат може дуже сильно відрізнятися. Наприклад, два практично однакових вираження видають абсолютно різні результати:
Get-Service | where {$ _.name -cmatch "event"}
Get-Service | where {$ _. name -cmatch "Event"}
Якорі
Для позитивного результату регулярному виразу потрібно лише наявність відповідних символів, незалежно від їх розташування в тексті. Це поведінка за умовчанням, але його можна змінити за допомогою спеціальних метасимволов, званих якорями (anchors).Якоря дозволяють точно вказати на позицію в рядку, в якій повинні бути шукані символи.
Метасимволи кришка ^ і \ A збігаються з початком рядка, перед першим символом. Для прикладу виведемо список служб, що починаються з символу "b":
Get-Service | where {$ _. name -match "^ b"}
Метасимволи долар $, \ Z або \ z збігаються з закінченням рядка.Для прикладу виведемо список служб, що закінчуються на "s":
Get-Service | where {$ _. name -match "s $"}
Тут може виникнути питання, чи є відмінності між якорями (наприклад між $, \ Z і \ z) і коли який з них краще використовувати. Для цього представимо розбирається за допомогою регулярного виразу об'єкт як текст, що складається з фізичних рядків (string), розділених символом нового рядка (\ n):
string 1 \ n
string 2 \ n
...
string n
За замовчуванням об'єкт обробляється через підрядник, однак існує розширений режим прив'язки до рядків (multiline), в якому однією логічною рядку можуть відповідати кілька фізичних рядків, розділених символом \ n.Залежно від режиму обробки збіг символів може відрізнятися:
^ - збігається з позицією на початку рядка, в розширеному режимі також збігається з позицією після будь-якого символу нового рядка;
\ A - не підтримує розширений режим і завжди відповідає початку рядка;
$ - збігається з позицією в кінці рядка і перед символом нового рядка, завершальним текст, в розширеному режимі збігається з позицією перед будь-яким символом нового рядка;
\ Z - не підтримує розширений режим , збігається з позицією в кінці рядка і перед завершальним символом нового рядка;
\ z - не підтримує розширений режим, збігається з позицією в кінці рядка тільки в тому випадку, якщо перед ним немає символу нового рядка.
Межі
Як вже було сказано, для регулярного виразу немає необхідності в точному збігу, досить наявності шуканих символів в тексті. Так наприклад слово "cat" буде знайдено не тільки в "My cat is fat", але і в "bobcat", "category", "staccato" та інших, які не мають ні найменшого відношення до котячих.Тому, якщо необхідно тільки ціле слово, можна позначити його межі (boundaries) за допомогою метасимвола \ b.
Для прикладу виведемо список файлів в директорії, що містять у назві слово power:
Get -ChildItem 'C: \ Files \ PS Books' | where {$ _. Name -match "power"}
А тепер припустимо, що мені потрібно, щоб слово power в назві присутнє окремо від інших.Змінимо команду, позначивши межі слова:
Get-ChildItem 'C: \ Files \ PS Books' | where {$ _. Name -match "\ bpower \ b"}
Можливий і зворотний випадок, коли необхідно знайти слово не цілком, а саме в складі з іншими - наприклад знайти "cat" в "bobcat", "category" або "staccato", але не в "My cat is fat".Для цього є спеціальний метасимвол \ B, який називається «не межа" (nonboundaries). Візьмемо приклад з файлами і змінимо його таким чином, щоб слово power знайшлося тільки в складі інших слів:
Get-ChildItem 'C: \ Files \ PS Books' | where {$ _. Name -match "\ Bpower \ B"}
Примітка. Про всяк випадок уточню, що метасимволу \ b і \ B зовсім не зобов'язані бути парними.Їх можна використовувати поодинці і в різних поєднаннях, наприклад \ bpower, power \ B або \ bpower \ B.
Класи символів
Приблизно визначившись, де шукати, переходимо до того, що шукати . Для пошуку в регулярних виразах можна використовувати класи символів. Наприклад, символьний клас [ds] збігається з одним будь-яким із зазначених в дужках символів:
Get-Service | where {$ _.Name -match "v [ds] s"}
Символи в класі можна вказувати не по одному, а задавати у вигляді діапазону. Наприклад [e-v] збігається з одним будь-яким символом, що знаходиться в зазначеному діапазоні (від e до v):
Get-Service | where {$ _. Name -match "[e-v] log $"}
Примітка. Знак дефіс (-) усередині класу є метасимволом і позначає діапазон символів.Якщо ви хочете вказати дефіс як звичайний символ, то його необхідно поставити на початку виразу, наприклад так [-az], або екранувати.
Окремі символи і діапазони можна комбінувати, наприклад [adev-z] означає "a або d або e або будь-який символ від v до z ". Подібним чином можна позначати будь-які класи символів, наприклад [a-z] означає будь-який символ латинського алфавіту, а [0-9] відповідно будь-яку цифру.
Для найбільш поширених класів є скорочені позначення:
\ w - word. У цей клас входить будь-яка буква, цифра або нижнє підкреслення, еквівалентно [a-zа-я0-9_];
\ d - digit. У цей клас входить будь-яка цифра, еквівалентно [0-9];
\ s - space. Клас, що співпадає з будь-яким символом пропуску (пробіл, табуляція, новий рядок і т.п.).
Примітка. В клас \ w можуть входити і символи інших алфавітів, в залежності від використовуваного на комп'ютері мови.
Для прикладу виведемо список файлів в директорії, в назві яких присутні цифри:
Get-ChildItem 'C: \ Files \ PS Books' | where {$ _. Name -match "\ d"}
Ну і найбільш загальним символьним класом в регулярних виразах є точка (.), Яка описує один будь-який символ. Для прикладу виведемо службу з ім'ям, що починається на b, що закінчується на s, між якими можуть знаходитися будь-які два символу:
Get-Service | where {$ _. Name -match "^ b..s $"}
Негативні класи
Іноді простіше піти від зворотного, тобтоне перераховувати всі символи, які повинні бути присутніми в рядку, а вказати лише ті, яких там бути не повинно. Зробити це можна за допомогою вже знайомого нам символу кришка (^). Так вираз [^ adf] означає "будь-який символ окрім a, d або f". Кришка, поставлена в якості першого символу в класі, означає заперечення, а сам клас називається негативним (або інвертованим).
Для прикладу виведемо всі файли в директорії, крім файлів з розширенням pdf:
Get-ChildItem 'C: \ Files \ PS Books' | where {$ _. Name -match "[^ pdf] $"}
Для основних класів (\ w, \ d, \ s) є альтернативний варіант заперечення. Для того, щоб включити в них заперечення, досить перевести їх у верхній регістр:
\ W - все крім букви, цифри або нижнього підкреслення, еквівалентно [^ \ s];
\ D - все крім цифр, еквівалентно [^ \ d];
\ S - все крім символів пропуску, еквівалентно [^ \ s].
Оператор notmatch
Ще один спосіб піти від зворотного - це використовувати оператор notmatch, зворотний оператору match. Діє він у такий спосіб - порівнює текст зліва з регулярним виразом справа, і якщо текст не містить символи, зазначені в регулярному виразі, повертає True, інакше False.Так попередній приклад з негативними групами можна переписати таким чином:
Get-ChildItem 'C: \ Files \ PS Books' | where {$ _. Name -notmatch "pdf $"}
Екранування
Як вже згадувалося, в регулярних виразах є звичайні символи і метасимволу. Але іноді буває необхідно використовувати метасимволи в якості звичайних литералов.Зробити це можна, заекранувати метасимвол за допомогою зворотного слеша (\).
Наприклад, нам потрібно знайти всі файли, що містять ініціали E. в назві. Спробуємо зробити так:
Get-ChildItem 'C: \ Files \ PS Books' | where {$ _. Name -match "E."}
Оскільки в даному випадку точка є метасимволом, то в результаті ми отримаємо файли, що мають у своїй назві "букву E, за якою слідує один будь-який символ" .А тепер заекраніруем точку і отримаємо потрібний результат:
Get-ChildItem 'C: \ Files \ PS Books' | where {$ _. Name -match "E \."}
Поки все. Далі буде