Главная

Sunday, 5 April 2020

Select-String и регулярные выражения в Windows PowerShell.

Всем привет.

Часто можно встретить вопросы связанные не столько с самим PowerShell, сколько с применением в нем регулярных выражений. Это и понятно - регулярные выражения (regexp, regular expressions) обладают огромной мощью, и способны сильно упростить жизнь системного администратора или программиста. Однако в мире системного администрирования Windows они мало известны и непопулярны - в cmd.exe практически единственная возможность их применения это утилита findstr.exe, которая обладает очень маленьким функционалом и использует жутко урезанный диалект регулярных выражений. В VBScript функционал регулярных выражений тоже хорошо запрятан, и практически не используется. А вот в PowerShell, авторы языка позаботились о том чтобы регулярные выражения были легко доступны, удобны в использовании и максимально функциональны. Тем более что с последним пунктом всё оказалось достаточно просто - PowerShell использует реализацию регулярных выражений .NET, а она является одной из самых функциональных и производительных, и даже способна потягаться даже с признанным лидером в этой области - perl.

Регулярные выражения это специальный мини-язык служащий для разбора (parsing) текстовых данных. С его помощью можно разделять строки на компоненты, выбирать нужные части строк для дальнейшей обработки, производить замены, и всё это с огромной гибкостью и точностью.

Впрочем знакомство с регулярными выражениями лучше начать не с них, а с более простой технологии которая служит подобным целям, с которой знакомы все Windows администраторы – с подстановочных символов. Наверняка вы не раз выполняли команду dir, и указывали ей в качестве аргумента маску файла, например *.exe. В данном случае звёздочка означает “любое количество любых символов”. Аналогично можно использовать и знак вопроса, он будет означать “один любой символ”, то есть dir ??.exe выведет все файлы с расширением .exe и именем из двух символов. В PowerShell'овской реализации подстановочных символов можно применять и еще одну конструкцию — группы символов. Так например [a-f] будет означать “один любой символ от a до f (a,b,c,d,e,f) ”, а [smw] любую из трех букв (s, m или w). Таким образом команда get-childitem [smw]??.exe выведет файлы с расширением .exe, у которых имя состоит из трех букв, и первая буква либо s, либо m, либо w. Неплохо, неправда ли? Так вот, по сравнению с возможностями регулярных выражения - это пустяки. 


https://nyukers.blogspot.com/2017/12/logparser.html
Супер Select-String.

Не буду сегодня переписывать все подробности применения регулярки в PowerShell. Остановлюсь лишь на командлете PowerShell, который использует регулярные выражения. Таким командлетом является Select-String. Он используется для поиска строк совпадающих с регулярным выражением. Строки для отбора можно передать из массива строк, например:
PS C:\> $lines = Get-Content C:\Windows\setupact.log
PS C:\> $lines | Select-String "error"
dispci.dll:  DispCIOpenDxgKrnlAndDisableNewReferences: D3DKMTOpenAdapterFromDeviceName failed with error 0xc000007a.
[10/24/2009 20:47.16.192] WudfCoInstaller: Final status: error(0) The operation completed successfully.
[10/26/2009 14:45.08.912] WudfCoInstaller: Final status: error(0) The operation completed successfully.
[10/27/2009 18:24.13.032] WudfCoInstaller: Final status: error(0) The operation completed successfully.
[10/27/2009 18:24.14.421] WudfCoInstaller: Final status: error(0) The operation completed successfully.
[11/02/2009 11:32.22.880] WudfCoInstaller: Final status: error(0) The operation completed successfully.
[11/13/2009 15:16.16.837] WudfCoInstaller: Final status: error(0) The operation completed successfully.

Еще можно указывать файлы для проверки содержащихся в них строк, просто указав их путь, или маску (с помощью обычных подстановочных знаков). Так в следующем примере я делаю поиск строки error:<code> во всех файлах <code>*.log в папке c:\Windows: 
PS C:\> Select-String "error:" C:\Windows\*.log
TSSysprep.log:7:sysprep.cpp(314)ERROR: ResetTSPublicPrivateKeys() FAILED: 2
WindowsUpdate.log:2663:2009-10-31    15:11:26:983     896    13fc    PT    WARNING: PTError: 0x80072ee2
WindowsUpdate.log:3926:2009-11-01    19:09:14:748     896    548    PT    WARNING: PTError: 0x8024402c
WindowsUpdate.log:3930:2009-11-01    19:09:14:749     896    548    PT    WARNING: PTError: 0x8024402c
WindowsUpdate.log:3941:2009-11-01    19:09:28:778     896    548    PT    WARNING: PTError: 0x8024402c
WindowsUpdate.log:3945:2009-11-01    19:09:28:778     896    548    PT    WARNING: PTError: 0x8024402c
WindowsUpdate.log:3956:2009-11-01    19:09:42:808     896    548    PT    WARNING: PTError: 0x8024402c
WindowsUpdate.log:3960:2009-11-01    19:09:42:808     896    548    PT    WARNING: PTError: 0x8024402c

Select-String отличается от конструкции where {$_ -match "error:"} тем что выводит не просто совпадения строк, а полноценные объекты содержащие дополнительную информацию. В данном случае были выведены не только совпавшие строки, но и файлы в которых они были найдены (TSSysprep.log и WindowsUpdate.log), и номера строк. Полный список доступных свойств можно посмотреть следующей командой:
PS C:\> Select-String "error:" C:\Windows\*.log |
>> Get-Member -MemberType property

TypeName: Microsoft.PowerShell.Commands.MatchInfo
Name       MemberType Definition
----       ---------- ----------
Context    Property   Microsoft.PowerShell.Commands.MatchInfoContext Context {get;set;}
Filename   Property   System.String Filename {get;}
IgnoreCase Property   System.Boolean IgnoreCase {get;set;}
Line       Property   System.String Line {get;set;}
LineNumber Property   System.Int32 LineNumber {get;set;}
Matches    Property   System.Text.RegularExpressions.Match[] Matches {get;set;}
Path       Property   System.String Path {get;set;}
Pattern    Property   System.String Pattern {get;set;}
Давайте например выведем только имена файлов и номера совпавших строк:
PS C:\> Select-String "error:" C:\Windows\*.log |
>> Format-Table Path, LineNumber -AutoSize
Path                         LineNumber
----                         ----------
C:\Windows\TSSysprep.log              7
C:\Windows\WindowsUpdate.log       2663
C:\Windows\WindowsUpdate.log       3926
C:\Windows\WindowsUpdate.log       3930
C:\Windows\WindowsUpdate.log       3941
C:\Windows\WindowsUpdate.log       3945
C:\Windows\WindowsUpdate.log       3956
C:\Windows\WindowsUpdate.log       3960

Если весь этот «объектный мусор» вам не нужен, вы можете получить только строки, следующей командой:
PS C:\> Select-String "error:" C:\Windows\*.log |
>> Select-Object -ExpandProperty line
sysprep.cpp(314)ERROR: ResetTSPublicPrivateKeys() FAILED: 2
2009-10-31      15:11:26:983     896    13fc    PT      WARNING: PTError: 0x80072ee2
2009-11-01      19:09:14:748     896    548     PT      WARNING: PTError: 0x8024402c
2009-11-01      19:09:14:749     896    548     PT      WARNING: PTError: 0x8024402c
2009-11-01      19:09:28:778     896    548     PT      WARNING: PTError: 0x8024402c
2009-11-01      19:09:28:778     896    548     PT      WARNING: PTError: 0x8024402c
2009-11-01      19:09:42:808     896    548     PT      WARNING: PTError: 0x8024402c
2009-11-01      19:09:42:808     896    548     PT      WARNING: PTError: 0x8024402c

У Select-String есть и несколько дополнительных возможностей. Так если вам не интересно знать какие строки совпали, а лишь необходимо выяснить были ли совпадения вообще, воспользуйтесь ключом -Quiet:
PS C:\> netsh advfirewall firewall show rule "Remote Desktop (TCP-In)" |
>> select-string "Enabled:\s+Yes" -Quiet
True

Эта команда проверяет, содержится ли в выводе netsh строка совпадающая с Enabled:\s+Yes и если содержится, то выводит значение $True. Разумеется тут тоже можно указывать напрямую имя файла или несколько с помощью подстановочных символов, тогда True будет выдано в случае если хотя бы один из файлов содержит указанную строку.

Параметр -List говорит Select-String что нужно найти лишь по одному совпадению на каждый файл. Это может быть полезно если вам надо найти все файлы содержащие определенную строку:
PS C:\> Select-String "error:" C:\Windows\*.log -List |
>> select -ExpandProperty path
C:\Windows\TSSysprep.log
C:\Windows\WindowsUpdate.log

В PowerShell 2.0 у Select-String появился еще один очень полезный ключ  -Context. Который мне особенно нравится по аналогии с LogParser. Он позволяет вывести не только совпавшую строку, но еще и указанное количество строк до неё и после неё. В следующем примере выводится 3 строки предшествующих совпадению и одна после него:
PS C:\> Select-String "error:" C:\Windows\TSSysprep.log -Context 3,1
  Windows\TSSysprep.log:4:*******Version:Major=6, Minor=1, Build=7600, PlatForm=2, CSDVer=, Free
  Windows\TSSysprep.log:5:
  Windows\TSSysprep.log:6:sysprep.cpp(309)Entering RCMSysPrepRestore
> Windows\TSSysprep.log:7:sysprep.cpp(314)ERROR: ResetTSPublicPrivateKeys() FAILED: 2
  Windows\TSSysprep.log:8:sysprep.cpp(316)Leaving RCMSysPrepRestore

Непосредственно совпавшая строка помечается с помощью символа > в начале строки. Если указать в качестве аргумента не массив из двух элементов, а просто число, то будет выведено указанное количество строк с обоих сторон от совпадения:
PS C:\> netsh advfirewall firewall show rule "Remote Desktop (TCP-In)" |
>> select-string "Enabled:" -Context 2
  Rule Name:                            Remote Desktop (TCP-In)
  ----------------------------------------------------------------------
> Enabled:                              Yes
  Direction:                            In
  Profiles:                             Domain,Private,Public

В Select-String тоже можно использовать группы захвата, хотя получить их содержимое несколько сложнее. Дело в том что тут не используется специальная переменная $Matches, а вместо неё результаты совпадения, в виде объекта System.Text.RegularExpressions.Match помещаются в свойство Matches результирующего объекта. Подробнее устройство этого объекта мы рассмотрим позднее, когда будем изучать класс [Regex], а пока я просто покажу как же можно получить например значение первой группы захвата:
PS C:\> Select-String "error: (\S+)" C:\Windows\*.log |
>> Format-table path,linenumber,{$_.Matches[0].groups[1].value}
Path                         LineNumber $_.Matches[0].groups[1].value
----                         ---------- -----------------------------
C:\Windows\TSSysprep.log              7 ResetTSPublicPrivateKeys()
C:\Windows\WindowsUpdate.log       2663 0x80072ee2
C:\Windows\WindowsUpdate.log       3926 0x8024402c
C:\Windows\WindowsUpdate.log       3930 0x8024402c
C:\Windows\WindowsUpdate.log       3941 0x8024402c
C:\Windows\WindowsUpdate.log       3945 0x8024402c
C:\Windows\WindowsUpdate.log       3956 0x8024402c
C:\Windows\WindowsUpdate.log       3960 0x8024402c

Другие полезные параметры командлета на которые стоит обратить внимание, это -CaseSensetive, -Encoding и -NotMatch. 

Успехов.


No comments:

Post a Comment

А что вы думаете по этому поводу?