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

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

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 ″^[a-z]{3}$″}

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

 

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

Get-Process | where {$_.ProcessName -match ″^[a-z]{1,4}$″}

квантификаторы, min и max

 

Максимальное значение можно опустить, задав только минимальное, например {3, } означает ″3 и более символов″. Для наиболее общих квантификаторов есть сокращенное написание:

+ (плюс) — один или более символов, эквивалент {1, };
* (звездочка) — любое количество символов или полное их отсутствие, эквивалент {0, };
? (знак вопроса) — один символ или отсутствие символа, эквивалент {0,1}.

Обратите внимание, что, в отличие от подстановочных символов, квантификаторы в регулярных выражениях не используются сами по себе, а всегда применяются к символу или группе, например (.?) — один любой символ, (.*) — любое количество любых символов. Следующей командой выведем процессы с именем, начинающемся и заканчивающиеся на s, между которыми может быть любое количество символов:

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

любое количество любых символов

 

И еще, стоит помнить о необязательности квантификаторов, у которых в качестве минимального значения стоит ноль (напр. ? и *).  Это значит, что регулярные выражения с их использованием для положительного результата не нуждаются хотя-бы в одном совпадении, они совпадают даже при отсутствии символов. Например выражения ″.?″ , ″.*″ или ″[a-z]*″ совпадают с чем угодно, включая пустую строку.

необязательные квантификаторы

Операторы 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″

оператор replace

 

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

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

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

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

оператор split

 

Примечание. Так же как и 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+))\.$″

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

вложенные группы

 

При необходимости группе можно присвоить имя, чтобы было удобнее к ней обращаться. Именованные группы имеют синтаксис (?<name> 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<name> или \k’name’ , где name — имя именованной группы. Такие ссылки однозначно указывают на группу и их невозможно спутать с восьмеричными символами. С их помощью выражение изменится следующим образом:

″Test grouping in regular regular expressions.″ -match ″(?<first>\w+)\s(\k<first>)″

обратные ссылки к именованным группам

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

При использовании захватывающих групп на сохранение их содержимого тратится дополнительное время и ресурсы, что при обработке больших объемов данных может отрицательно сказаться на производительности. Поэтому, если группы нужны исключительно для группировки символов, а в захвате нет необходимости, то можно использовать ″незахватывающие″ группы. Эти группы получаются с помощью переключателя (?: ), например:

″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]}

пример парсинга логов

 

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

 
 
Комментарии

Пока нет комментариев.