markfree
markfree

Reputation: 183

Powershell Get-Content specific content inside text

I receive a text file with a multiple lists like shown below (edit: more accurate example dataset included)

# SYSTEM X
# SINGULAR
192.168.1.3
# SUB-SYSTEM V
192.168.1.4
192.168.1.5
192.168.1.6
# SYSTEM Y
# MANDATORY
192.168.1.7
192.168.1.8
192.168.1.9
192.168.1.7
192.168.1.8
192.168.1.9

Each "SYSTEM comment" means its a new set after it. I want to read each block of content separately so each set should be assigned to an object discarding the embedded comments. I just need the IPs. Something like:

$ipX = get-content -path [file.txt] [set X]
$ipY = get-content -path [file.txt] [set Y]
$ipZ = get-content -path [file.txt] [set Z]

But I'm not sure how to actually assign these sets separately. Help please.

Upvotes: 1

Views: 2965

Answers (4)

Theo
Theo

Reputation: 61068

If I understand the question with the new example correctly, you want to parse out the file and create single variables of that each holding an array ip IP addresses.

If that is the case, you could do:

# loop through the file line-by-line
$result = switch -Regex -File 'D:\Test\thefile.txt' {
    '#\sSYSTEM\s(\w+)' {
        # start a new object, output the earlier object if available
        if ($obj) { $obj }
        $obj = [PsCustomObject]@{ 'System' = $Matches[1]; 'Ip' = @() }
    }
    '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' {
        # looks like an IPv4 address. Add it to the Ip property array of the object
        $obj.Ip += $_
    }
    default {}
}

Now you have an array ob objects in $result:

System Ip                                                     
------ --                                                     
Y      {192.168.1.7, 192.168.1.8, 192.168.1.9, 192.168.1.7...}
X      {192.168.1.3, 192.168.1.4, 192.168.1.5, 192.168.1.6}  

To make separate variables of that is as easy as:

$ipX = ($result | Where-Object { $_.System -eq 'X' }).Ip
$ipY = ($result | Where-Object { $_.System -eq 'Y' }).Ip
$ipZ = ($result | Where-Object { $_.System -eq 'Z' }).Ip

Your example has duplicate IP addresses. If you don't want these do
$ipX = ($result | Where-Object { $_.System -eq 'X' }).Ip | Select-Object -Unique (same for the others)

Upvotes: 0

AdminOfThings
AdminOfThings

Reputation: 25001

You can use Select-String to extract a specific section of text:

# Update $section to be the set you want to target
$section = 'Set Y'
Get-Content a.txt -Raw |
    Select-String -Pattern "# $section.*\r?\n(?s)(.*?)(?=\r?\n# Set|$)" | Foreach-Object 
        {$_.Matches.Groups[1].Value}

Using Get-Content with -Raw reads in the file as a single string making multi-line matching easier. With PowerShell 7, Select-String includes a -Raw switch making this process a bit simpler.

This outputs capture group 1 results, which match the (.*?). If you want to capture between comments rather than between Set <something> and Set <something>, you can edit the -Pattern value at the end to only be # rather than # Set.

Regex Breakdown:

  • # matches the characters # literally
  • $section substitutes your variable value matches the value literally provided there are no regex characters in the string
  • .* matches any character (except for line terminators)
  • \r matches a carriage return
  • ? Quantifier — Matches between zero and one times, as many times as possible, giving back as needed (greedy)
  • \n matches a line-feed (newline) character
  • (?s) modifier: single line. Dot matches newline characters
  • 1st Capturing Group (.*?)
  • .*? matches any characters lazily
  • Positive Lookahead (?=\r?\n# Set)
  • \r? matches a carriage return zero or more times
  • \n matches a line-feed (newline) character
  • # Set matches the characters # Set literally
  • $ matches the end of the string

Upvotes: 0

Doug Maurer
Doug Maurer

Reputation: 8868

Here's another approach. We will take advantage of Foreach-Object's -End block to [PSCustomObject] the final one.

Get-Content $file | Foreach-Object {
    if($_ -match 'SET (.+?)'){
        if($ht){[PSCustomObject]$ht}
        $ht = [ordered]@{Set = $Matches.1}
    }
    if($_ -match '^[^#]'){
        $ht["IPs"] += $_
    }
} -End {if($ht){[PSCustomObject]$ht}}

Output

Set IPs               
--- ---               
X   [ip][ip][more ips]
Y   [ip][ip][more ips]
Z   [ip][ip][more ips]

If you want to also ensure $ht is empty to start with you could use the -Begin block.

Get-Content $file | Foreach-Object -Begin{$ht=$null}{
    if($_ -match 'SET (.+?)'){
        if($ht){[PSCustomObject]$ht}
        $ht = [ordered]@{Set = $Matches.1}
    }
    if($_ -match '^[^#]'){
        $ht["IPs"] += $_
    }
} -End {if($ht){[PSCustomObject]$ht}}

Upvotes: 0

marsze
marsze

Reputation: 17055

Here's one possible solution. The result will be a hashtable, each key containing any array of ips for the set:

$result = @{}
get-content file.txt | foreach {
    if ($_ -match "#\s*SET\s+(\w+)") {
        $result[($key = $matches.1)] = @()
    }
    elseif ($_ -notlike "#*") {
        $result[$key] += $_
    }
}

Contents of $result:

Name                           Value                                                                                                                                                                                  
----                           -----                                                                                                                                                                                  
Y                              {[ip], [ip], [more ips]}                                                                                                                                                               
Z                              {[ip], [ip], [more ips]}                                                                                                                                                               
X                              {[ip], [ip], [more ips]}    

Upvotes: 1

Related Questions