Регулярные выражения (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$″}
Примечание. Знак дефис (-) внутри класса является метасимволом и обозначает диапазон символов. Если вы хотите указать дефис как обычный символ, то его необходимо поставить в начале выражения, например так [-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$″}
Экранирование
Как уже упоминалось, в регулярных выражениях есть обычные символы и метасимволы. Но иногда бывает необходимо использовать метасимволы в качестве обычных литералов. Сделать это можно, заэкранировав метасимвол с помощью обратного слэша (\).
Например, нам нужно найти все файлы, содержащим инициалы E. в названии. Попробуем сделать так:
Get-ChildItem ′C:\Files\PS Books′ | where {$_.Name -match ″E.″}
Поскольку в данном случае точка является метасимволом, то в результате мы получим файлы, имеющие в своем названии ″букву E, за которой следует один любой символ″. А теперь заэкранируем точку и получим нужный результат:
Get-ChildItem ′C:\Files\PS Books′ | where {$_.Name -match ″E\.″}
Пока все. Продолжение следует 🙂
Давай , еще регулярный выражений! Примеров для сисадминов и вообще практического применения