Home > Software design >  Powershell : Find a group of text in a file then extract a specific line in that group of text
Powershell : Find a group of text in a file then extract a specific line in that group of text

Time:04-07

I've been on this for few days now, I'm trying to parse multiple text files containing data like this :

[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

I want to extract the "IpAddress" in the section where thoses lines are present :

    SupportsCom=Yes
    SupportsPos=Yes

The thing is, I've tried using -context to grab the nth line after the section name "[Cluster1]", but that section name is different from file to file ...

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

I've tried using the Precontext to grab the Nth line before SupportsCom=Yes, but the line position of "IpAddress=" is different from file to file ...

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

Is there a way to grab the section containing "SupportsCom=Yes" knowing that the section is delimited by a blank line above and below, then search in that section a string that contains "IpAddress=" then return the value afterthe "=" ?

CodePudding user response:

Ok, since you are not allowed to use a module (perhaps later..), this should get you what you want

# 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'

CodePudding user response:

Updated answer:

If you don't care about the name of the section(s), where IpAddress is found in, you can use this "one-liner" (broken into multiple lines for readability):

$ip = (Get-Content $location -Raw) -split '\[. ?\]' | 
    ConvertFrom-StringData | 
    Where-Object { $_.SupportsCom -eq 'Yes' -and $_.SupportsPos -eq 'Yes' } | 
    ForEach-Object IpAddress 
  • The Get-Content line reads the input file as a single multi-line string and splits it at the section headers (e. g. [Cluster1]).
  • ConvertFrom-StringData converts the Key = Value lines into one hashtable per section.
  • For each hashtable, Where-Object checks whether it contains SupportsCom=Yes and SupportsPos=Yes
  • ForEach-Object IpAddress is shorthand for writing Select-Object -ExpandProperty IpAddress which gives you the actual value of IpAddress instead of an object that contains a member named IpAddress.
  • Note that $ip can be either a single string value or an array of strings (if there are multiple matching sections).

Original answer:

You could also write a general-purpose function that converts INI sections into objects. This enables you to use the pipeline with a simple Where-Object statement to get the data you are interested in.

Generic function to output INI sections as objects, one by one:

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

Usage:

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

Notes:

  • The INI file is parsed using the switch statement, which can directly use a file as input. This is much faster than using a Get-Content loop.
  • As we are using -RegEx parameter, the switch statement matches each line of the file to the given regular expressions, entering the case branches only if the current line matches.
  • Get detailed explanation about how the RegEx's work:
    • match lines like [Section] -> RegEx101
    • match lines like Key = Value -> RegEx101
  • ForEach-Object IpAddress is shorthand for writing Select-Object -ExpandProperty IpAddress which gives you the actual value of IpAddress instead of an object that contains a member named IpAddress.
  • Note that $ip can be either a single string value or an array of strings (if there are multiple matching sections).
  • Related