I'm trying to run through files named using alphabetical dates, and set file date-times accordingly. My code works fine, and I was ready to consider it complete, when I noticed this issue. My code should detect two dates and generate an error, but it doesn't. I've extracted the relevant code, and recreated the issue:
$str = "test20001231 20170415.txt"
$match = ($str -match "(?<=\b|\D)20\d{6,6}(?=\b|\D)")
"$match"
"$($Matches.length)"
"$($Matches[0].ToString())"
Gives this output:
True
1
20001231
My understanding of the regex code is that it should match everything that is an 8 digit number beginning with 20, wherever it is in the string, unless it is following or preceding another digit. So I am expecting $Matches.length to be 2.
I've tested the regex code in a number of places online, and it matches the two dates as I expect: http://regexstorm.net/tester?p=%28%3f%3c%3d%5cb%7c%5cD%2920%5cd%7b6%2c6%7d%28%3f%3d%5cb%7c%5cD%29&i=test20001231+20170415.txt http://www.phpliveregex.com/p/jLA
The issue applies to PS and PS ISE. I've searches lots (I think), and not turned up anything helpful. Any suggestions? Many thanks in advance, Dave
PowerShell's -match operator only ever looks for the first match (if any) per input string, because its purpose is to test for a (any) match, irrespective of whether there is more than one.
Note that a single -match expression can have multiple input strings, if the LHS is an array, in which case an array of the elements that match is returned; e.g.: 'foo', 'bar', 'baz' -match 'b' yields array 'bar', 'baz'. However, for each array element only a single match is again tested for, and the automatic $Matches variables is not populated in this case - see bottom.
All commands below assume PSv3+, but could be made to work in v2 too.
You need to use the .NET framework's [regex] class to get multiple matches:
PS> ([regex]::Matches('test20001231 20170415.txt', '(?<=\b|\D)20\d{6,6}(?=\b|\D)')).Value
20001231
20170415
[regex]::Matches() outputs a collection of [System.Text.RegularExpressions.Match] instances[1] whose .Value properties contain the matches.
Note how .Value is applied to the entire collection, which in PSv3+
automatically returns the property values of the collection members as an array.
To get just the count of matches:
PS> ([regex]::Matches('test20001231 20170415.txt', '(?<=\b|\D)20\d{6,6}(?=\b|\D)')).Count
2
Another option is to use Select-String -AllMatches, which outputs [Microsoft.PowerShell.Commands.MatchInfo] instances
whose .Matches property contains each line's collection of [System.Text.RegularExpressions.Match] instances:
PS> ('test20001231 20170415.txt' |
    Select-String -AllMatches '(?<=\b|\D)20\d{6,6}(?=\b|\D)').Matches.Value
20001231
20170415
As above, substituting .Count for .Value outputs the number of matches.
Note that use of Select-String is a bit heavy-handed for use with a single input string, but it's the right tool to use for large input collections, such as a file's lines.
Optional reading: The automatic $Matches variable:
The automatic $Matches variable is populated (as of PSv5.1):
-match operator$Matches is neither populated nor reset.-match returns $true)
-match returns $false), a preexisting $Matches value, if any, is left untouched.$Matches is a [hashtable] instance with the following entries:
0's entry is the entire match - this key is by definition always present.<n>'s entry is what the - unnamed - capture group with index <n> matched.<name>'s entry is what named capture group <name> matched.The fact that $Matches (potentially) also contains capture-group values justifies its plural name - despite only relating to a single match of the given regex.
[1] To inspect the type of a single object or the type of the elements of a collection, pipe to Get-Member: ([regex]::Matches('foo', 'o')) | Get-Member
To inspect the type of a collection itself, pass it to Get-Member -InputObject:Get-Member -InputObject ([regex]::Matches('foo', 'o'))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With