Powershell:在文件中查找一组文本,然后提取该组文本中的特定行

Powershell : Find a group of text in a file then extract a specific line in that group of text

我已经研究了几天了,我正在尝试解析包含如下数据的多个文本文件:

[Cluster1]
    GatewayIp=xx.xxx.xxx.xx
    IpAddress=xx.xxx.xxx.x
    MTU=0000
    NetMask=xxx.xxx.xxx.0
    Port=xxx
    Protocol=xxxx/xxxxx
    Sessions=xxxxxx
    Bands=xxx, xxx, x
    Binding=xxxxx
    GroupNumber=x
    InitQueue=xxxxxx
    Interface=xxxxxx
    Process=xxx
    SupportsCar=No
    SupportsCom=Yes
    SupportsPos=Yes
    SupportsXvd=No

[Cluster2]
    GatewayIp=xx.xxx.xxx.xx
    IpAddress=xx.xxx.xxx.x
    MTU=0000
    NetMask=xxx.xxx.xxx.0
    Port=xxx
    Protocol=xxxx/xxxxx
    Sessions=xxxxxx
    Bands=xxx, xxx, x
    Binding=xxxxx
    GroupNumber=x
    InitQueue=xxxxxx
    Interface=xxxxxx
    Process=xxx
    SupportsCar=No
    SupportsCom=No
    SupportsPos=No
    SupportsXvd=Yes

我想在存在这些行的部分中提取“IpAddress”:

    SupportsCom=Yes
    SupportsPos=Yes

问题是,我尝试使用 -context 来获取节名“[Cluster1]”之后的第 n 行,但是该节名因文件而异...

$ip = Select-String -Path "$location" -Pattern "\[Cluster1\]" -Context 0,2 |
    Foreach-Object {$_.Context.PostContext}

我试过在 SupportsCom=Yes 之前使用 Precontext 抓取第 N 行,但是“IpAddress=”的行位置因文件而异...

$ip = Select-String -Path "$location" -Pattern "    SupportsCom=Yes" -Context 14,0 |
    Foreach-Object { $_.Line,$_.Context.PreContext[0].Trim()}

有没有办法获取包含“SupportsCom=Yes”的部分,知道该部分由上下空行分隔,然后在该部分中搜索包含“IpAddress=”的字符串,然后 return “=”后面的值 ?

好的,因为不允许您使用模块(也许以后......),这应该可以满足您的需求

# change the extension in the Filter to match that of your files
$configFiles = Get-ChildItem -Path 'X:\somewhere' -Filter '*.ini' -File

$result = foreach ($file in $configFiles) {
    # initialize these variables to $null
    $IpAddress = $supportsCom = $supportsPos = $null
    # loop through the file line by line and try regex matches on them
    switch -Regex -File $file {
        '^\[([^\]]+)]' { 
            # did we get all wanted entries from the previous cluster?
            if ($IpAddress -and $supportsCom -and $supportsPos) {
                if ($supportsCom -eq 'Yes' -and $supportsPos -eq 'Yes') {
                    # just output the IpAddress so it gets collected in variable $result
                    $IpAddress
                }
                # reset the variables to $null
                $IpAddress = $supportsCom = $supportsPos = $null
            }
            # start a new cluster
            $cluster = $matches[1]
        }
        '^\s+IpAddress\s*=\s*(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' { $IpAddress = $matches[1]}
        '^\s+SupportsCom\s*=\s*(Yes|No)' { $supportsCom = $matches[1] }
        '^\s+SupportsPos\s*=\s*(Yes|No)' { $supportsPos = $matches[1]}
    }
}

# show results on screen
$result

# or save as text file
$result | Set-Content -Path 'X:\somewhere\IpAddresses.txt'

更新后的答案:

如果您不关心 IpAddress 所在部分的名称,您可以使用此“one-liner”(为了便于阅读,分成多行) :

$ip = (Get-Content $location -Raw) -split '\[.+?\]' | 
    ConvertFrom-StringData | 
    Where-Object { $_.SupportsCom -eq 'Yes' -and $_.SupportsPos -eq 'Yes' } | 
    ForEach-Object IpAddress 
  • Get-Content 行将输入文件作为单个 multi-line 字符串读取,并且 splits 它位于 headers 部分(例如 [Cluster1])。
  • ConvertFrom-StringDataKey = Value 行转换为每节 hashtable 行。
  • 对于每个哈希表,Where-Object 检查它是否包含 SupportsCom=YesSupportsPos=Yes
  • ForEach-Object IpAddress 是 shorthand 用于编写 Select-Object -ExpandProperty IpAddress 它为您提供 IpAddress 的实际值而不是包含名为 [= 的成员的 object 13=].
  • 请注意,$ip 可以是单个字符串值或字符串数​​组(如果有多个匹配部分)。

原回答:

您还可以编写一个 general-purpose 函数,将 INI 部分转换为 objects。这使您能够通过简单的 Where-Object 语句使用管道来获取您感兴趣的数据。

将 INI 部分输出为 objects 的通用函数,一个接一个:

Function Read-IniObjects {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory, ValueFromPipeline)] [String] $Path
    )

    process {
        $section = @{}   # A hashtable that stores all properties of the currently processed INI section.

        # Read file line by line and match each line by the given regular expressions.
        switch -File $Path -RegEx { 
            '^\s*\[(.+?)\]\s*$' {               # [SECTION]
                # Output all data of previous section
                if( $section.Count ) { [PSCustomObject] $section }

                # Create new section data
                $section = [ordered] @{ IniSection = $matches[ 1 ] }
            }
            '^\s*(.+?)\s*=\s*(.+?)\s*$' {        # KEY = VALUE
                $key, $value = $matches[ 1..2 ]
                $section.$key = $value
            }
        }
        
        # Output all data of last section
        if( $section.Count ) { [PSCustomObject] $section }
    }
}

用法:

$ip = Read-IniObjects 'test.ini' |
    Where-Object { $_.SupportsCom -eq 'Yes' -and $_.SupportsPos -eq 'Yes' } | 
    ForEach-Object IpAddress

备注:

  • INI文件使用switch语句解析,可以直接使用一个文件作为输入。这比使用 Get-Content 循环要快得多。
  • 由于我们使用的是-RegEx参数,switch语句将文件的每一行与给定的正则表达式进行匹配,仅当当前行匹配时才进入case分支。
  • 获取有关 RegEx 工作原理的详细说明:
  • ForEach-Object IpAddress 是 shorthand 用于编写 Select-Object -ExpandProperty IpAddress 它为您提供 IpAddress 的实际值而不是包含名为 [= 的成员的 object 13=].
  • 请注意,$ip 可以是单个字符串值或字符串数​​组(如果有多个匹配部分)。