多行正则表达式匹配配置块

时间:2012-09-24 20:09:01

标签: regex powershell

我在尝试匹配文件中的某个配置块(多个配置块)时遇到了一些问题。下面是我试图从配置文件中提取的块:

ap71xx 00-01-23-45-67-89
 use profile PROFILE
 use rf-domain DOMAIN
 hostname ACCESSPOINT
 area inside
!

有多个这样的,每个都有不同的MAC地址。如何跨多行匹配配置块?

4 个答案:

答案 0 :(得分:41)

您可能遇到的第一个问题是,为了匹配多行,您需要将文件的内容作为单个字符串而不是单独的行处理。例如,如果您使用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”将忽略大小写

e.g:

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 cmdlet进行搜索,因为您可以指定-AllMatches并输出所有匹配项,而-match运算符会在第一次匹配后停止。有道理,因为它是一个布尔运算符,只需要确定是否有 a 匹配。

答案 1 :(得分:3)

如果这可能仍然对某人有价值并且根据实际要求,Keith的答案中的正则表达不需要那么复杂。如果用户只想输出每个块,则以下内容就足够了:

$fileContent = [io.file]::ReadAllText("c:\file.txt")
$fileContent |
    Select-String '(?smi)ap71xx[^!]+!' -AllMatches |
    %{ $_.Matches } |
    %{ $_.Value }

正则表达式ap71xx[^!]*!的效果会更好,不建议在正则表达式中使用.*,因为它会产生意外结果。模式[^!]+!将匹配除感叹号之外的任何字符,后跟感叹号。

如果输出中不需要块的开头,则更新的脚本为:

$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 }

答案 2 :(得分:2)

此正则表达式将搜索文本ap,后跟任意数量的字符和以!结尾的换行符:

(?si)(a).+?\!{1}

所以我有点无聊。我编写了一个脚本,它会像你描述的那样分解文本文件(只要它只包含你显示的行)。它可以与其他随机行一起使用,只要它们不包含关键字:ap,profile,domain,hostname或area。它将导入它们,并逐行检查每个属性(MAC,配置文件,域,主机名,区域),并将它们放入可以在以后使用的对象中。我知道这不是你要求的,但是因为我花时间研究它,希望它可以用于一些好处。如果有人有兴趣,这是脚本。它需要根据您的特定需求进行调整:

$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

答案 3 :(得分:0)

这是我的看法。如果不需要正则表达式,则可以使用-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