2

Let's say I have a test file named testfile.txt containing the below line:

one (two) "three"

I want to use PowerShell to say that if the entire string exists, place a line directly underneath it with the value:

four (five) "six" 

(Notice that it includes both spaces, brackets and double quotes. This is important, as the problem I am having is I think with escaping the brackets and double quotes).

So the result would be:

one (two) "three"
four (five) "six" 

I thought the easiest way of doing it would be to say that if the first string is found, replace it with the first string itself again, and the new string forming a new line included in the same command. I had difficulty putting the strings in line so I tried using a herestring variable whereby an entire text block with formatting is read. It still does not parse the full string with quotes into the pipeline. I'm new to powershell so don't hold back if you see something stupid.

$herestring1 = @"
one (two) "three"
"@

$herestring2 = @"
one (two) "three"
four (five) "six"
"@

if((Get-Content testfile.txt) | select-string $herestring1) {
"Match found - replacing string"
(Get-Content testfile.txt) | ForEach-Object { $_ -replace $herestring1,$herestring2 } | Set-Content ./testfile.txt
"Replaced string successfully"
}
else {
"No match found"}

The above just gives "No match found" every time. This is because it does not find the first string in the file. I have tried variations using backtick [ ` ] and doubling quotes to try to escape, but I thought the point in a here string was that it should parse the text block including all formatting so I should not have to.

If I change the file to contain only:

one two three

and then change the herestring accordingly to:

$herestring1 = @"
one two three
"@

$herestring2 = @"
one two three
four five six
"@

Then it works ok and I get the string replaced as I want.

Martin Brandl
  • 56,134
  • 13
  • 133
  • 172
coursemyhorse
  • 23
  • 2
  • 6

2 Answers2

1

As Martin points out, you can use -SimpleMatch with Select-String to avoid parsing it as a regular expression.

But -replace will still be using a regex.

You can escape the pattern for RegEx using [RegEx]::Escape():

$herestring1 = @"
one (two) "three"
"@

$herestring2 = @"
one (two) "three"
four (five) "six"
"@

$pattern1 = [RegEx]::Escape($herestring1)

if((Get-Content testfile.txt) | select-string $pattern1) {
"Match found - replacing string"
(Get-Content testfile.txt) | ForEach-Object { $_ -replace $pattern1,$herestring2 } | Set-Content ./testfile.txt
"Replaced string successfully"
}
else {
"No match found"}

Regular expressions interpret parentheses () (what you are calling brackets) as special. By default, spaces are not special, but they can be with certain regex options. Double quotes are no problem.

In regex, the escape character is backslash \, and this is independent of any escaping you do for the PowerShell parser using backtick `.

[RegEx]::Escape() will ensure anything special to regex is escaped so that a regex pattern will interpret it as literal, so your pattern will end up looking like this: one\ \(two\)\ "three"

briantist
  • 45,546
  • 6
  • 82
  • 127
  • Fantastic, thanks to you both. Can I just ask though, in your above example I want to understand why we do not have to use the the regex escape method for $herestring2 ? So What I mean is, I would expect to have to put in a $pattern2 variable as well, doing the same thing there. It seems it was not needed so I don't understand how it uses $herestring2 ok. Thanks so much. First post ever on this site and answered pretty much instantly saving me so much time. Very grateful. – coursemyhorse Oct 17 '16 at 14:56
  • @coursemyhorse the replacement value is not a regular expression pattern. It can use a few special characters (namely `$1` for example to refer to backreferences of capture groups) but if there are no capture groups it will be interpreted literally anyway. So escaping `$herestring2` would result in your replacement string containing extra characters you don't want. – briantist Oct 17 '16 at 14:59
  • You are right, if I add it as I suggested it gives a result as: – coursemyhorse Oct 17 '16 at 15:10
  • one\ \(two\)\ "three"\r\nfour\ \(five\)\ "six" I'm still not sure I understand why that occurs, or why only the herestring1 requires escaping only if I am honest. – coursemyhorse Oct 17 '16 at 15:11
  • @coursemyhorse to put it another way, regular expressions are all about finding matches to patterns. Certain characters in the regex have special meaning for the pattern. The `-replace` operator uses regex to _find the string to replace_ but the value that it replaces it _with_ is not itself a pattern, so its values do not need to be escaped. If you were to use `$herestring2` as a pattern for another `-replace`, then it would be need to be escaped for that, but still not if it's the replacement value (second argument to `-replace`). Does that make it any clearer? – briantist Oct 17 '16 at 15:14
  • 1
    That totally explained it. I understand now. Thanks. I just need to learn and remember which ones need which when I use them I guess! Consider this concluded. – coursemyhorse Oct 17 '16 at 15:18
  • @coursemyhorse great, glad that helped. If you consider it resolved, you can choose to accept an answer by selecting the checkmark next to it. You may also upvote any and all answers which were helpful to you. – briantist Oct 17 '16 at 16:35
0

Just use the Select-String cmdlet with the -SimpleMatch switch:

# ....
if((Get-Content testfile.txt) | select-string -SimpleMatch $herestring1) {
# ....

-SimpleMatch

Indicates that the cmdlet uses a simple match rather than a regular expression match. In a simple match, Select-String searches the input for the text in the Pattern parameter. It does not interpret the value of the Pattern parameter as a regular expression statement.

Source.

Martin Brandl
  • 56,134
  • 13
  • 133
  • 172