GeezerVison is showing github.com — Dollar Sign Gets 'Eaten' by -replace When More Than One Dollar Sign Adjacent In String · Issue #6842 · PowerShell/PowerShell
Dollar Sign Gets 'Eaten' by -replace When More Than One Dollar Sign Adjacent In String #6842DescriptionIf you try to do a -replace with a string that has two or more dollar signs next to it, one dollar sign gets removed. This happens even if the characters are escaped.
Steps to reproduce
Scenario 1: Two Dollar Signs in String Literal

Scenario 2: More Than Two Dollar Signs in String Literal

Scenario 3: One Dollar Sign in String Literal

Scenario 4: Two Dollar Signs in String Literal (escaped)

Expected behavior
Scenario 1: P@$$w0rd
Scenario 2: P@$$$w0rd
Scenario 3: P@$w0rd
Scenario 3: P@$$w0rd
Actual behavior
Scenario 1:

Scenario 2:

Scenario 3:

Scenario 4:

Environment data
$PSVersionTable

Activitymklement0 commented on May 8, 2018 The -replace replacement operand is not a literal: $ has special meaning in it and is used to refer to aspects of the regex matching, such as $& to refer to the entire match and $1 to refer to what the 1st capture group matched.
Therefore, $ characters you want to treat literally must be escaped by doubling them.
(Note that this escaping mechanism is unrelated to PowerShell's `-based escaping).
And that is what happened accidentally in your case: the $$ sequence was interpreted as an escaped $.
Unescaped $ sequences that happen not to form a valid regex-match reference are also retained as literals, but it's better not to rely on that.
You can read more about this here.
The robust workaround is to use the following:
$testString -replace '...', $password.Replace('$', $$')
However, if your replacement operation doesn't actually need regex matching, only simple substring replacement, you can use [string]::Replace() for the entire operation, but note that it is case-sensitive by default:
$testString.Replace('...', $password)Reacted by Rob Holt, blixthecat and Pyprohly blixthecat commented on May 8, 2018 Thank you for explaining! I couldn't find a reference to that kind of escaping anywhere! Reacted by Michael Klement BrucePay commented on May 9, 2018 We should probably have include a link to Substitutions in Regular Expressions in about_Comparison_Operators as well as mentioning that substitutions happen with -replace in the body of the document. MaximoTrinidad commented on May 9, 2018 Someone forgot to post that the symptom exist in PSCore6. Although I would have use the following .NET object replace method:
PS C:\Program Files\PowerShell\6-preview> $password
P@$$word
PS C:\Program Files\PowerShell\6-preview> $password.Length
8
PS C:\Program Files\PowerShell\6-preview> $password.ToString()
P@$$word
PS C:\Program Files\PowerShell\6-preview> "$($password.ToString())"
P@$$word
PS C:\Program Files\PowerShell\6-preview> $testString.replace('%MGR_PWD',"$($password.ToString())");
P@$$word
I mostly use .NET object methods instead of using parameters. But, that's me! mklement0 commented on May 9, 2018 Maximo Trinidad (@MaximoTrinidad):
To be clear: the behavior is by design, and it is here to stay.
However, the behavior is poorly documented, hence Bruce Payette (@BrucePay)'s suggestion.
Using String.Replace(string oldvalue, string newvalue) may be the simplest solution in this case, but it is by no means a general alternative to -replace; not only do the behaviors differ, -replace offers many more features:
-
Regex support, which -replace invariably applies.
-
String.Replace() only supports literal substring replacement - both for matching and in the replacement string.
- This prevents advanced matching behavior, such as anchoring matches or capturing parts of the input string and referencing these parts in the replacement-string operand.
- On the flip side, you do not have to worry about escaping
$ chars. in the replacement string, as you demonstrate.
-
You can indirectly get literal substring replacements with -replace too:
- If the search operand is a string literal,
\-escape all regex metacharacters that you want to be treated as literals.
- If the search operand is a variable reference, call
[regex]::Escape() on it.
- If the replacement operand is a string literal,
$-escape $ instances that you want to be treated as literals (i.e., double $ chars. meant to be literals).
- If the search operand is a variable reference, call
.Replace('$', '$$') on it (i.e., take advantage of simple substring replacement via String.Replace()) or, if you want to stick with -replace, use (... -replace '\$', '$$$$').
-
As an aside: thanks to work by @IISResetMe, -replace as of v6.1.0-preview.2 even supports passing a script block as the replacement operand; e.g.,
'1 + 1 = 2' -replace '\d+', { [int] $_.Value * 2 } yields '2 + 2 = 4'
-
Case-insensitivity.
String.Replace(string oldValue, string newValue), is case-sensitive, but you can opt into case-sensitivity via overloads String.Replace(string oldValue, string newValue, bool ignoreCase, cultureinfo culture) or String.Replace(string oldValue, string newValue, System.StringComparison comparisonType)
- Conversely,
-creplace gives you case-sensitivity (more conveniently) in PowerShell.
-
Support for array-valued LHS operands.
-
Support for implicit to-string conversion of the LHS.
P.S.: You can find an analogous juxtaposition of -split and String.Split() in this SO answer. mklement0 commented on May 10, 2018
MetadataMetadataDevelopmentNo branches or pull requestsIssue actions
|