Главная Без рубрики, Новое Регулярные выражения в Windows PowerShell
  • Регулярные выражения в Windows PowerShell

    Введение

    regexp-0 Мне достаточно часто задают вопросы связанные не столько с самим 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. Неплохо, неправда ли? Так вот, по сравнению с возможностями регулярных выражения – это ерунда 🙂 Но начнём с малого 🙂

    Для начала изучения мы будем использовать оператор PowerShell -match, который позволяет сравнивать текст слева от него, с регулярным выражением справа. В случае если текст подпадает под регулярное выражение, оператор выдаёт True, иначе – False.

    PS C:\> "PowerShell" -match "Power"
    True

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

    PS C:\> "Shell" -match "Power"
    False
    PS C:\> "PowerShell" -match "rsh"
    True

    Еще одна тонкость: оператор -match по умолчанию не чувствителен к регистру символов (как и другие текстовые операторы в PowerShell), если же вам нужна чувствительность к регистру, используйте -cmatch:

    PS C:\> "PowerShell" -cmatch "rsh"
    False

    В регулярных выражениях можно использовать и группы символов:

    PS C:\> Get-Process | where {$_.name -match "sy[ns]"}
    
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
        165      11     2524       8140    79     0,30   5228 mobsync
        114      10     3436       3028    83    50,14   3404 SynTPEnh
        149      11     2356        492    93     0,06   1592 SynTPStart
        810       0      116        380     6               4 System

    И диапазоны в этих группах:

    PS C:\> "яблоко","апельсин","груша","абрикос" -match "а[а-п]"
    апельсин
    абрикос

    Кстати тут я в левой части оператора -match поместил массив строк, и он соответственно вывел лишь те строки, которые подошли под регулярное выражение.

    Разумеется перечисления символов можно комбинировать, например группа [агдэ-я] будет означать “А или Г или Д или любой символ от Э до Я включительно”. Но гораздо интереснее использовать диапазоны для определения целых классов символов. Например [а-я] будет означать любую букву русского алфавита, а [a-z] английского. Аналогично можно поступать с цифрами – следующая команда выведет все процессы в именах которых встречаются цифры:

    PS C:\> Get-Process | where {$_.name -match "[0-9]"}
    
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
         93      10     1788       2336    70     1,25    548 FlashUtil10c
        158      12     6500       1024    96     0,14   3336 smax4pnp
         30       6      764        160    41     0,02   3920 TabTip32

    Так как эта группа используется достаточно часто, для неё была выделена специальная последовательность – \d (от слова digit). По смыслу она полностью идентична [0-9], но гораздо короче 🙂

    PS C:\> Get-Process | where {$_.name -match "\d"}
    
    Handles  NPM(K)    PM(K)      WS(K) VM(M)   CPU(s)     Id ProcessName
    -------  ------    -----      ----- -----   ------     -- -----------
         93      10     1788       2336    70     1,25    548 FlashUtil10c
        158      12     6500       1024    96     0,14   3336 smax4pnp
         30       6      764        160    41     0,02   3920 TabTip32

    Так же последовательность была выделена для группы “любые буквы любого алфавита, любые цифры, или символ подчеркивания” эта группа обозначается как \w (от word) она примерно эквивалентна конструкции [a-zа-я_0-9]\w еще входят символы других алфавитов которые используются для написания слов).

    Еще вам наверняка встретится другая популярная группа: \s – “пробел, или другой пробельный символ” (например символ табуляции). Сокращение от слова space. В большинстве случаев вы можете обозначать пробел просто как пробел 🙂 но эта конструкция добавляет читабельности регулярному выражению.

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

    Все вышеперечисленные конструкции можно использовать как отдельно, так и в составе групп, например [\s\d] будет соответствовать любой цифре или пробелу. Если вы хотите указать внутри группы символ - (тире/минус) то надо либо экранировать его символом \ (обратный слеш), либо поставить его в начале группы, чтобы он не был случайно истолкован как диапазон:

    PS C:\> "?????","Word","123","-" -match "[-\d]"
    123
    -

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

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

    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 и затем конец строки.

    Квантификаторы

    Что ж, мы уже немало узнали о символьных группах и якорях. Это неплохое начало, но обычно регулярные выражения гораздо сложнее, и записывать их по одному символу было бы тяжеловато. Что если вам нужно отобрать строки состоящие из четырех символов, каждый из которых может быть буквой от 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 "^бобры?$"
    бобр
    бобры

    Группы захвата

    Теперь, когда мы можем с помощью регулярных выражений описывать и проверять строки по достаточно сложным правилам, пора познакомится с другой не менее важной концепцией регулярных выражений – “группами захвата” (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

    Мы уже научились не просто проверять строки на соответствие правилам, но и извлекать из них самое интересное содержимое. Но регулярные выражения позволяют не только анализировать строки, но и изменять их. Так что пора познакомиться с еще одним оператором 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

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

    Жадность

    По умолчанию, все количественные модификаторы в регулярных выражениях – жадные. То есть они пытаются захватить как можно больше символов (разумеется пока это позволяют условия). Взять к примеру .+ Это выражение означает 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

    Пришло время познакомится с целым командлетом 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. Их названия говорят сами за себя, поэтому не буду показывать примеры для каждого.

    Switch

    Давайте теперь рассмотрим конструкцию 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

    Ну и напоследок выглянем за пределы стандартных командлетов и операторов, и воспользуемся для работы с регулярными выражениями, так называемым, “сырым” .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

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

    Дальнейшие действия

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

    reg reg2

    Mastering Regular Expressions содержит много информации о механизмах работы регулярных выражений, их реализациях (Perl, Java, .NET и т.д.), возможностях оптимизации и подходах к решению разных сложных задач. Большая часть информации в этой книге предназначена скорее для разработчиков, которые намерены использовать регулярные выражения в своих программах или для тех кто хочет узнать максимум по этой теме. К сожалению не могу рекомендовать русский перевод этой книги – я встретил там несколько опечаток, в том числе и в тексте регулярных выражений.

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

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

    Статья была изначально опубликована в блоге http://xaegr.wordpress.com1, 2, 3, 4, 5, 6, 7, 8, 9.

Комментарии

  1. Отличная статья! Вася, ты как всегда крут!

  2. Спасибо, я очень старался 🙂

  3. Отлично!
    Спасибо за статью!

  4. Отличная работа!

  5. PS C:> “Жирный текст выделяется соответствующим тегом.” -replace “<([^”,'[$1]’
    Не совсем понял это выражение. Как я понимаю:
    “знак меньше, затем один или более символ отличающийся от открывающей скобки, затем закрывающая скобка”.
    PS C:> “Жирный текст выделяется >b>соответствующим тегом.” -replace “<([^”,'[$1]’
    Жирный [b]текст[/b] выделяется [>>b]соответствующим[/b] тегом.

    Почему подстрока > не была заменена на [>] в этом случае?

  6. PS C:> "Жирный текст выделяется >b>соответствующим тегом." -replace "<([^",'[$1]'
    Жирный [b]текст[/b] выделяется [>>b]соответствующим[/b] тегом.
    Почему подстрока > не была заменена на [>] в этом случае?

  7. Дмитрий, попробуйте заменить в посте на < и > соответственно, а то я ничего понять не могу 🙂

  8. А блин…. Короче используйте & l t ; и & g t ; но без пробелов 🙂

  9. Вопрос был о жадности операторов – прочитал следующий раздел и понял

  10. PS C:&gt “Жирный &ltb&gtтек>ст&lt/b&gt выделяется &lt&gt&gtb&gtсоответствующим&lt/b&gt тегом.” -replace “&lt([^&lt]+?)&gt”,'[$1]’
    Жирный [b]тек>ст[/b] выделяется [&gt]b&gtсоответствующим[/b] тегом.

    ________________________________________________________________________________________________________________________________________________________________________
    PS C:&gt “Жирный &ltb&gtтек&gtст&lt/b&gt выделяется &lt&gt&gtb&gtсоответствующим&lt/b&gt тегом.” -replace “&lt([^&lt]+)&gt”,'[$1]’
    Жирный [b&gtтек]ст[/b] выделяется [&gt&gtb]соответствующим[/b] тегом.

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

  11. охохох ; забыл теперь
    сделали-бы что-ли авторизацию по OpenID для возможности редактировать свои комментарии

  12. Круто!

  13. Очень интересно!
    Спасибо!

  14. Спасибо огромное!
    Открыл для себя новый мир возможностей!!

  15. космос!

  16. Спасибо, многое для себя прояснил!

    Есть вопрос как использовать -repalce, что-бы удалить всё вокруг этого выражения “[a-z]d{3}”. Например из FP-A544-01 вытащить A544. Или из B225-01 вытащить B225. Пробовал использовать знак отрицания “^”, что-то не получается.

  17. Спасибо, многое для себя прояснил!

    Есть вопрос. Как использовать -repalce, что-бы удалить всё вокруг этого выражения “[a-z]d{3}”. Например из FP-A544-01 вытащить A544. Или из B225-01 вытащить B225. Пробовал использовать знак отрицания “^”, не получается.
    $r = $c -replace ‘ ‘, ‘[a-z]d{3}’ – тоже не получается. Что не так?

  18. Может кто знает, как строку, в которой слова/выражения разделены запятыми, разбить на отдельные строки, в которых будут эти слова/выражения.
    Пример такой строки: “CN=COMP-222,OU=SUBDEPARTMENT-NAME-2,OU=DEPARTMENT-NAME,DC=domain-sample,DC=com” ?

  19. Вопрос снят. Нашел подходящий метод для разбора строки тут http://vam.in.ua/index.php/it/25-ms-powershell/152-powershell-parse-string.html .

  20. $ListEventStr = Get-EventLog -LogName Security | Where-Object {$_.EventID -eq 4624}
    if ($ListEventStr) {
    foreach ($OneStr in $ListEventStr ) {
    $TimeGenerated=$OneStr.TimeGenerated;

    $StrName = $OneStr.Message | select-string “Logon Type:” -context 0,4
    write-host $strName
    }
    Цель, вытащить из вывода Message – только 4 строки после Logon Type:
    Подскажите пожалуйста – что я не так сделал?

  21. Thank you!

  22. […] Jump Start в PowerShell (часть II) Первые шаги для пауэршельшиков Регулярные выражения в Powershell Полезная программа для анализа регулярных […]

  23. Как уже было сказано, для регулярного выражения нет необходимости в точном совпадении, достаточно наличия искомых символов в тексте. Так например слово ?cat? будет найдено не только в ?My