1

I have an xml file, ignore the numbers at the start, that's not part of it.

(1)<?xml version="1.0" encoding="UTF-8"?>
(2)<one>
(3)<x> </x>
(4)</one><look> HERE </look> <find> 123 </find> <some> </some>
(5)<two></two>

I know there is a line that has a <look> HERE </look> tag, and I want to be able to find the value located within the <find> tags (in this case 123), which is on the same line.

Get-Content '.\x.xml' # works

$line = $x -match "<look> HERE </look>" # this returns the entire line (line 4 in this case)

$line -match "<find>(?<content>.*)</find>"

$matches['content'] #cannot index into a null array error

Why? I expect to get 123 returned

K Split X
  • 3,405
  • 7
  • 24
  • 49

4 Answers4

2

Try parsing XML this way instead. Powershell can cast strings into XML objects, and per my comment using regex to parse XML is not a good idea.

$x = [xml]@"
<xml>
<one>
<x> </x>
</one><look> HERE </look> <find> 123 </find> <some> </some>
<two></two>
</xml>
"@

$x.xml.find

123

Jacob Colvin
  • 2,625
  • 1
  • 17
  • 36
  • This is good, but it requires knowledge of the xml layout itself, and xml's can be pretty big, and so the here string would be too. Thanks for the answer – K Split X Apr 04 '18 at 22:12
  • 1
    The `here-string` in this case is just a replacement for your `get-content` call. Use `[xml]$x = get-content ...` and you'll have forced it to parse the content as an xml file and get the object back. – Jonathan Leech-Pepin Apr 05 '18 at 00:06
2

To answer the actual question as to why (regardless of if you should be doing it that way. Jacob Colvin's answer provides a better method.

A single string returns a boolean and returns $true or $false when using -match

If instead you use Select-String you can then use ForEach-Object { $_.matches.groups } | Where-Object {$_.name -match 'content'} | select -expand value to get the information you want.

So the two options would be:

$line | Where-Object {
    $_ -match "<find>(?<content>.*)</find>"
} | ForEach-Object {
    $matches['content']
}
# Or
$line | select-string "<find>(?<content>.*)</find>" | ForEach-Object {
    $_.matches.groups
} | Where-Object {
    $_.name -match "content"
} | Select -Expand value
Jonathan Leech-Pepin
  • 7,684
  • 2
  • 29
  • 45
2

When -match is applied against a collection, it always returns a collection. If you look at the type of $line you'll see that it's array of 1 element:

> $line.GetType().FullName
System.Object[]
> $line.Count
1

When matching against a collection, $matches isn't set since there could be multiple matches in the collection. $matches is only set for scalar match operations. Since $line is still a collection, to make this work you need to index into $line before applying -match:

> $line[0] -match "<find>(?<content>.*)</find>"
True

Now the match succeeds for the scalar value and content is set:

> $matches['content']
 123
Bruce Payette
  • 2,511
  • 10
  • 8
2

I had the same error and the solution was just trivial. I had to wrap my string with "".

I made a search replace in a file:

    $content = (Get-Content filename.txt)
    $content -match '.*(?<namedGroup>test)'
    Write-Output $Matches.namedGroup

and I had to change it to :

    $content = (Get-Content filename.txt)
    "$content" -match '.*(?<namedGroup>test)'
    Write-Output $Matches.namedGroup
Dan
  • 21
  • 2