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

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

  • комментариев 17 to “Регулярные выражения — Группы захвата”

    1. Регулярные выражения – Количественные модификаторы (квантификаторы) « PowerShell и другие скрипты Says:

      […] Группы захвата Опубликовано в Regular Expressions. Метки: Regular Expressions. […]

    2. Юрий Says:

      Спасибо за интересный материал, о $matches даже не догадывался честно говоря.

      • Xaegr Says:

        Ну догадаться до такого сложновато 🙂 В PS много ньюансов 🙂

    3. shs Says:

      «Первая буква в строке – S, затем одна или больше групп состоящих из (минус и любое количество цифр) до конца строки»:
      Приветы. 😉

      Может быть стои заменить в этой фразе «минус» на «знака минус» (или чего-либо подобного)? Долго не мог въехать в смысл этой фразы — казалось, что в предложении по ошибке пропущенно некое существительное, из которого надо вычесть любое количество цифр 😉

      • Xaegr Says:

        Да, действительно, сейчас заменю и еще в кавычки засуну всё.
        Делитесь такими поправками плиз, я посты писал урывками, там наверняка много описок и бессмыслицы 🙂

    4. Виктор Says:

      Спасибо за статью.
      Тоже долго искал где же сохраняются найденные группы.
      Почему-то ожидал встретить нечто подобное ruby. (Раз уж в Powershell все есть объект). Например
      $result = («foo bar» -match ‘(foo)\s+(bar)’)
      Было бы логично если бы $result был объектом. И тогда сразу
      $result[2] -> строка »bar»
      Или сразу:
      («foo bar» -match ‘(foo)\s+(bar)’)[2]

      P.S. Кстати, исправьте в тексте «…а лишь начиная с еденицы …»
      (едИница)

      • Xaegr Says:

        Виктор, такое тоже есть 🙂 В PowerShell кроме того что всё в объектах, еще важно удобство пользования. И $matches с этой точки зрения лучше и проще. Зато работа напрямую с объектами регулярных выражений даёт больше возможностей, так что обязательно расскажу и о них позже.

    5. Регулярные выражения – Жадность « PowerShell и другие скрипты Says:

      […] Регулярные выражения – Жадность 24.12.2009 — Xaegr Просто отличное название для очередной статьи о регулярных выражениях в блоге посвященном PowerShell Но оно действительно подходит лучше всего. Сегодня мы поговорим об одной важной концепции регулярных выражений. От чего зависит сколько символов будет захвачено количественным модификатором с варьирующейся длинной? Именно от жадности Если вы наткнулись на пост случайно, то сначала лучше ознакомьтесь с предыдущими постами серии – 1,2,3,4,5. […]

    6. Регулярные выражения – Select-String « PowerShell и другие скрипты Says:

      […] отрицательными группами и якорями, квантификаторами, группами захвата, операторами –replace и –split, а так же с концепцией […]

    7. Регулярные выражения – Switch « PowerShell и другие скрипты Says:

      […] посты которые рекомендуется прочитать сначала: 1, 2, 3, 4, 5, 6, […]

    8. shs Says:

      После выполнения «S-1-5-21-1964843605-2840444903-4043112481» -match «^S(-\d+)+$»

      $matches будет иметь следующее содержимое:
      Name Value
      —- ——
      1 -4043112481
      0 S-1-5-21-1964843605-2840444903-4043112481

      а можно ли каким-либо способом добиться того, чтобы сохранить для последующей обработки все найденые для (-\d+) совпадения в строке (т.е. -1, -5, -21, -1964843605, и т.д.)?

      • Xaegr Says:

        Да! Об этом будет в следующем посте из серии 🙂 К сожалению пока не знаю когда до него доберусь 😦 Но он будет точно.

      • Xaegr Says:

        Вот пример если прям срочно 🙂 — https://xaegr.wordpress.com/2009/10/31/extract-webpage-links/

        • shs Says:

          Спасибо! А то я уже весь хелп носом изрыл. А оказывается решили не не заморачиваться и не дублировать то, что есть в .Net (а, вот, .Net мне практически не знаком 😦 ).

          • Xaegr Says:

            Да нет, по сути половина PowerShell — дублирование .NET 🙂 Просто видимо посчитали что такой advanced функционал не в первую очередь надо упрощать. Те гуры которым надо — и через .NET доберутся 🙂

    9. Скрипт поиска файлов-писем (почтовых сообщений) по полю “Subject” (“Тема”) « ShS's Blog Says:

      […] – нежадная группа захвата, которая захватит и сохранит для нас все символы, которые расположены […]


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