I'm using Named Captures
in PowerShell
. But the following example is not returning all matches. All the lines in the following code return correct output - except the lines 7 and 8. I was expecting $Matches.1
and $Matches.2
to return v
and A
respectively. But these two lines of code return nothing.
Question: What I may be missing here or maybe not correctly understanding here?
Ref: Lates version 7.4 of PowerShell
PS> $string = 'The last logged on user was A\v'
PS> $string -match 'was (?<domain>.+)\\(?<user>.+)'
PS> $Matches.domain #returns A
PS> $Matches.user #returns v
PS> $Matches.count #returns 3
PS> $Matches.0 #returns was A\v
PS> $Matches.1 #returns nothing
PS> $Matches.2 #returns nothing
PS> $Matches #returns the following output
Name | Value |
---|---|
domain | A |
user | v |
0 | was A\v |
The automatic $Matches
variable is a hashtable whose entries are populated as follows:
The entry with key (index) 0
(type [int]
) is always present, representing the whole match.
Additional entries are only populated if there are capture groups in the regex, as follows:
Any named capture group (e.g. (?<domain>.+)
) is represented by an entry with a key of the same name (type [string]
) - only (it doesn't also get a positional entry - see below).
$Matches.domain
or, alternatively, $Matches['domain']
, returns the substring matched by the example capture group, if any.Any - remaining or only - unnamed and therefore positional capture groups (e.g. (.+)
) are represented in order, with indices, namely as entries with [int]
-typed keys starting with 1
.
$Matches.1
or, alternatively, $Matches[1]
, would return the substring that the example capture group matched, assuming it is the first positional (unnamed) one in the regex.It follows from the above that in your case $Matches
only has 'domain'
and 'user'
entries beyond the always-present 0
entry, because only named capture groups, with these names, are present.
Also note that the fact that $Matches
is implemented as a [hashtable]
implies that its entries are inherently unordered - thus, you cannot rely on the enumeration order of the entries to be meaningful (via .GetEnumerator()
for the entries as a whole, or via .Keys
or .Values
for the keys and values, respectively), and even the entries with keys that are conceptually indices - for the whole match and any unnamed / positional capture groups - are typically not reflected in their numerical order.
Important considerations of $Matches
use:
It is automatically populated by:
-match
, the regular-expression matching operator:
Only if the LHS is a single string (object) and a match is found, i.e. only when -match
returns $true
Notably, a preexisting $Matches
result is left untouched if $false
is returned, so $Matches
should only be consulted after receiving $true
.
-match
only ever looks for one match in the input string.
-matchall
operator.A simple example:
$str = 'Maroon 5'
if ($str -match '(o)+. \d') {
Write-Verbose -Verbose "Matched: $str - `$Matches contains:"
$Matches
} else {
# NOTE: $Matches was NOT populated
# nor were previous results reset.
Write-Verbose -Verbose "Did not match: $str"
}
By contrast, with array-valued LHS, -match
- like other comparison operators - acts as a filter that returns the sub-array of matching input strings, and doesn't populate $Matches
at all (nor resets any previous results).
the switch
statement, if used with the -Regex
switch:
Specifically, string-based branch conditionals are then treated as regexes and the associated action block - which is only entered in case of a match - then has $Matches
populated.
A simple example:
switch -Regex ('Maroon 5', 'foo') {
'(o)+. \d' { Write-Verbose -Verbose "Matched: $_ - `$Matches contains:"; $Matches }
default { Write-Verbose -Verbose "Did not match: $_" }
}