How do I pass a RegEx token to a PowerShell subexpression in a RegEx substitution?

Viewed 88

I have the following code:-

'\u0026' -replace '(\u)(\d{4})', '$$([char]0x$2)'

That will obviously result with:-

$([char]0x0026)

If I make the RegEx substitution into an expandable string with:-

'\u0026' -replace '(\\u)(\d{4})', "$([char]0x`${2})"

Then I will get:-

Unexpected token '0x`$' in expression or statement.

If I simplify things to:-

'\u0026' -replace '(\\u)(\d{4})', "0x`${2}"

Then I can get:-

0x0026

But, what I want is to cast that '0x0026' to a char so it replaces '\u0026' to '&'. However, it seems impossible to pass a RegEx substituted token to a PowerShell subexpression in this way. If you separate the two languages with:-

'\u0026' -replace '(\\u)(\d{4})', "$([char]0x0026) 0x`${2}"

Then the below will result:-

& 0x0026

Which is great as it shows PowerShell subexpressions do work in RegEx substitutions as the converted ampersand shows.

I am new to RegEx. Have I hit my limit already?

3 Answers

Apperently, you want to unescape an escaped regular expression. You can do this using the .net [regex] unescape method:

[Regex]::Unescape('Jack\u0026Jill')

Yields:

Jack&Jill

There's a way in powershell 7, where -replace's 2nd arg can be a scriptblock. Getting the 2nd matching group takes a bit more doing using $_:

'\u0026' -replace '(\\u)(\d{4})', { $b = $_ }
$b.groups

Groups   : {0, 1, 2}
Success  : True
Name     : 0
Captures : {0}
Index    : 0
Length   : 6
Value    : \u0026

Success  : True
Name     : 1
Captures : {1}
Index    : 0
Length   : 2
Value    : \u

Success  : True
Name     : 2
Captures : {2}
Index    : 2
Length   : 4
Value    : 0026


'\u0026' -replace '(\\u)(\d{4})', { [char][int]('0x' + $_.groups[2]) }

&

Note that \d won't match all hex numbers. ([[:xdigit:]] doesn't work.)

'\u002b' -replace '(\\u)([0-9a-f]{4})', { [char][int]('0x' + $_.groups[2]) }

+

Use a scriptblock substitution (6.2 and up):

'\u0026' -replace '(\\u)(\d{4})', {"0x$($_.Groups[2].Value)"}

In earlier versions of PowerShell you can do the same by calling [Regex]::Replace():

[regex]::Replace('\u0026', '(\\u)(\d{4})', {param($m) "0x$($m.Groups[2].Value)"})

In both cases, the block will act as a callback for every single match, allowing you to construct the replacement string after getting access to the matched substring(s), but before the substitution takes place:

PS ~> [regex]::Replace('\u0026', '(\\u)(\d{4})', {param($m) "0x$($m.Groups[2].Value)"})
0x0026
Related