rga.cz
rga.cz

Reputation: 85

PowerShell RegEx to split MAC address

I need to verify MAC address in RAW format using RegEx and split it into an array of 6 values by 2 characters.
When I use following pattern, I get content of last iteration of capture group only:

PS C:\Windows\System32> "708BCDBC8A0D" -match "^([0-9a-z]{2}){6}$"
True
PS C:\Windows\System32> $Matches

Name                           Value
----                           -----
1                              0D
0                              708BCDBC8A0D


PS C:\Windows\System32>

With what pattern can I caputere all the groups?
I need this result:

0 = 708BCDBC8A0D
1 = 70
2 = 8B
3 = CD
4 = BC
5 = 8A
6 = 0D

Upvotes: 4

Views: 1540

Answers (2)

mklement0
mklement0

Reputation: 440162

As you've observed, the automatic $Matches variable, which reflects the result of the most recent (scalar-input[1]) regular-expression-based match operation, only ever contains the last instance of what an embedded capture group ((...)) captured.

Generally, -match only ever looks for at most ONE match in the input.

  • GitHub issue #7867 proposes introducing a new -matchall operator that would find all matches and return them as an array.

Direct use of the [regex] class (System.Text.RegularExpressions.Regex) that underlies PowerShell's regex functionality already provides that ability, namely in the form of the ::Matches() method, in which case capture groups aren't even needed.

# Note: Inline option (?i) makes the regex case-INsensitive
#       (which PowerShell's operators are BY DEFAULT).
PS> [regex]::Matches('708BCDBC8A0D', '(?i)[0-9a-f]{2}').Value
70
8B
CD
BC
8A
0D

However, with a bit of trickery, you can also use -split, the string splitting operator:

# Note: No inline option needed: -split - like -match and -replace -
#       is case-INsensitive by default.
PS> '708BCDBC8A0D' -split '([0-9a-f]{2})' -ne ''
70
8B
CD
BC
8A
0D

If can assume that all character pairs in the input strings are hex byte values, you can simplify to:

'708BCDBC8A0D' -split '(..)' -ne ''

Note:

  • The regex is of necessity enclosed in (...), a capturing group, to explicitly instruct -split to include what it matches in the results; since the regex normally describes the separators between the substrings of interest, its matches are normally not included.

  • In this case it is only the "separators" we care about, whereas the substrings between them are empty strings here, so we filter them out with -ne ''.


[1] If the LHS of a -match operation is an array (a collection), matching occurs against each element, and the sub-array of matching elements (rather than a single Boolean) is returned. In this case, $Matches is not populated.

Upvotes: 0

filimonic
filimonic

Reputation: 4644

You can not capture multiple groups with single group definition. Avoid using RegEx when unnecessary as it takes lots of CPU. Valuable for millions of recrds.

For MACs you can use special PhysicalAddress class:

[System.Net.NetworkInformation.PhysicalAddress]::Parse('708BCDBC8A0D') 

For .Net 5 (Powershell Core I think based on it) there is TryParse method added, but in .Net 4.5 there is no TryParse method.

To check .Net framework powershell running use [System.Reflection.Assembly]::GetExecutingAssembly().ImageRuntimeVersion


'708BCDBC8A0D' -match "^$('([A-F0-9]{2})' * 6)$"; $Matches
'708BCDBC8A0D' -match '^([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})([A-F0-9]{2})$'; $Matches

'@(0..5) | ForEach-Object {'708BCDBC8A0D'.Substring($_ * 2, 2)}'

@(
    [String]::new('708BCDBC8A0D'[0..1]),
    [String]::new('708BCDBC8A0D'[2..3]),
    [String]::new('708BCDBC8A0D'[4..5]),
    [String]::new('708BCDBC8A0D'[6..7]),
    [String]::new('708BCDBC8A0D'[8..9]),
    [String]::new('708BCDBC8A0D'[10..11])
)

Upvotes: 1

Related Questions