Using -match and the $matches variable in PowerShell

iLovePowerShell Logo

iLovePowerShell LogoI’ve been rereading the Windows PowerShell Cookbook and I came across a variable I hadn’t noticed before…

It turns out to be related to the -match comparison operator. -Match performs a regular expression comparison. A simple way of thinking about regular expressions is that they “describe” the patterns of characters. Another way of thinking of regular expressions is “Wildcards on steroids.”

For these examples, I’m going to keep things very simple as far as regular expressions go:

.   <– means “anything”
*  <– means “zero or more of it”
.* <– means “zero or more of anything”

 -Match and $Matches 

<# Like -eq, -match will evaluate True/False 
   when compared against one object #>
"One" -eq "One"
"One" -match ".*"

<# Also like -eq, -match passes objects that evaluate to 
  TRUE through the pipeline when compared against multiple objects#>
"One","Two","Three" -eq "Two"
"One","Two","Three" -match "T.*"

An interesting thing about using the -match comparison operator is that when matches are evaluated, the result is stored in a unique variable, $matches.

The $matches variable takes a little getting used to.

#1 - $Matches doesn't work with -eq
"One", "Two", "Three" -eq "One" | Foreach {$Matches[0]}
<# Surprised? If you've dropped these 
  examples into the ISE, it looks like it works. #>

#2 - $Matches really doesn't work with -eq
"One", "Two", "Three" -eq "Two" | Foreach {$Matches[0]}
<# See? I tricked you! The reason it looked like it worked on #1 was because
   it was still set from the last time -match was used. #>

#3 - $Matches isn't set when evaluating multiple objects 
"One" -match ".*"
"One", "Two", "Three" -match "T.*"

#4 - $Matches CAN be used with multiple items, but evaluate each item individually
"One" -match ".*"
"One","Two","Three" | Where-Object {$_ -match "T.*"} | Foreach {$Matches[0]}
<#Notice how $matches now equals "Three"? What happened to "Two"? 
  Since I used the pipeline to break the list into three items, 
  the -match comparison in the Where cmdlet does update the 
  $Matches variable. But immediately after evaluating TRUE for "Two", 
  it updates with "Three" and Two returns to the ether. #>

#5 - Don't be fooled by trying to -match on multiples
"One","Two","Three" -match "T.*" | Foreach {$Matches[0]}
<#See? It's tricky.#>

Finally, you’ll just want to notice that I keep switching between $Matches and $Matches[0]. $Matches is a variable that contains a hashtable. The hashtable (a key-pair) has a key and a value. The key is the index and the value is “what was returned as the match.” Since there is only one value in the $Matches variable, you can get to the value of the match by referencing the key-pair by its name.

Alright, so you’ve seen how $Matches works. But why use it?

In the examples that I’ve shown here, you probably aren’t that impressed. I promise I kept it simple so it wouldn’t be too intimidating with the crazy looking regular expressions. Regex can look really nuts.

Let’s use another example that is equally as useless but at least serves as a better example of why we might like $Matches.

#Let's make a little list of words
$words = "The quick brown fox jumped over the lazy dog".Split()

#Now show only words that start with a letter between "a" and "l" and also have an "o" somewhere in the word.
$Words | Where {$_ -match "[A-L,a-l].*o.*"} | Foreach {$Matches[0]}

That really can’t be done with using a -like comparison operator and normal wildcards. Still not sure if you’d have a use case for having the matches of a regular expression displayed? Here’s when you want to use it.

Grab a list of strings. Pipe them into a Where-Object or Select-String to perform a -match against them. If what you want is the whole string when it has the match, then you just use the standard output. But if what you want is only the resulting match, then you can pipe it to Foreach-Object {$matches[0]}

Did this help? Have something to add? Leave a comment below.