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: $_" }
}