Search code examples
powershellreplacefile-get-contents

How to replace a single occurrence of a specific word in a text file


I have a configuration.txt file which my script uses to populate variables in my script. The Tex file reads like:

Setting1= Yes
Setting2= Yes
Setting3= Yes

I then populate my variables as such (to pull just Yes or No):

$Setting1=(Get-Content -Path .\configuration.txt | Where-Object {$_ -match 'Setting1='})
$Setting1=($Setting1 -Split '=', 2)[1].trim()

$Setting2=(Get-Content -Path .\configuration.txt | Where-Object {$_ -match 'Setting2='})
$Setting2=($Setting2 -Split '=', 2)[1].trim()

$Setting3=(Get-Content -Path .\configuration.txt | Where-Object {$_ -match 'Setting3='})
$Setting3=($Setting3 -Split '=', 2)[1].trim()

Then I have some logic and if the user, via prompts in my script, change these settings, the script updates the text file with the following code:

((Get-Content -Path .\configuration.txt -Raw | Where-Object {$_ -match 'Setting1'}) -replace $Setting1, $UserSetting1) | Set-Content -Path .\configuration.txt

Where $UserSetting1 is the user's input (either 'yes' or 'no').

Now what this is doing is changing every 'yes' or 'no' in the configuration file instead of only changing the 'yes' or 'no' on the particular line I am trying to use the match function to pull out.

In other words if the user wants to change setting1 to 'no' and keep settings 2 & 3 as a 'yes'; the script is changing all the settings to Yes, instead of only setting1.

I am under the impression that I have to use -Raw in order to perform a -replace, but as stated above, it seems that the -match function doesn't work with the -Raw function...and if I don't use -raw, then my configuration file is re-written with only the single line that I matched.


Solution

  • You could just use the Replace Operator to make an update.

    $UserSetting1 = 'No'
    (Get-Content -Path .\configuration.txt) -replace '(?<=Setting1=).*',$UserSetting1 |
        Set-Content
    

    Since -replace uses a regex matching mechanism, you can construct the match to only return the data you want to replace. The replacement string can be a variable. See below for the regex explanation. It matches the remaining characters on a line after Setting1= and then replaces those characters with the value stored in $UserSetting1. Lines that have no match will be returned as is without replacement.

    • (?<=) is the regex positive lookbehind assertion. It asserts that at a given position in the string that there is an exact match behind it.
    • Setting1= literal match of the string Setting1=.
    • .* as many characters as possible until the end of string (just before the newline character)[1].

    Using the -Raw switch reads the file contents as a single string. If you perform a regex match (-match) on a single string that has a match, $true is returned. The entire string is considered a match and defaults to capture group 0. $matches[0] contains that match. Therefore, if you use -replace to replace a substring within that match, it will replace every instance of that substring that meets the -replace matching criteria.

    If the -Raw switch is not used, then the file contents are read in as an array of lines. -replace alone would still replace every instance of a match. However, you can now use -match and Where-Object to execute different code for certain matched lines.

    Combining -match and -replace without -Raw could lead to only a subset of your read data. As you process each line of the file, -match will essentially filter out any lines that don't return $true before you process any replacements. This scenario is fine for returning the replacement, but can cause issues when you are trying to rewrite an entire file. You would need additional logic to track which array elements you have and have not modified and output them appropriately when you update your content.


    [1] . can match newline characters if the regex mode is single-line. You can enable this mode in the -replace regex by beginning it with (?s).