Заметки о Windows и других программных продуктах Microsoft...

PowerShell и регулярные выражения (часть 1)

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

 

С помощью оператора match можно обрабатывать не только текстовые данные, но и любые другие объекты. В качестве примера получим список служб и выберем те из них, в названии которых присутствует ″event″:

Get-Service | where {$_.Name -match ″event″}

использование оператора match

Регистрозависимость

Зависимость от регистра символов — важный момент, который необходимо учитывать при написании регулярных выражений. В общем случае регулярные выражения регистрозависимы, однако в 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$″}

диапазон символов

 

Примечание. Знак дефис (-) внутри класса является метасимволом и обозначает диапазон символов. Если вы хотите указать дефис как обычный символ, то его необходимо поставить в начале выражения, например так [-a-z], либо экранировать.

Отдельные символы и диапазоны можно комбинировать, например [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$″}

оператор notmatch

Экранирование

Как уже упоминалось, в регулярных выражениях есть обычные символы и метасимволы. Но иногда бывает необходимо использовать метасимволы в качестве обычных литералов. Сделать это можно, заэкранировав метасимвол с помощью обратного слэша (\).

Например, нам нужно найти все файлы, содержащим инициалы E. в названии. Попробуем сделать так:

Get-ChildItem ′C:\Files\PS Books′ | where {$_.Name -match ″E.″}

Поскольку в данном случае точка является метасимволом, то в результате мы получим файлы, имеющие в своем названии ″букву E, за которой следует один любой символ″.  А теперь заэкранируем точку и получим нужный результат:

Get-ChildItem ′C:\Files\PS Books′ | where {$_.Name -match ″E\.″}

экранирование метасимволов

 

Пока все. Продолжение следует 🙂

 
 
Комментарии
Сергей

Давай , еще регулярный выражений! Примеров для сисадминов и вообще практического применения

Леколь

Хм… Очень уж нестрогая формулировка…
————-
«Для примера выведем все файлы в директории, кроме файлов с расширением pdf:
Get-ChildItem ′C:\Files\PS Books′ | where {$_.Name -match ″[^pdf]$″}
————-

По смыслу же этот регэксп работает более широко, а именно:
Выведем все файлы, кроме тех, у которых последний символ расширения
совпадает с любым из символов (p) или (d) или (f).

То есть, конечно, этот регэксп выкинет из вывода файлы с окончанием «.pdf»

Но в компанию выкинутых попадет и немеряное число других
файлов, чо-нить вроде: «.ap», «.arf»,».rad»,».tap»,».xf»,».cd»,».bp»… и т.д.

Согласен, не самый удачный пример. Но в данной конкретной ситуации задачу свою выполняет 🙂

Леколь

«Служенье муз не терпит суеты!»
// А.Пушкин

И этот пост ориентирован на неспешность и строгость формулировок.
Кстати, я и сам (в роли «придираста») в предыдущем посте
лопухнулся и пропустил этот же «прынцыпалный мамэнт».

Вот автор статьи говорит:

«Для примера выведем ВСЕ файлы в директории, кроме файлов с расширением pdf».

Get-ChildItem ′C:\Files\PS Books′ | where {$_.Name -notmatch ″pdf$″}

Минутку! А нельзя ли в регулярном выражении точнее выразить мысль,
что речь пойдет не вообще о последовательности символов «pdf», стоящих в конце имени файла,
а именно о «расширении файлов».
Ведь фраза «расширение файлов» неявно подразумевает точку,
обязательно стоящую перед расширением.

Поэтому приведенное выше очень симпатичное нотматчевое выражение
вычеркнет из вывода не только пдф-фйлы, но и ни в чем неповинные
экзотически именованные файлы, НЕ ИМЕЮЩИЕ расширения pdf.

imya_vrode_pdf
odnako_eto_ne_pdf
tolko_kazhetsya_cho_pdf

Разумеется, я прекрасно понимаю, что автор статьи прекрасно знает, как (двумя символами) реализовать своё обещание, и пропустить через фильтр мои экзотические файлы.

Но об этом не лишне подумать и читателям,
уважительно и доверчиво внимающим очень дельным и толковым пояснениям автора.

Отличный пример. И повод задуматься 🙂

Ответить