【问题标题】:Extract string from text file via Powershell通过 Powershell 从文本文件中提取字符串
【发布时间】:2019-07-03 12:57:04
【问题描述】:

我一直在尝试使用 PowerShell 从 .txt 文件中的多行中提取某些值。

Host
Class
INCLUDE vmware:/?filter=Displayname Equal "server01" OR Displayname Equal "server02" OR Displayname Equal "server03 test"

这就是我想要的:

server01
server02
server03 test

到目前为止我有代码:

$Regex = [Regex]::new("(?<=Equal)(.*)(?=OR")           
$Match = $Regex.Match($String)

【问题讨论】:

标签: regex powershell


【解决方案1】:

另一种选择 (PSv3+),将 [regex]::Matches() 与 -replace 运算符结合使用以获得简洁的解决方案:

$str = @'
Host
Class
INCLUDE vmware:/?filter=Displayname Equal "server01" OR Displayname Equal "server02" OR Displayname Equal "server03 test"
'@ 

[regex]::Matches($str, '".*?"').Value -replace '"'

正则表达式".*?" 匹配所有"..." 封闭的标记; .Value 提取它们,-replace '"' 去除 " 字符。

这可能并不明显,但根据我的测试,这恰好是此处答案中最快的解决方案 - 见底部。


顺便说一句:如果 -match 运算符(仅查找 a(一个)匹配项)具有一个名为 -matchall 的变体,则上述内容将更加符合 PowerShell 习惯。 ,这样人们就可以写:

# WISHFUL THINKING (as of PowerShell Core 6.2)
$str -matchall '".*?"' -replace '"'

请参阅 GitHub 上的 this feature suggestion。


选读:性能对比

务实地说,这里的所有解决方案都是有帮助的,并且可能足够快,但可能存在必须优化性能的情况。

一般来说,使用Select-String(以及一般的管道)会带来性能损失 - 同时提供优雅和内存高效的流处理处理。

此外,重复调用脚本块(例如,{ $_.Value })往往会很慢 - 特别是在具有 ForEach-Object 或 Where-Object 的管道中,而且 - 在较小程度上 - 使用 .ForEach() 和 @ 987654339@收集方式(PSv4+)。

在正则表达式领域,您为可变长度的后视表达式(例如(?&lt;=EQUAL\s*"))和使用捕获组(例如(.*?))付出了性能损失。

这是使用Time-Command function 进行的性能比较,平均运行 1000 次:

Time-Command -Count 1e3 { [regex]::Matches($str, '".*?"').Value -replace '"' },
   { [regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value} },
   { [regex]::Matches($str, '\"(.*?)\"').Groups.Where({$_.name -eq '1'}).Value },
   { $str | Select-String -Pattern '(?<=Equal\s*")[^"]+' -AllMatches | ForEach-Object{$_.Matches.Value} } |
     Format-Table Factor, Command

我的 MacBook Pro 的计时示例;确切的时间并不重要(您可以删除Format-Table 调用以查看它们),但相对性能反映在Factor 列中,从最快到最慢。

Factor Command
------ -------
1.00   [regex]::Matches($str, '".*?"').Value -replace '"' # this answer
2.85   [regex]::Matches($str, '\"(.*?)\"').Groups.Where({$_.name -eq '1'}).Value # AdminOfThings'
6.07   [regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value} # Wiktor's
8.35   $str | Select-String -Pattern '(?<=Equal\s*")[^"]+' -AllMatches | ForEach-Object{$_.Matches.Value} # LotPings'

【讨论】:

    【解决方案2】:

    你可以使用

    [regex]::matches($String, '(?<=Equal\s*")[^"]+')
    

    请参阅regex demo。

    见more ways to extract multiple matches here。但是,您的主要问题是正则表达式模式。 (?&lt;=Equal\s*")[^"]+ 模式匹配:

    • (?&lt;=Equal\s*") - 一个以 Equal 和 0+ 个空格开头的位置,然后是 "
    • [^"]+ - 消耗 1+ 字符而不是双引号。

    演示:

    $String = "Host`nClass`nINCLUDE vmware:/?filter=Displayname Equal ""server01"" OR Displayname Equal ""server02"" OR Displayname Equal ""server03 test"""
    [regex]::matches($String, '(?<=Equal\s*")[^"]+') | Foreach {$_.Value}
    

    输出:

    server01
    server02
    server03 test
    

    这是一个完整的 sn-p 读取文件,获取所有匹配项并保存到文件:

    $newfile = 'file.txt'
    $file = 'newtext.txt'
    $regex = '(?<=Equal\s*")[^"]+'
    Get-Content $file | 
         Select-String $regex -AllMatches | 
         Select-Object -Expand Matches | 
         ForEach-Object { $_.Value } |
         Set-Content $newfile
    

    【讨论】:

    • 仅供参考,如果匹配项可能跨越多行,请将 Get-Content $file | 替换为 Get-Content $file | Out-String |,或者,如果您使用的是 PowerShell v3 或更新版本,请使用 Get-Content $file -Raw |
    • 如果有多个INCLUDE vmware:/?filter发生了什么?我打开了新问题。 stackoverflow.com/questions/54646160/…
    • @Arbelac 你能解释一下这个问题吗?
    • @Arbelac 试试$regex.matches( $String.Split("`r`n").where({$_.contains('"')})[0] ).groups.where{$_.name -eq 1}.value | sc "c:\temp\result.txt" 或者,将选线条件改成更安全的.where({$_ -match '"[^"]+"'})
    【解决方案3】:

    使用 Wiktor 的优秀 RegEx 直接使用 Select-String 读取文件的替代方法:

    Select-String -Path .\file.txt -Pattern '(?<=Equal\s*")[^"]+' -AllMatches|
        ForEach-Object{$_.Matches.Value} | Set-Content NewFile.txt
    

    样本输出:

    > Get-Content .\NewFile.txt
    server01
    server02
    server03 test
    

    【讨论】:

      【解决方案4】:

      您可以修改您的正则表达式以使用括号中指示的捕获组。反斜杠只是转义引号。这使您可以捕获您正在寻找的内容,然后进一步过滤它。这里的捕获组自动命名为 1,因为我没有提供名称。捕获组 0 是包括引号在内的整个匹配项。我切换到 Matches 方法,因为它包含字符串的所有匹配项,而 Match 只捕获第一个匹配项。

      $regex = [regex]'\"(.*?)\"'    
      $regex.matches($string).groups.where{$_.name -eq 1}.value
      

      如果要导出结果,可以执行以下操作:

      $regex = [regex]'\"(.*?)\"'    
      $regex.matches($string).groups.where{$_.name -eq 1}.value | sc "c:\temp\export.txt"
      

      【讨论】:

      • +1 以获得很好的概括。狡辩:不需要\ -escape "。语法 .where{...} 绝对有效,但对我来说,更详细的形式 .where({...}) 出于概念原因更可取,因此没有人会尝试使用 .where {...}(注意空格),这会中断。顺便说一句:Set-Content 的别名 sc 已从 PowerShell Core 中删除。
      猜你喜欢
      • 2018-04-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 2019-09-28
      相关资源
      最近更新 更多