Регулярные выражения – Switch

февраля 24, 2010 by xaegr

regexp-8 Ну чтож, пора новогодних каникул давно закончилась, и мне стало уже тяжело находить отмазки на вопросы о продолжении серии :) Так как времени с момента предыдущего поста прошло уже немало времени, да и для тех кто натолкнулся на этот пост случайно – вот ссылки на посты которые рекомендуется прочитать сначала: 1, 2, 3, 4, 5, 6, 7.

В последнем выпуске мы познакомились с Select-String, командлетом PowerShell который использует в своей работе регулярные выражения. Сегодня же мы рассмотрим конструкцию Switch, которая тоже может задействовать регекспы, и в результате становится вдвое полезнее :)

Сначала хорошо бы вспомнить что такое вообще switch :) Наверняка вы уже знакомы, но всё же… В switch вы указываете некоторую переменную, и несколько блоков кода с вариантами действий, в зависимости от значения этой переменной. Например:

switch ($n)
{
    1 {Write-Host "Единица"}
    2 {Write-Host "Двойка"}
    3 {Write-Host "Тройка"}
    default {Write-Host "Другое число"}
}

Разумеется на самом деле возможности несколько богаче. К примеру вместо переменной можно указать подвыражение PowerShell, а вместо конкретных вариантов чисел, задать другие скриптблоки:

switch (get-process | where {$_.path -like "c:\windows\*"})
{
    {$_.Handles -gt 300} {"у $($_.Name) слишком много handle'ов"}
    {$_.Handles -le 100} {"у $($_.Name) очень мало handle'ов"}
}

Но и этого команде PowerShell показалось мало, и они добавили несколько дополнительных возможностей. Нас впрочем интерисует лишь одна из них, ключ -regex. Вобщем ничего сложного в нём нет – он добавляется после ключевого слова switch, и перед выражением с данными, и заставляет switch интерпретировать варианты значений как регулярные выражения:

switch -regex (Get-Content C:\Windows\win.ini)
{
    "^\[(.+)\]$" {"Секция '" + $matches[1] + "'"}
    "^([^=]+)=(.*)$" {"Ключ '" + $matches[1] + "' со значением '" + $matches[2] + "'"}
}            

В вышеприведенном примере я указал в качестве источника значений содержимое файла win.ini, а в качестве вариантов – два регулярных выражения. При работе такой конструкции, каждая строчка проверяется на совпадения с каждым регулярным выражением, и в случае совпадения выполняется соответствующий блок кода.

Вот еще один пример, в нём я использую командлет foreach-object для того чтобы передавать в switch элементы для обработки по очереди. Это позволяет начать получать результаты не дожидаясь окончания выполнения команды.

C:\SysInternals\tcpvcon.exe -n | foreach {switch -regex ($_)
    {
        "\[(.+)\] (.+)" {
            if ($Obj) {$Obj}
            $Obj = New-Object PSObject -Property @{Protocol=$Matches[1]; Executable=$Matches[2]}
        }
        "(\S+):\s+(\S.*)$" {
            $Obj | Add-Member noteproperty -Name $Matches[1] -Value $Matches[2]
        }
    }
}            

Иногда вы можете столкнуться с поведением switch которое может показаться странным – для некоторых значений выполняется более одного блока кода:

PS C:\> switch -regex ("word") {
>> "^w" {"$_ starts with letter 'w'"}
>> "^\w+$" {"$_ is word"}
>> }
>>
word starts with letter 'w'
word is word

Дело в том что даже если строчка уже совпала с первым выражением и код выполнился, после этого она будет сравниваться со следующим, и так далее. Чтобы этого не происходило, можно добавить в конце кода ключевое слово break, тогда после выполнения этого блока кода, будет осуществлен выход из switch:

PS C:\> switch -regex ("word") {
>> "^w" {"$_ starts with letter 'w'"; break}
>> "^\w+$" {"$_ starts with another letter"}
>> }
>>
word starts with letter 'w'

Как вы могли убедится, switch очень хорошо подходит для разбора вывода консольных команд и преобразования их в объекты, а так же для разбора конфигурационных файлов. Аналогично его можно использовать и для работы с файлами текстовых логов, особенно в тех случаях когда записи не следуют единому правилу.

На этом пока всё, в следующем посте я постараюсь рассказать о использовании объекта [regex] :)


Курс по PowerShell (6434A) в моём исполнении :)

января 26, 2010 by xaegr

teacher1 10-12 марта, в Москве, в учебном центре Инвента, я буду читать курс по Windows PowerShell. Это официальный Microsoft’овский курс – 6434A – Automating Windows Server 2008 Administration with Windows PowerShell. С его программой можно ознакомиться тут.

Хотя в названии и фигурирует Windows Server 2008 – большая часть информации касается PowerShell вообще, независимо от операционной системы, а Windows Server 2008 используется лишь для демонстрации функционала PowerShell.

Курс подойдет и для людей совсем не знакомых с PowerShell, и для уже обладающих некоторыми знаниями. Обучение начинается с установки PowerShell и рассмотрения базового функционала. Затем идёт работа с конвейерами, форматированием вывода, и конструкциями контроля выполнения, и т.д. Ближе к концу вы научитесь использовать возможности PowerShell для работы с WMI, ADSI, COM, и .NET на реальных примерах. В курсе много лабораторных работ, что очень важно для хорошего усвоения материала.

Внимание, бонус! Курс не ограничивается программой курса :) Если мы будем укладываться по времени – можно пытать меня всевозможными вопросами, я с удовольствием расскажу вам всё что знаю о PowerShell, лишь бы это было интересно и другим слушателям :)

Продолжительность курса – 3 дня, с 9 утра, до 17 вечера.

Стоимость – 13500 руб.

Записаться можно тут. И лучше сделать это пораньше, так как количество мест ограничено.

До встречи :)


SharePoint User Group

января 12, 2010 by xaegr

Маленькая такая “новость” :) 22 января я прочитаю доклад о PowerShell в московской юзергруппе SharePoint. Там будет краткое знакомство с PowerShell для разработчиков, покажу несколько примеров работы из PowerShell с SharePoint, ну и конечно можно будет попытать меня вопросами :)

 

Объявление о встрече на сайте юзергруппы.

Время проведения: 22 января, сбор в 18:30, начало первого доклада в 19:00.

Место проведения: Офис Microsoft:

Проезд до ст. метро «Крылатское»: 1-й вагон из центра, в переходе направо и на улице еще раз направо. Сразу у выхода (напротив д. 10 по Осеннему бульвару ) остановка маршруток с надписью «Krylatsky Hills».

Бесплатный рейсовый микроавтобус до бизнес-центра «Крылатские холмы» регулярно курсирует от метро. Дорога до бизнес-центра занимает порядка 5–7 минут. Автобусы следуют без остановки. Время работы автобуса — с 8:00 до 22:00.

Офис «Microsoft Россия» расположен в корп. 1 (самое левое здание).

Карта.

Регистрироваться на встречу можно здесь, участие бесплатное.


Интересные примеры на PowerShell

января 10, 2010 by xaegr

perl Недавно натолкнулся вот на этот пост. В нём приведено несколько интересных примеров того “как в Perl одна-две строчки кода могут сделать больше, чем десять строк в каком-нибудь другом языке программирования” ;) Мне показалось что будет интересно сравнить как эти примеры выглядели бы на PowerShell ;) И главное – это может быть полезно тем кто уже знает Perl но сейчас изучает PS.

Как известно PowerShell очень молодой язык, и разумеется он унаследовал множество элементов других языков, и следовательно местами схож с многими из них. Я часто слышу о коде PowerShell фразы типа “О, да они же украли PHP!”, “Это же C# с более простым синтаксисом”. Но по-моему больше всего PowerShell похож на Perl. Это и не удивительно – Perl был одним из любимых языков авторов PS, и это здорово – многим хорошим особенностям в PS мы обязанны именно Perl’у.

Hats off to superstar Larry Wall and Perl, very few people and technologies that have had the level of (positive :-) ) impact these 2 have had on the industry.  The world is a better place because that guy was born!

Jeffrey Snover


Это была отмазка ;) Теперь перейдем к коду :)

Сразу скажу – мне кажется это просто удачная подборка попалась, многие примеры удалось записать на PS сильно короче, и главное – понятнее (субъективно конечно :) ). В очень многих областях Perl легко даст фору PowerShell’у в плане компактности кода, может быть даже и некоторые из этих примеров на Perl можно записать гораздо более красиво, так что буду рад если знающие Perl люди оставят свои конструктивные комментарии :)

Я цитирую только описания, код на Perl можно посмотреть в оригинальном посте.

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

#Встроенный оператор -contains
$array -contains $element            

2. Удалить из массива @arr элементы, которые есть в массиве @skip.

#Вопросительный знак - алиас для where
$arr | ? {$skip -notcontains $_}

Вместо пункта 3 я написал красивый (субъективно разумеется ;) ) фильтр:

filter Replace-Words
{
    foreach ($arg in $args) {
        $pair = $arg.split("=",2) #Разрезаем аргумент на 2 части по знаку 
        $_ = $_ -replace $pair[0],$pair[1] #Заменяем вхождения в строке
    }
    $_ #Выдаём результирующую строку
}

Использовать например так:

$text = Get-Content .\test1.txt
$text | Replace-Words плохое=хорошее яблоки=груши | Set-Content .\test1.txt

4. Вывести список имен файлов и каталогов в заданной директории, отсортированный по дате последнего доступа. Обычно глобы сортируют список по имени файлов и каталогов. Для сортировки по дате последнего изменения, заменить цифру 8 на 9.

В PS для сортировки по дате изменения надо заменить не 8 на 9, а LastAccessTime на LastWriteTime ;)

ls | sort lastAccessTime

5. Удалить повторяющиеся элементы в массиве.

$arr | select -Unique

6. Перемешать элементы массива

$arr | sort {Get-Random}

Командлет Get-Random появился только в PS 2.0, в 1.0 можно сделать так:

$r = New-Object random
$arr | sort {$r.next()}

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

$arr | Get-Random

8. Аналог PHP функции urlencode.

[System.Uri]::EscapeUriString(http://проверка)

Регулярные выражения – Select-String

декабря 28, 2009 by xaegr

regexp-7 Итак, подведём итоги этого года :) В области регулярных выражений разумеется :) Мы успели познакомится с основами, отрицательными группами и якорями, квантификаторами, группами захвата, операторами –replace и –split, а так же с концепцией “жадности”. Пришло время познакомится с целым командлетом 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: во всех файлах *.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. Он позволяет вывести не только совпавшую строку, но еще и указанное количество строк до неё и после неё. В следующем примере выводится 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. Их названия говорят сами за себя, поэтому не буду показывать примеры для каждого.

Продолжение следует, но уже в следующем году :)

Пользуясь случаем хочу пожелать всем читателям моего блога успехов в следующем году. Чтобы скрипты экономили вам еще больше времени принося больше дохода, и доставляя всё больше удовольствия от работы. Ну и просто счастья и здоровья конечно :) До встречи в новом году!


Регулярные выражения – Жадность

декабря 24, 2009 by xaegr

regexp-6 Просто отличное название для очередной статьи о регулярных выражениях в блоге посвященном PowerShell :) Но оно действительно подходит лучше всего. Сегодня мы поговорим об одной важной концепции регулярных выражений. От чего зависит сколько символов будет захвачено количественным модификатором с варьирующейся длинной? Именно от жадности :) Если вы наткнулись на пост случайно, то сначала лучше ознакомьтесь с предыдущими постами серии – 1,2,3,4,5.

По умолчанию, все количественные модификаторы в регулярных выражениях – жадные. То есть они пытаются захватить как можно больше символов (разумеется пока это позволяют условия). Взять к примеру .+ Это выражение означает 1 или более вхождений, но разве оно остановится на одном вхождении? Нет! Оно будет жрать захватывать символы пока у него будет эта возможность, то есть до ограничителя если он есть, а если его нет – то до конца (или начала) строки. Например:

PS C:\> "Очень вкусная булка." -replace "б\S+"
Очень вкусная

Заметьте, захавал всё, и даже точкой не подавился :) А что если мы допускаем любое количество повторений группы, но хотим ограничиться минимумом? Тогда нам поможет "нежадная" версия этого количественного модификатора:

PS C:\> "Очень вкусная булка." -replace "б\S+?"
Очень вкусная лка.

Да, просто добавив после этого количественного модификатора вопросительный знак, мы сразу заставили его ограничится минимумом – одним символом. Нежадные версии других количественных модификаторов получаются таким же образом: *? ?? {1,5}?

Разумеется нежадный модификатор может захватить и больше, но только если у него не останется другого выбора:

PS C:\> "Очень вкусная булка." -replace "б\S+?\."
Очень вкусная

В этом выражении оговаривается что \S+? должен захватить минимум, но до следующей точки.

Маскировка служебных символов в регулярных выражениях, в отличии от других строк PowerShell, делается с помощью символа \ Например

\. Точка
\( Открывающая скобка
\\ Обратный слеш

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

И еще один пример полезности нежадных квантификаторов:

PS C:\> "Теги <123> надо удалить <456>." -replace '<.+>'
Теги .
PS C:\> "Теги <123> надо удалить <456>." -replace '<.+?>'
Теги  надо удалить .

Впрочем… конкретно в этой ситуации можно поступить и иначе:

PS C:\> "Теги <123> надо удалить <456>." -replace '<[^>]+>'
Теги  надо удалить .

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

Разумеется подобные "нежадные" модификаторы можно использовать не только в -replace:

PS C:\> "Число 123." -match '\d+'
True
PS C:\> $matches

Name                           Value
----                           -----
0                              123

PS C:\> "Число 123." -match '\d+?'
True
PS C:\> $matches

Name                           Value
----                           -----
0                              1

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

PS C:\Windows\system32> if ('123456' -match '^(\d+)(\d+)$') {$matches}

Name                           Value
----                           -----
2                              6
1                              12345
0                              123456

Тут первый \d+ захватил максимум цифр, оставив второму лишь минимально необходимое для него – одну. Если же использовать нежадные версии, то первый квантификатор постарается захватить минимум, а уж всё остальное придётся захватывать второму:

PS C:\Windows\system32> if ('123456' -match '^(\d+?)(\d+?)$') {$matches}

Name                           Value
----                           -----
2                              23456
1                              1
0                              123456

 

Продолжение: Select-String


Регулярные выражения – Операторы -replace и -split

декабря 18, 2009 by xaegr

regexp-5 Сначала, как обычно, список предыдущих постов из серии про регулярные выражения, для тех кто случано наткнулся на эту страничку. Начать лучше именно с них:

  • Введение
  • Отрицательные группы и якоря
  • Количественные модификаторы (квантификаторы)
  • Группы захвата

    Сегодня хорошая новость для тех кому уже поднадоел оператор –match в предыдущих статьях о регулярных выражениях :) Мы уже научились не просто проверять строки на соответствие правилам, но и извлекать из них самое интересное содержимое. Но регулярные выражения позволяют не только анализировать строки, но и изменять их. Так что пора познакомится познакомиться с еще одним оператором PowerShell, который использует в своей работе регулярные выражения – -replace. Слева от этого оператора указывается обрабатываемая строка, а справа массив состоящий из двух элементов: первый – регулярное выражение определяющее что заменяем, и второй элемент – строка на которую заменяем.

    Давайте посмотрим пример:

    PS C:\> "PowerShell" -replace "Power","Super"
    SuperShell

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

    PS C:\> "PowerShell" -replace "S.+$", "GUI"
    PowerGUI

    Оператор -replace замещает всю совпавшую часть выражения, то есть то что в $matches находилось бы под индексом 0. Впрочем в -replace можно использовать и группы захвата. Вместо помещения в $matches, захваченные данные подставляются во второй элемент массива (то на что заменяем) с помощью символа доллара, и индекса группы, например $0 – всё совпавшее выражение, $1 – содержимое первой группы, $2 – второй, и так далее. Обратите внимание, не только в регулярных выражениях символ $ имеет дополнительные значения (якорь конца строки), но и в самом PowerShell. Так что если просто вставить $1 в строку окруженную двойными кавычками, то PowerShell попытается подставить туда значение переменной $1, а так как такой скорее всего не существует, то он просто заменит эту последовательность на пустоту. Чтобы этого не произошло, а $1 был бы обработан оператором -replace, следует использовать одинарные кавычки (внутри которых PowerShell не раскрывает переменные):

    PS C:\> "Латинские буквы, например ABCDE, надо подчеркнуть." -replace "[a-z]+", '_$0_'
    Латинские буквы, например _ABCDE_, надо подчеркнуть.
    
    PS C:\> "Жирный <b>текст</b> выделяется <b>соответствующим</b> тегом." -replace "<([^<]+)>",'[$1]'
    Жирный [b]текст[/b] выделяется [b]соответствующим[/b] тегом.

    У оператора –replace, как и у других операторов PowerShell для работы со строками, есть и версия отличающая верхний и нижний регистр символов:

    PS C:\> "Все слова начинающиеся с Заглавных Букв надо выделить." -creplace "[А-Я]\S+",'*$0*'
    *Все* слова начинающиеся с *Заглавных* *Букв* надо выделить.

    Не все об этом знают, но второй элемент массива справа от -replace необязательный. Если его опустить, то команда будет выполнять замену на "" (пустоту). Именно поэтому и нет оператора -remove :) Например уберём из текста всё кроме цифр:

    PS C:\> "+7 911 123-45-67" -replace "\D"
    79111234567

    Ну и раз уж взялись за номера телефонов, отформатируем эту последовательность в нужном нам формате :)

    PS C:\> "79111234567" -replace '^(.)(...)(...)(..)(..)$','+$1 ($2) $3-$4-$5'
    +7 (911) 123-45-67

    Другой оператор, похожий на -replace, это -split. Он появился только во второй версии PowerShell. В отличии от -replace, он разделяет строку на части и возвращает массив строк. Справа от него указывается регулярное выражение по которому он будет делить строку. В следующем примере это пробел или знак минуса:

    PS C:\> "+7 911 123-45-67" -split "[- ]"
    +7
    911
    123
    45
    67
    
    PS C:\> "Хакерам^не-нужны*пробелы" -split '\W'
    Хакерам
    не
    нужны
    пробелы

    Еще ему можно указать максимальное количество частей в результате:

    PS C:\> "+7 911 123-45-67" -split "[- ]", 3
    +7
    911
    123-45-67

    То есть, после достижения указанного количества результатов, дальнейшее деление не происходит.

    Продолжение: Жадность.


  • Регулярные выражения – Группы захвата

    декабря 11, 2009 by xaegr

    regexp-4 Нет, сегодня речь не пойдет о вооруженных людях в черных масках ;) Я просто продолжаю свой рассказ об основах регулярных выражениях, и сегодня мы рассмотрим одну из самых интересных и полезных их возможностей.

    Если вы натолкнулись на этот пост случайно, сначала лучше прочитайте предыдущие посты из этой серии:

  • Введение
  • Отрицательные группы и якоря
  • Количественные модификаторы (квантификаторы)

    Теперь, когда мы можем с помощью регулярных выражений описывать и проверять строки по достаточно сложным правилам, пора познакомится с другой не менее важной концепцией регулярных выражений – "группами захвата" (capture groups). Как следует из названия, группы можно использовать для … сюрприз … группировки :) К группам захвата, как и к символам и символьным группам можно применять количественные модификаторы. Например следующее выражение означает "Первая буква в строке – S, затем одна или больше групп состоящих из “знака - (минус) и любого количества цифр за ним” до конца строки":

    PS C:\> "S-1-5-21-1964843605-2840444903-4043112481" -match "^S(-\d+)+$"
    True
    Или:
    PS C:\> "Ноут","Ноутбук","Лептоп" -match "Ноут(бук)?"
    Ноут
    Ноутбук

    Эти примеры показывают как можно использовать группы захвата для группировки, но это вовсе не главное их качество :) Гораздо важнее то, что часть строки подпавшая под подвыражение находящееся внутри такой группы, помещается в специальную переменную – $matches. $Matches это массив, и в нем может находится содержимое нескольких групп. Причем под индексом 0 туда помещается вся совпавшая строка, а лишь начиная с единицы идет содержимое групп захвата. Опять же, лучше посмотреть на примере:

    PS C:\> "At 17:04 Firewall service was stopped." -match "(\d\d:\d\d) (\S+)"
    True
    PS C:\> $matches
    
    Name                           Value
    ----                           -----
    2                              Firewall
    1                              17:04
    0                              17:04 Firewall

    Под индексом 0 находится вся часть строки подпавшая под регулярное выражение, под 1 находится содержимое первых скобок, и под 2 соответственно содержимое вторых скобок. К содержимому $matches можно обращаться как к элементам любого другого массива в PowerShell:

    PS C:\> $matches[1]
    17:04
    PS C:\> $matches[2]
    Firewall

    Если в строке присутствует много групп захвата, то бывает полезно дать им имена, это сильно облегчает дальнейшую работу с полученными данными:

    PS C:\> "At 17:04 Firewall service was stopped." -match "(?<Время>\d\d:\d\d) (?<Служба>\S+)"
    True
    
    PS C:\> $matches
    Name                           Value
    ----                           -----
    Время                          17:04
    Служба                         Firewall
    0                              17:04 Firewall
    
    PS C:\> $matches.Время
    17:04
    PS C:\> $matches["Служба"]
    Firewall

    Регулярное выражение конечно усложнилось, но зато работать с результатами гораздо приятнее. Синтаксис именования следующий:

    (?<Название Группы>подвыражение)

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

    Другой тип группы который часто используется – незахватывающая группа. Она может пригодиться в тех случаях когда вам не нужно захватывать содержимое группы, а надо применить её только для группировки. Например в вышеприведённом примере с SID, такая группа была бы более уместна:

    PS C:\> "S-1-5-21-1964843605-2840444903-4043112481" -match "^S(?:-\d+)+$"
    True
    PS C:\> $matches
    
    Name                           Value
    ----                           -----
    0                              S-1-5-21-1964843605-2840444903-4043112481

    Как вы могли заметить, синтаксис такой группы: (?:подвыражение). Группы можно и вкладывать одну в другую:

    PS C:\> "MAC address is '00-19-D2-73-77-6F'." -match "is '([a-f\d]{2}(?:-[a-f\d]{2}){5})'"
    True
    PS C:\> $matches
    
    Name                           Value
    ----                           -----
    1                              00-19-D2-73-77-6F
    0                              is '00-19-D2-73-77-6F'

    Попробуйте разобрать данное выражение самостоятельно :)

    Продолжение: Операторы -replace и -split


  • Регулярные выражения – Количественные модификаторы (квантификаторы)

    декабря 4, 2009 by xaegr

    regexp-3

    Предыдущие посты из серии:

    Что ж, мы уже немало узнали о символьных группах и якорях. Это неплохое начало, но обычно регулярные выражения гораздо сложнее, и записывать их по одному символу было бы тяжеловато. Что если вам нужно отобрать строки состоящие из четырех символов, каждый из которых может быть буквой от A до F или цифрой? Регулярное выражение могло бы выглядеть примерно так:

     

    PS C:\> "af12","1FE0","1fz1","B009","C1212" -match "^[a-f\d][a-f\d][a-f\d][a-f\d]$"
    af12
    1FE0
    B009

    Не слишком то лаконично, не правда ли? :) К счастью всю эту конструкцию можно значительно сократить. Для этого в регулярных выражениях существует специальная концепция – "количественные модификаторы" (квантификаторы). Эти модификаторы приписываются к любой группе справа, и оговаривают количество вхождений этой группы. Например количественный модификатор {4} означает 4 вхождения. Посмотрим на нашем примере:

    PS C:\> "af12","1FE0","1fz1","B009","C1212" -match "^[a-f\d]{4}$"
    af12
    1FE0
    B009

    Данное регулярное выражение полностью эквивалентно предыдущему – "4 раза по [a-f\d]". Но этот количественный модификатор не обязательно жестко оговаривает количество повторений. Можно например задать количество как "от 4 до 6". Делается это указанием внутри фигурных скобок двух чисел через запятую – минимума и максимума:

    PS C:\> "af12","1FE0","1fA999","B009","C1212","A00062","FF00FF9" -match "^[a-f\d]{4,6}$"
    af12
    1FE0
    1fA999
    B009
    C1212
    A00062

    Если вам безразлично максимальное количество вхождений, например вы хотите указать "3 вхождения или больше", то максимум можно просто опустить (оставив запятую на месте), например "строка состоящая из 3х или более цифр":

    PS C:\> "1","12","123","1234","12345" -match "^\d{3,}$"
    123
    1234
    12345

    Минимальное значение опустить не получится, впрочем можно просто указать единицу:

    PS C:\> "1","12","123","1234","12345" -match "^\d{1,3}$"
    1
    12
    123

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

    + (плюс), эквивалентен {1,} то есть, "одно или больше вхождений"

    * (звездочка), то же самое что и {0,} или если на русском языке – "любое количество вхождений, в том числе и 0"

    ? (вопросительный знак), равен {0,1} – "либо одно вхождение, либо полное отсутствие вхождений".

    Помните, в регулярных выражениях, количественные модификаторы сами по себе использоваться не могут. Для них обязателен символ или символьная группа, которые и будут определять их смысл. Вот несколько примеров:

    .+ Один или более любых символов. Аналог ?* в простых подстановках (как в cmd.exe). Следующее выражение выбирает процессы у которых имя "начинается с буквы S, затем следует 1 или более любых символов, затем снова буква S и сразу после неё конец строки". Иначе говоря "имена которые начинаются и заканчиваются на S":

    PS C:\> Get-Process | where {$_.name -match "^s.+s$"}
    
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        257      14     6540       5220    53     5,97    508 services
         30       2      424        128     5     0,08    280 smss

    \S* Любое количество символов не являющихся пробелами. Помните, подобное выражение может совпасть и с ""(с пустой строкой), ведь под любым количеством подразумевается и ноль, то есть 0 вхождений – тоже результат.

    PS C:\> "abc", "cab", "a c","ac","abdec" -match "a\S*c"
    abc
    ac
    abdec

    Заметьте, строка "ac" тоже совпала, хотя между буквами A и C вообще не было символов. Если заменить * на + то будет иначе:

    PS C:\> "abc", "cab", "a c","ac","abdec" -match "a\S+c"
    abc
    abdec

    бобры? (Это был не вопрос, а регулярное выражение ;) ) Последовательность "бобр", после которой может идти символ "ы", а может и отсутствовать:

    PS C:\> "бобр","бобры","бобрята" -match "^бобры?$"
    бобр
    бобры

    Продолжение: Группы захвата


    Регулярные выражения – Отрицательные группы и якоря

    ноября 26, 2009 by xaegr

    regexp-2Продолжаем разговор о регулярных выражениях. В предыдущем посте я рассказал об основах, а в этом рассмотрим некоторые более “продвинутые” конструкции регулярных выражений.

    Предполагается что вы уже знаете как указать регулярному выр ажению какие символы и/или их последовательности должны быть в строке для совпадения. А что если вам нужно указать не те символы которые должны присутствовать, а те которых не должно быть? То есть если вам нужно вывести лишь согласные буквы, вы можете конечно их перечислить, а можете использовать и отрицательную группу с гласными, например:

    PS C:\> "a","b","c","d","e","f","g","h" -match "[^aoueyi]"
    b
    c
    d
    f
    g
    h

    "Крышка" в качестве первого символа группы символов означает именно отрицание. То есть на месте группы может присутствовать любой символ кроме перечисленных в ней. Для того чтобы включить отрицание в символьных группах (\d, \w, \s), не обязательно заключать их в квадратные скобки, достаточно… перевести их в верхний регистр :) Например \D будет означать "что угодно, кроме цифр", а \S "всё кроме пробелов"

    PS C:\> "a","b","1","c","45" -match "\D"
    a
    b
    c
    PS C:\> "a","-","*","c","&" -match "\W"
    -
    *
    &

    Уже гораздо могущественнее обычных символов подстановки, не так ли? :) А ведь мы только начали изучать основы! Символьные группы позволяют нам указать лишь содержимое одной позиции, один символ находящийся в неопределенном месте строки. А что если нам надо например выбрать все слова которые начинаются с буквы w? если просто поместить эту букву в регулярное выражение, то оно совпадёт для всех строк где w вообще встречается, и не важно – в начале, в середине или в конце строки. В таких случаях на помощь приходят "якоря". Они позволяют производить сравнение начиная с определенной позиции в строке. ^ (крышка) является якорем начала строки, а $ (знак доллара) - обозначает конец строки. Не запутайтесь - ^ как символ отрицания используется лишь в начале группы символов, а вне группы - этот символ является уже якорем :) Да, да, авторам регулярных выражений явно не хватало специальных символов, и они по возможности, использовали их более чем в одном месте (о втором значении $ поговорим позже) :) Впрочем лучше посмотреть на примере:

    PS C:\> Get-Process | where {$_.name -match "^w"}
    
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
         80      10     1460        156    47     0,11    452 wininit
        114       9     2732       1428    55     0,56   3508 winlogon
        162      11     3660       1652    44     0,14   3620 wisptis
        225      20     5076       4308    95    31,33   3800 wisptis
        469      28     9572      11904   101     3,23   1844 wlcrasvc
        706      54    52452      43008   632     9,64   1072 wmdc
        105      10     2308       1428    76     0,08   4056 wuauclt

    Эта команда вывела процессы у которых сразу после начала имени (^) следует символ w. Иначе говоря имя начинается на w. Давайте для усложнения примера, и для упрощения понимания, добавим сюда “крышку” в значении отрицательной группы:

    PS C:\> Get-Process | where {$_.name -match "^w[^l-z]"}
    
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
         80      10     1460        156    47     0,11    452 wininit
        114       9     2732       1428    55     0,56   3508 winlogon
        162      11     3660       1652    44     0,14   3620 wisptis
        225      20     5076       4308    95    31,50   3800 wisptis

    Теперь команда вывела нам процессы у которых имя начинается с символа w, а следующий символ является чем угодно, только не из диапазона l-z.

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

    Ну и для закрепления, опробуем второй якорь – конец строки:

    PS C:\> "Яблоки","Груши","Дыня","Енот","Апельсины","Персик" -match "[ыи]$"
    Яблоки
    Груши
    Апельсины

    Это выражение вывело нам все слова в которых последняя буква И или Ы.

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

    PS C:\> "abc","adc","aef","bca","aeb","abec","abce" -match "^a.[cb]$"
    abc
    adc
    aeb

    Это регулярное выражение выводит все строки которые начинаются с буквы А, за которой следует один любой символ (точка), затем символ C или B и затем конец строки.

    Продолжение: Квантификаторы