Ура, я всё таки выкроил время для завершающего поста в серии про регулярные выражения 🙂 На этот раз мы выглянем за пределы стандартных командлетов и операторов, и воспользуемся так называемым, «сырым» .NET 🙂 Это не так страшно как может показаться, зато очень полезно, и зачастую просто необходимо.
Работать мы будем с классом .NET, полное имя которого звучит как System.Text.RegularExpressions.Regex
, но к счастью в PowerShell для него есть адаптер-ярлык, позволяющий называть его просто [Regex]
. Давайте рассмотрим этот класс поближе. У него есть несколько интересных нам статических методов. Статические — значит для их вызова не требуется создавать объект класса, а можно вызывать напрямую. Посмотреть список таких методов можно командлетом Get-Member
с ключом -Static
:
PS C:\> [regex] | Get-Member -Static TypeName: System.Text.RegularExpressions.Regex Name MemberType Definition ---- ---------- ---------- CompileToAssembly Method static System.Void CompileToAssembly(System.Text.RegularExpressions.RegexCompilationInf... Equals Method static bool Equals(System.Object objA, System.Object objB) Escape Method static string Escape(string str) IsMatch Method static bool IsMatch(string input, string pattern), static bool IsMatch(string input, st... Match Method static System.Text.RegularExpressions.Match Match(string input, string pattern), static... Matches Method static System.Text.RegularExpressions.MatchCollection Matches(string input, string patt... ReferenceEquals Method static bool ReferenceEquals(System.Object objA, System.Object objB) Replace Method static string Replace(string input, string pattern, string replacement), static string ... Split Method static string[] Split(string input, string pattern), static string[] Split(string input... Unescape Method static string Unescape(string str) CacheSize Property static System.Int32 CacheSize {get;set;}
На всякий случай, напомню основные особенности синтаксиса статических методов .NET:
- Во-первых, для вызова статического метода класса используется конструкция
[класс]::метод()
- Во-вторых, в отличии от параметров командлетов и функций, для передачи параметров методам .NET они помещаются в скобки сразу за именем метода, и разделяются запятой, например:
[класс]::метод(параметр1,параметр2)
- В отличии от «родных» конструкций PowerShell, все .NET сравнения по умолчанию чувствительны к регистру символов.
Теперь можно приступать 🙂 Сначала рассмотрим метод IsMatch
. Он принимает два параметра: строку текста и регулярное выражение с которым сравнивается эта строка. В качестве результата он возвращает либо True
либо False
:
PS C:\> [regex]::IsMatch("123","^\d+
quot;) True PS C:\> [regex]::IsMatch("123","^\d
quot;) False
Всё очень просто, даже проще -match 🙂
Далее возьмем Replace
, у него уже 3 параметра: строка, регулярное выражение, и текст для замены. Ну и возвращает он, как можно догадаться уже измененный текст.
PS C:\> [regex]::replace('321 12 34','\s','-') 321-12-34
В этом методе уже можно использовать группы захвата, и вставлять их значения в текст для замены используя переменные $1 $2 и т.д.
Ну и последний из простых методов, это Split
, он, как нетрудно догадаться принимает в качестве первого параметра текст, а в качестве второго регулярное выражение по которому он будет резать текст на части. Результат соответственно — массив строк:
PS C:\> [regex]::Split("123-45=67+89",'[-=+]') 123 45 67 89
Теперь перейдем к самому интересному — это метод Match. Как и метод IsMatch он принимает как параметры текст, и регулярное выражение. Но вот его результат — сильно отличается. Он возвращает не простой ответ True/False, а целый объект класса System.Text.RegularExpressions.Match, содержащий результаты сравнения.
PS C:\> $match = [regex]::Match("def5abc","(\d).+
quot;) PS C:\> $match Groups : {5abc, 5} Success : True Captures : {5abc} Index : 3 Length : 4 Value : 5abc
Разберём его возможности.
В свойстве Success
содержится булево значение, показывающее было ли сравнение успешным. Index
— показывает номер символа в тексте начиная с которого выражение совпало. Length
— длинна совпавшего текста. Value
– сам совпавший текст (если бы использовался -match, то это значение было бы в $matches[0]
).
В свойстве Groups
содержится коллекция объектов такого же класса Match
, но для каждой группы захвата (включая нулевую):
PS C:\> $match.Groups Groups : {5abc, 5} Success : True Captures : {5abc} Index : 3 Length : 4 Value : 5abc Success : True Captures : {5} Index : 3 Length : 1 Value : 5
Таким образом чтобы собрать вручную аналог переменной $matches
, нужно собрать все значения свойства value
у объектов содержащихся в Groups
, например так:
PS C:\> $myMatches = $match.Groups | select -ExpandProperty value PS C:\> $myMatches 5abc 5 PS C:\> $myMatches[1] 5
Еще у объекта Match
, есть метод NextMatch
, который не требует аргументов, и возвращает следующий объект Match
в данной строке, если такой есть. С его помощью можно реализовать пошаговый перебор всех совпадений, например:
$Line = '<b>abc</b> <b>bca</b> <i>123</i> <b>cab</b>' $Pattern = '<(.)>(.[^<]+)</.>' $Match = [regex]::match($Line,$Pattern) While ($Match.Success) { $line = "Found '" + $Match.Groups[2].value + "' in '" + $Match.Groups[1].value + "'." write-host $line $Match = $Match.NextMatch() }
Впрочем обычно такую задачу можно решить еще проще, с помощью метода [Regex]::Matches
. Он сразу возвращает коллекцию объектов
Match
для всех совпадений в строке. Например получим все ссылки со страницы http://ya.ru
:
$client = New-Object system.net.webclient $text = $client.DownloadString("http://ya.ru") $AllMatches = [regex]::matches($text,'<a [^>]*href="(http://[^"]+)"') $AllMatches | Foreach-Object {$_.groups[1].value}
Вот так вот просто 🙂
Все методы которые я перечислил выше, позволяют указывать еще дополнительный параметр — RegexOptions
. В нём можно задавать всевозможные опции для выполнения сравнения, такие как IgnoreCase
(игнорирование регистра символов) или Compiled
(выражение компилируется при первом сравнении, и последующие выполняются быстрее). Список опций можно посмотреть тут.
Еще маленькая тонкость, не обязательно использовать статические методы и постоянно указывать опции и регулярное выражение. Можно создать экземпляр класса Regex
, задать опции в нём, и использовать уже обычные, не статические методы:
PS C:\> $reg = [regex]'<a [^>]*href="(http://[^"]+)"' PS C:\> $reg.Matches($text) | %{$_.groups[1].value} http://help.yandex.ru/start/ http://mail.yandex.ru http://www.yandex.ru http://www.yandex.ru http://www.artlebedev.ru
или так:
$reg = New-Object regex '<a [^>]*href="(http://[^"]+)"','Compiled,IgnoreCase' PS C:\> $reg.Options IgnoreCase, Compiled PS C:\> $reg.Replace($text,'<a href="localhost"')
Как можно видеть, здесь указывается уже меньше параметров, так как регулярное выражение уже задано в объекте метод которого мы вызываем.
Ну и в качестве бонуса, еще два метода класса [Regex]
— Escape
и Unescape
. Как несложно догадаться они служат для маскировки текстовых строк для их безопасного использования в регулярных выражениях, и обратного преобразования.
PS C:\> [regex]::Escape('C:\Windows\explorer.exe') C:\\Windows\\explorer\.exe
После такого преобразования текст можно спокойно использовать как компонент в регулярных выражениях, не опасаясь что какие то знаки из него будут интерпритированы как спецсимволы. Ну и Unescape
преобразовывает текст обратно:
PS C:\> [regex]::Unescape('C:\\Windows\\explorer\.exe') C:\Windows\explorer.exe
На этом пока всё 🙂
10.4.2010 в 16:05
Спасибо, Василий за интересную серию. Странно, что ты решил остановиться, IMHO, тема неисчерпаема: можно было бы ее продолжать в вялотекущем порядке, а потом издать в виде отдельной книги 😉 (в каждой шутке есть доля шутки, но ты уже опубликовал кучу материалов, теперь добавил серийности, остается ждать от тебя книжку в офлайне)
ЗЫ Исправь очепятку:
PS C:\> $match.Groups
Groups : {5abc, 5}
Success : True
Captures : {5abc}
Index : 3
Length : 4
Value : 1abc
…
Должно быть :
…
Value : 5abc
10.4.2010 в 16:10
Fixed, спасибо.
Я решил остановиться с описанием регулярок 🙂 Практических примеров еще будет, наверняка. А вот книжки — нет. Мой блог не из таких которые можно легко превратить в книжку, потребуется слишком большая обработка, а мне этим заниматься лень. В планах у меня есть попробовать организовать что то вроде вики, с постами из блога — там гораздо гибче со структурой.
10.4.2010 в 19:24
>В планах у меня есть попробовать организовать что то вроде вики, с постами из блога – там гораздо гибче со структурой.
Да, на книгу надо много больше усилий, но ведь за них можно получить компенсацию в виде денежных знаков 😉
Не задумывался над монетизацией своей заслуженной популярности? 😉
Да, идея с Wiki, пожалуй, очень хороша. Wiki позволит упростить поиск и использование того материала, что ты наработал
11.4.2010 в 16:50
Спасибо за интересные статьи по regex в ps, я привел наконец домашнюю библиотеку в порядок.
10.5.2010 в 21:10
[…] Статья была изначально опубликована в блоге https://xaegr.wordpress.com – 1, 2, 3, 4, 5, 6, 7, 8, 9. […]