Is it possible to get match position using `switch -regex`

Viewed 443

Select-String -Pattern returns the location of each match. For example:

$s = 'abcxyzabc' | Select-String -Pattern 'abc(?<x>.*)abc'
$s.Matches[0].Groups | Select-Object Name, Value, Index, Length

gives:

Name Value     Index Length
---- -----     ----- ------
0    abcxyzabc     0      9
x    xyz           3      3

where Index and Length specifies the location of each match.

However, I can't find how to get the location of each match when using switch -regex. For example:

switch -regex ('abcxyzabc') {
    'abc(?<x>.*)abc' {
        $matches
    }
}

gives:

Name                           Value
----                           -----
x                              xyz
0                              abcxyzabc

I can't find anything like Index and Length to get the match location in $matches.

I also checked that the type of Matches returned by Select-String -Pattern is System.Text.RegularExpressions.Match[], while the type of $matches in switch -regex is System.Collections.Hashtable.

Did I miss anything or switch -regex is not supposed to give the location of each match?

3 Answers

Did I miss anything or is switch -regex not supposed to give the location of each match?

Indeed, neither the switch statement's -Regex switch nor the (effectively) underlying
-match operator are designed to provide information other than what text was captured
, via the automatic $Matches variable - see this answer for more information.

Obtaining match-location information (start index, length) requires one of the following:

Applying the latter to your example:

PS> [regex]::Match('abcxyzabc', 'abc(?<x>.*)abc')

Groups   : {0, x}
Success  : True
Name     : 0
Captures : {0}
Index    : 0
Length   : 9
Value    : abcxyzabc

Daniel's helpful answer shows you a - somewhat cumbersome - way to provide this functionality via switch.

A more efficient approach is to combine a foreach statement with an if statement:

foreach ($str in 'abcxyzabc', '...') {
  if     (($match = [regex]::Match($str, 'abc(?<x>.*)abc')).Success) { <# ... #> }
  elseif (($match = [regex]::Match($str, 'cde(?<x>.*)cde')).Success) { <# ... #> }
  # ...
}

You could use this ugly alternative which uses the Regex class directly and will give you index and length

$values = @(
    'abcxyzabc'
    'cat in the hat'
    'the dogg pound'
)

switch ($values) {
    {($script:myMatches = [regex]::Matches($_, 'abc(?<x>.*)abc'))} {
        $myMatches.Groups[1]
    }
    {($script:myMatches = [regex]::Matches($_, 'cat(?<x>.*)hat'))} {
        $myMatches.Groups[1]
    }
}

Results

Success  : True
Name     : x
Captures : {x}
Index    : 3
Length   : 3
Value    : xyz

Success  : True
Name     : x
Captures : {x}
Index    : 3
Length   : 8
Value    :  in the

Continuing from my comment.

https://adamtheautomator.com/powershell-switch/#Using_the_-RegEx_Parameter

Referencing the code below, the first line will import the contents of the RegExp.txt and store it in the $RegExp variable. Then, the Powershell switch statement uses the email.txt file as the input for test values as indicated by the -file parameter.

$RegExp = Get-Content .\RegExp.txt
switch -regex -file .\email.txt {
    $RegExp {"[$_] is an email address"}
    Default {"[$_] is NOT an email address"}
}

Once the code above is run in PowerShell, only the test value that matches the regular expression stored in the $RegExp will be validated.

https://riptutorial.com/powershell/example/3791/switch-statement-with-regex-parameter

The -Regex parameter allows switch statements to perform regular expression matching against conditions.

switch -Regex ('Condition')
{ 
  'Con\D+ion'    {'One or more non-digits'}
  'Conditio*$'   {'Zero or more "o"'} 
  'C.ndition'    {'Any single char.'}  
  '^C\w+ition$'  {'Anchors and one or more word chars.'} 
  'Test'         {'No match'} 
}

Update

relative to @daniel alternative helpful approach, mixed with your effort; can be refactored to this.

switch -regex ('abcxyzabc') 
{
    {($script:myMatches = [regex]::Matches($PSItem, 'abc(?<x>.*)abc'))} 
    {
        $myMatches.Groups | 
        Select-Object Name, Value, Index, Length
    }
} 

# Results
<#
Name Value     Index Length
---- -----     ----- ------
0    abcxyzabc     0      9
x    xyz           3      3
#>
Related