1

I am doing a string replacement in PowerShell. I have no control over the strings that are being replaced, but I can reproduce the issue I'm having this way:

> 'word' -replace 'word','@#$+'
@#word

When the actual output I need is

> 'word' -replace 'word','@#$+'
@#$+

The string $+ is being expanded to the word being replaced, and there's no way that I can find to stop this from happening. I've tried escaping the $ with \ (as if it was a regex), with backtick ` (as is the normal PowerShell way). For example:

> 'word' -replace 'word',('@#$+' -replace '\$','`$')
@#`word

How can I replace with a literal $+ in PowerShell? For what it's worth I'm running PowerShell Core 6, but this is reproducible on PowerShell 5 as well.

Mark Henderson
  • 2,586
  • 1
  • 33
  • 44

4 Answers4

4

Instead of using the -replace operator, you can use the .Replace() method like so:

PS> 'word'.Replace('word','@#$+')
@#$+

The .Replace() method comes from the .NET String class whereas the -Replace operator is implemented using System.Text.RegularExpressions.Regex.Replace().

More info here: https://vexx32.github.io/2019/03/20/PowerShell-Replace-Operator/

Brandon Olin
  • 322
  • 3
  • 11
2

It's confusing how these codes aren't documented under "about comparison operators". Except I have a closed bug report about them underneath if you look, 'wow, where are all these -replace 2nd argument codes documented?'. https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_comparison_operators?view=powershell-7 That's become my reference. "$+" means "Substitutes the last submatch captured". Doubling up the dollar signs works, 'substitutes a single "$" literal':

'word' -replace 'word','@#$$+'

@#$+

Or use the scriptblock version of the second argument in PS 6 and above (may be slower):

'word' -replace 'word',{'@#$+'}
js2010
  • 23,033
  • 6
  • 64
  • 66
  • I suggest making it clear that the script-block technique has significant performance implications (won't matter in a single call, but may in a loop / when operating on a LHS array). – mklement0 Feb 05 '20 at 17:11
  • 1
    Turns out that the information _is_ there, just not readily discoverable, inside the `about_Regular_Expressions` topic: [Substitutions in Regular Expressions](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_regular_expressions#substitutions-in-regular-expressions): ; I've created [a GitHub issue](https://github.com/MicrosoftDocs/PowerShell-Docs/issues/5416) suggesting specific improvements to the `about_Comparison_Operators` topic. – mklement0 Feb 05 '20 at 18:54
1

I couldn't find it documented but the "Visual Basic" style escaping rule worked, repeat the character.

'word' -replace 'word','@#$$+' gives you: @#$+

Dane W
  • 181
  • 2
  • 6
  • Sadly it doesn't work in the way I was hoping. When I run `'word' -replace 'word',('@#$+' -replace '$','$$')` it does the same expansion when it finally runs. I can't modify the source string, so I have to do it with a `replace` – Mark Henderson Feb 05 '20 at 00:00
  • @MarkHenderson: Your replacement expression is flawed: it would have to be `('@#$+' -replace '\$','$$$$')` (sic), or, more simply, `'@#$+'.Replace('$','$$')` – mklement0 Feb 05 '20 at 03:07
  • Indeed, `$$` works; it is documented [here](https://learn.microsoft.com/en-us/dotnet/standard/base-types/substitutions-in-regular-expressions#substituting-a--character) – mklement0 Feb 05 '20 at 03:15
  • The `$$$$` version does indeed work. I've posted my contrived example so although it looks like I could use your simplified version, I don't think I can. However the `$$$$` replacement works. A line like that needs a very detailed comment I think ;) – Mark Henderson Feb 05 '20 at 06:23
  • @MarkHenderson: The comment was just a summary of what [my answer](https://stackoverflow.com/a/60068470/45375) explains in detail. – mklement0 Feb 05 '20 at 16:56
1

tl;dr:

Double the $ in your replacement operand to use it verbatim:

PS> 'word' -replace 'word', '@#$$+' # note the doubled '$'
@#$+

PowerShell's -replace operator:

  • uses a regex (regular expression) as the search (1st) operand.

    • If you want to use a search string verbatim, you must escape it:
      • programmatically: with [regex]::Escape()
      • or, in string literals, you can alternatively \-escape individual characters that would otherwise be interpreted as regex metacharacters.
  • uses a non-literal string that can refer to what the regex matched as the replacement (2nd) operand, via $-prefixed tokens such as $& or $+ (see link above or Substitutions in Regular Expressions).

    • To use a replacement string verbatim, double any $ chars. in it, which is programmatically most easily done with .Replace('$', '$$') (see below).

If both your search string and your replacement string are to be used verbatim, consider using the [string] type's .Replace() method instead, as shown in Brandon Olin's helpful answer.

  • Caveat: .Replace() is case-sensitive by default, whereas -replace is case-insensitive (as PowerShell generally is); use a different .Replace() overload for case-insensitivity, or, conversely, use the -creplace variant in PowerShell to get case-sensitivity.

    • [PowerShell (Core) 7+ only] Case-insensitive .Replace() example:
      'FOO'.Replace('o', '@', 'CurrentCultureIgnoreCase')
  • .Replace() only accepts a single string as input, whereas -replace accepts an array of strings as the LHS; e.g.:

    • 'hi', 'ho' -replace 'h', 'f' # -> 'fi', 'fo'
  • .Replace() is faster than -replace, though that will only matter in loops with high iteration counts.


If you were to stick with the -replace operator in your case:

As stated, doubling the $ in your replacement operand ensures that they're treated verbatim in the replacement:

PS> 'word' -replace 'word', '@#$$+' # note the doubled '$$'
@#$+

To do this simple escaping programmatically, you can leverage the .Replace() method:

'word' -replace 'word', '@#$+'.Replace('$', '$$')

You could also do it with a nested -replace operation, but that gets unwieldy (\$ escapes a $ in the regex; $$ represent a single $ in the replacement string):

# Same as above.
'word' -replace 'word', ('@#$+' -replace '\$', '$$$$')

To put it differently: The equivalent of:

'word'.Replace('word', '@#$+')

is (note the use of the case-sensitive variant of the -replace operator, -creplace):

'word' -creplace [regex]::Escape('word'), '@#$+'.Replace('$', '$$')

However, as stated, if both the search string and the replacement operand are to be used verbatim, using .Replace() is preferable, both for concision and performance.

mklement0
  • 382,024
  • 64
  • 607
  • 775