3

I am trying to convert some string input into the correct format for working with a MAC address.

So I need to convert

00A0C914C829

to

00:A0:C9:14:C8:29

I have this PowerShell script to achieve this:

$string = "00A0C914C829"
$out = $string -replace "([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])([0-9A-Fa-f])", '$1$2:$3$4:$5$6:$7$8:$9$10:$11$12'
$out

Which outputs:

00:A0:C9:14:C8:29

But that regex seems ridiculously long.

Is there some way of making this more concise?


  • I think I need to group each section into variables using (), which is why it is so long. I also tried matching with ^([0-9A-Fa-f]){12}$, but this gave the output 9$2:$3$4:$5$6:$7$8:$9$10:$11$12 (12 groups of 1 character each)
  • I also tried the similar regex ^(([0-9A-Fa-f]){2}){6}$ (6 groups of 2 characters each) and then the replacement: $1:$2:$3:$4:$5:$6, but this gave output 29:9:$3:$4:$5:$6

I think the problem is that I don't understand how groups work properly ... Would really appreciate if someone could point me in the right direction!

Bassie
  • 9,529
  • 8
  • 68
  • 159

2 Answers2

10

Think about it in terms of smaller units. You actually want to insert : after every two characters (except at the end of the string) instead of trying to capture every single character and forcing it into the desired format.

$s -replace '..(?!$)', '$&:'

This regex matches two characters at a time and replaces them by the same characters ($& in a replacement string represents the match) followed by :. Except at the end of the string, which is done via a negative lookahead preventing a match at the end of the string. This works because the regex engine won't look for matches that extend backwards into previous matches. So after the first two characters have been matched, the next match can only occur after that first one. This implicitly gives you groups of two characters to replace.

Instead of . you can also use [0-9a-f] if you really want, but I think input validation is a different problem than output formatting here. The output code should be able to trust that the string indeed contains only hexadecimal digits.

Validation can be done as simply as

if ($s -notmatch '^[0-9a-f]{12}$') {
  throw
}

And it's actually easier to detect a malformed string this way than by detecting whether the replacement did nothing.

Joey
  • 344,408
  • 85
  • 689
  • 683
0

To add to Joey's answer. .NET has the PhysicalAddress class with build in validation. The parse method accepts multiple formats

https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.physicaladdress.parse?view=net-5.0#System_Net_NetworkInformation_PhysicalAddress_Parse_System_String_

001122334455

00-11-22-33-44-55

0011.2233.4455 (.NET 5 and later versions only)

00:11:22:33:44:55 (.NET 5 and later versions only)

F0-E1-D2-C3-B4-A5

f0-e1-d2-c3-b4-a5 (.NET 5 and later versions only)

You would need to install PS7 to parse some of those. I needed it to parse the Cisco IOS format

user242114
  • 29
  • 3
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. – Tyler2P Nov 01 '21 at 16:32