Многострочное регулярное выражение для соответствия конфигурационному блоку
У меня возникают некоторые проблемы, связанные с определением определенного блока конфигурации (нескольких) из файла. Ниже приведен блок, который я пытаюсь извлечь из файла конфигурации:
ap71xx 00-01-23-45-67-89
use profile PROFILE
use rf-domain DOMAIN
hostname ACCESSPOINT
area inside
!
Есть несколько таких, как это, каждый с другим MAC-адресом. Как сопоставить конфигурационный блок по нескольким строкам?
Ответы
Ответ 1
Первой проблемой, с которой вы можете столкнуться, является то, что для сопоставления нескольких строк вам необходимо обработать содержимое файла как одну строку, а не отдельную строку. Например, если вы используете Get-Content для чтения содержимого файла, то по умолчанию он предоставит вам массив строк - один элемент для каждой строки. Чтобы сопоставить строки, вы хотите, чтобы файл был в одной строке (и надеемся, что файл не слишком большой). Вы можете сделать это так:
$fileContent = [io.file]::ReadAllText("C:\file.txt")
Или в PowerShell 3.0 вы можете использовать Get-Content с параметром -Raw
:
$fileContent = Get-Content c:\file.txt -Raw
Затем вам нужно указать опцию регулярного выражения для соответствия между терминаторами строк, т.е.
- Режим SingleLine (
.
соответствует любому char, включая строку), а также
- Многострочный режим (
^
и $
соответствует встроенным терминаторам строк), например
-
(?smi)
- обратите внимание, что "i" означает игнорировать регистр
например:.
C:\> $fileContent | Select-String '(?smi)([0-9a-f]{2}(-|\s*$)){6}.*?!' -AllMatches |
Foreach {$_.Matches} | Foreach {$_.Value}
00-01-23-45-67-89
use profile PROFILE
use rf-domain DOMAIN
hostname ACCESSPOINT
area inside
!
00-01-23-45-67-89
use profile PROFILE
use rf-domain DOMAIN
hostname ACCESSPOINT
area inside
!
Используйте командлет Select-String
, чтобы выполнить поиск, потому что вы можете указать -AllMatches
, и он выведет все совпадения, тогда как оператор -match
остановится после первого совпадения. Имеет смысл, потому что это булев оператор, который просто должен определить, есть ли совпадение.
Ответ 2
В случае, если это может быть полезным для кого-то и в зависимости от фактического требования, регулярное выражение в ответе Кейта не должно быть таким сложным. Если пользователь просто хочет вывести каждый блок, будет достаточно:
$fileContent = [io.file]::ReadAllText("c:\file.txt")
$fileContent |
Select-String '(?smi)ap71xx[^!]+!' -AllMatches |
%{ $_.Matches } |
%{ $_.Value }
Регулярное выражение ap71xx[^!]*!
будет работать лучше, а использование .*
в регулярном выражении не рекомендуется, так как оно может генерировать неожиданные результаты. Шаблон [^!]+!
будет соответствовать любому символу, кроме восклицательного знака, за которым следует восклицательный знак.
Если начальный блок не требуется в выводе, обновленный script:
$fileContent |
Select-String '(?smi)ap71xx([^!]+!)' -AllMatches |
%{ $_.Matches } |
%{ $_.Groups[1] } |
%{ $_.Value }
Groups[0]
содержит всю согласованную строку, Groups[1]
будет содержать совпадение строк в круглых скобках в регулярном выражении.
Если $fileContent
не требуется для дальнейшей обработки, переменная может быть устранена:
[io.file]::ReadAllText("c:\file.txt") |
Select-String '(?smi)ap71xx([^!]+!)' -AllMatches |
%{ $_.Matches } |
%{ $_.Groups[1] } |
%{ $_.Value }
Ответ 3
Это регулярное выражение будет искать текст ap
, за которым следует любое количество символов и новые строки, заканчивающиеся на !
:
(?si)(a).+?\!{1}
Так что мне было немного скучно. Я написал script, который разбивает текстовый файл, как вы описали (пока он содержит только строки, которые вы отображали). Он может работать с другими случайными строками, если они не содержат ключевых слов: ap, профиль, домен, имя хоста или область. Он будет импортировать их и проверять строки за строкой для каждого из свойств (MAC, Profile, domain, hostname, area) и поместить их в объект, который может быть использован позже. Я знаю, что это не то, о чем вы просили, но так как я потратил время на это, надеюсь, это может быть полезно для хорошего. Вот script, если кто-то заинтересован. Его необходимо будет настроить под ваши конкретные потребности:
$Lines = Get-Content "c:\test\test.txt"
$varObjs = @()
for ($num = 0; $num -lt $lines.Count; $num =$varLast ) {
#Checks to make sure the line isn't blank or a !. If it is, it skips to next line
if ($Lines[$num] -match "!") {
$varLast++
continue
}
if (([regex]::Match($Lines[$num],"^\s.*$")).success) {
$varLast++
continue
}
$Index = [array]::IndexOf($lines, $lines[$num])
$b=0
$varObj = New-Object System.Object
while ($Lines[$num + $b] -notmatch "!" ) {
#Checks line by line to see what it matches, adds to the $varObj when it finds what it wants.
if ($Lines[$num + $b] -match "ap") { $varObj | Add-Member -MemberType NoteProperty -Name Mac -Value $([regex]::Split($lines[$num + $b],"\s"))[1] }
if ($lines[$num + $b] -match "profile") { $varObj | Add-Member -MemberType NoteProperty -Name Profile -Value $([regex]::Split($lines[$num + $b],"\s"))[3] }
if ($Lines[$num + $b] -match "domain") { $varObj | Add-Member -MemberType NoteProperty -Name rf-domain -Value $([regex]::Split($lines[$num + $b],"\s"))[3] }
if ($Lines[$num + $b] -match "hostname") { $varObj | Add-Member -MemberType NoteProperty -Name hostname -Value $([regex]::Split($lines[$num + $b],"\s"))[2] }
if ($Lines[$num + $b] -match "area") { $varObj | Add-Member -MemberType NoteProperty -Name area -Value $([regex]::Split($lines[$num + $b],"\s"))[2] }
$b ++
} #end While
#Adds the $varObj to $varObjs for future use
$varObjs += $varObj
$varLast = ($b + $Index) + 2
}#End for ($num = 0; $num -lt $lines.Count; $num = $varLast)
#displays the $varObjs
$varObjs
Ответ 4
Вот мой дубль. Если вам не нужно регулярное выражение, вы можете использовать -like или .contains(). Вопрос никогда не говорит, что такое шаблон поиска. Вот пример с текстовым файлом Windows.
$file = (get-content -raw file.txt) -replace "'r" # avoid the line ending issue
$pattern = 'two
three
f.*' -replace "'r"
# just showing what they really are
$file -replace "'r",'\r' -replace "'n",'\n'
$pattern -replace "'r",'\r' -replace "'n",'\n'
$file -match $pattern
$file | select-string $pattern -quiet