Регулярные выражения — Regex

NET_small Ура, я всё таки выкроил время для завершающего поста в серии про регулярные выражения🙂 На этот раз мы выглянем за пределы стандартных командлетов и операторов, и воспользуемся так называемым, "сырым" .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+$")
True
PS C:\> [regex]::IsMatch("123","^\d$")
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).+$")
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

На этом пока всё🙂

комментариев 5 to “Регулярные выражения — Regex”

  1. shs Says:

    Спасибо, Василий за интересную серию. Странно, что ты решил остановиться, IMHO, тема неисчерпаема: можно было бы ее продолжать в вялотекущем порядке, а потом издать в виде отдельной книги😉 (в каждой шутке есть доля шутки, но ты уже опубликовал кучу материалов, теперь добавил серийности, остается ждать от тебя книжку в офлайне)

    ЗЫ Исправь очепятку:

    PS C:\> $match.Groups

    Groups : {5abc, 5}
    Success : True
    Captures : {5abc}
    Index : 3
    Length : 4
    Value : 1abc

    Должно быть :

    Value : 5abc

  2. Xaegr Says:

    Fixed, спасибо.

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

  3. shs Says:

    >В планах у меня есть попробовать организовать что то вроде вики, с постами из блога – там гораздо гибче со структурой.
    Да, на книгу надо много больше усилий, но ведь за них можно получить компенсацию в виде денежных знаков😉
    Не задумывался над монетизацией своей заслуженной популярности?😉

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

  4. homakov Says:

    Спасибо за интересные статьи по regex в ps, я привел наконец домашнюю библиотеку в порядок.

  5. ITband.ru » Регулярные выражения в Windows PowerShell Says:

    […] Статья была изначально опубликована в блоге https://xaegr.wordpress.com – 1, 2, 3, 4, 5, 6, 7, 8, 9. […]


Обсуждение закрыто.