Многострочный Regex Lookbehind Сбой в Powershell - PullRequest
0 голосов
/ 04 марта 2020

Я пытаюсь разобрать определенный текстовый файл. Одна часть файла:

Installed     HotFix
n/a           Internet Explorer - 0
Applications:

В PowerShell это в настоящее время находится в файле C: \ temp \ software.txt. Я пытаюсь заставить его вернуть все строки между «HotFix» и «Applications:» (как может быть больше в будущем.)

Моя текущая команда выглядит так:

Get-Content -Raw -Path 'C:\temp\software.txt' | Where-Object { $_ -match '(?<=HotFix\n)((.*?\n)+)(?=Applications)' }

Другие регулярные выражения, которые я пробовал:

'(?<=HotFix`n)((.*?`n)+)(?=Applications)'
'(?<=HotFix`n)((.*?\n)+)(?=Applications)'
'(?<=HotFix\n)((.*?`n)+)(?=Applications)'
'(?<=HotFix$)((.*?\n)+)(?=Applications)'
'(?<=HotFix)((.*?\n)+)(?=Applications)'
'(?<=HotFix)((.*?`n)+)(?=Applications)'

Ответы [ 3 ]

0 голосов
/ 04 марта 2020

Если вы читаете файл в строку, следующее регулярное выражение будет читать интересующие строки:

/(?<=HotFix\n).*?(?=\nApplications:)/s

demo

Регулярное выражение читает:

Соответствует нулю или более символов, лениво (?), перед которыми следует строка "HotFix\n", за которой следует строка "\nApplications:".

(?<=HotFix\n) является положительный взгляд за спиной ; (?=\nApplications:) является положительным прогнозом .

Флаг s (/s) заставляет .*? продолжать движение после концов строк. (Некоторые языки имеют разные флаги, которые имеют одинаковый эффект.)

.*? (ленивое совпадение) используется вместо .* (жадное совпадение) в случае, если после нескольких строк "Hot Fix" строка, которая начинается "Applications:". Ленивая версия будет соответствовать первой; жадная версия, последняя.

Я не был бы склонен использовать регулярное выражение для этой задачи. Во-первых, весь файл должен быть прочитан в строку, что может быть проблематично c (по памяти), если файл достаточно большой. Вместо этого я просто прочитал бы файл построчно, сохранив в памяти только текущую строку. После прочтения строки «Bad Fix» сохраните следующие строки, пока не будет прочитана строка «Applications:». Затем, после закрытия файла, все готово.

0 голосов
/ 04 марта 2020

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

Сначала сопоставьте строку, заканчивающуюся HotFix. Затем запишите в группе 1 все следующие строки, которые не начинаются с Приложения, а затем сопоставьте Applications

^.*\bHotFix\r?\n((?:(?!Applications:).*\r?\n)+)Applications:

Объяснение

  • ^.*\bHotFix\r?\n Соответствие строка, заканчивающаяся HotFix
  • ( Захват группа 1
    • (?: Группа без захвата
      • (?!Applications:).*\r?\n Соответствует всей строке, если он не начинается с Applications:
    • )+ Закрыть группу без захвата и повторить 1+ раза для соответствия всем строкам
  • ) Закрыть группу 1
  • Applications: Совпадение буквально

Regex demo

enter image description here

0 голосов
/ 04 марта 2020

Я думаю, Select-String даст лучшие результаты здесь:

((Get-Content -Path 'C:\temp\software.txt' textfile -Raw |
    Select-String -Pattern '(?sm)(?<=HotFix\s*$).*?(?=^Applications:)' -AllMatches).Matches.Value).Trim()

Модификатор Regex s используется, потому что вы ожидаете, что символ . потенциально совпадет с символами новой строки. Модификатор Regex m используется так, что конец строки $ и начало строки ^ могут совпадать в каждой строке. Вместе этот синтаксис (?sm) в PowerShell.

Where {$_ -match ...} вернет все, что делает условие истинным. Поскольку вы передаете вывод Get-Content -Raw, все содержимое файла будет одной строкой и, следовательно, вся строка будет выводиться при условии true.

Поскольку вы использовали -match здесь для одной строки, все удачные совпадения будут сохраняться в переменной $matches automati c. Ваша подходящая строка будет доступна в $matches[0]. Если вы ожидали нескольких совпадений, -match не будет работать так, как здесь скомпоновано.


Кроме того, метод. NET Matches() класса Regex также может выполнять эту работу:

[regex]::Matches((Get-Content 'c:\temp\software.txt' -Raw),'(?sm)(?<=HotFix\s*$).*?(?=^Applications:)').Value.Trim()

Без Trim() вам понадобится понять ситуацию с символом новой строки:

[regex]::Matches((Get-Content software.txt -Raw),'(?m)(?<=HotFix\r?\n?)[^\r\n]+(?=\r?\n?^Applications:)').Value

Альтернатива без регулярного выражения может использовать оператор switch.

switch -File Software.txt -Regex {
    'HotFix\s*$' { $Hotfix,$Applications = $true,$false }
    '^Applications:' { $Applications = $true }
    default {
        if ($Hotfix -and !$Applications) {
            $_
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...