我在尝试匹配文件中的某个配置块(多个配置块)时遇到了一些问题。下面是我试图从配置文件中提取的块:
ap71xx 00-01-23-45-67-89
use profile PROFILE
use rf-domain DOMAIN
hostname ACCESSPOINT
area inside
!
有多个这样的,每个都有不同的MAC地址。如何跨多行匹配配置块?
答案 0 :(得分:41)
您可能遇到的第一个问题是,为了匹配多行,您需要将文件的内容作为单个字符串而不是单独的行处理。例如,如果您使用Get-Content来读取文件的内容,那么默认情况下它会为您提供一个字符串数组 - 每行一个元素。要跨行匹配,您需要将文件放在一个字符串中(并希望文件不会太大)。你可以这样做:
$fileContent = [io.file]::ReadAllText("C:\file.txt")
或者在PowerShell 3.0中,您可以将Get-Content与-Raw
参数一起使用:
$fileContent = Get-Content c:\file.txt -Raw
然后,您需要指定一个正则表达式选项以匹配行终止符,即
.
匹配任何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