Reputation: 6408
I am writing a script for customising a configuration file. I want to replace multiple instances of strings within this file, and I tried using PowerShell to do the job.
It works fine for a single replace, but doing multiple replaces is very slow because each time it has to parse the whole file again, and this file is very large. The script looks like this:
$original_file = 'path\filename.abc'
$destination_file = 'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
$_ -replace 'something1', 'something1new'
} | Set-Content $destination_file
I want something like this, but I don't know how to write it:
$original_file = 'path\filename.abc'
$destination_file = 'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
$_ -replace 'something1', 'something1aa'
$_ -replace 'something2', 'something2bb'
$_ -replace 'something3', 'something3cc'
$_ -replace 'something4', 'something4dd'
$_ -replace 'something5', 'something5dsf'
$_ -replace 'something6', 'something6dfsfds'
} | Set-Content $destination_file
Upvotes: 132
Views: 251860
Reputation: 23663
Just a general reusable solution:
function Replace-String {
[CmdletBinding()][OutputType([string])] param(
[Parameter(Mandatory = $True, ValueFromPipeLine = $True)]$InputObject,
[Parameter(Mandatory = $True, Position = 0)][Array]$Pair,
[Alias('CaseSensitive')][switch]$MatchCase
)
for ($i = 0; $i -lt $Pair.get_Count()) {
if ($Pair[$i] -is [Array]) {
$InputObject = $InputObject |Replace-String -MatchCase:$MatchCase $Pair[$i++]
}
else {
$Regex = $Pair[$i++]
$Substitute = if ($i -lt $Pair.get_Count() -and $Pair[$i] -isnot [Array]) { $Pair[$i++] }
if ($MatchCase) { $InputObject = $InputObject -cReplace $Regex, $Substitute }
else { $InputObject = $InputObject -iReplace $Regex, $Substitute }
}
}
$InputObject
}; Set-Alias Replace Replace-String
Usage:
$lookupTable |Replace 'something1', 'something1aa', 'something2', 'something2bb', 'something3', 'something3cc'
or:
$lookupTable |Replace ('something1', 'something1aa'), ('something2', 'something2bb'), ('something3', 'something3cc')
Example:
'hello world' |Replace ('h','H'), ' ', ('w','W')
HelloWorld
Upvotes: 1
Reputation: 2812
With version 3 of PowerShell you can chain the replace calls together:
(Get-Content $sourceFile) | ForEach-Object {
$_.replace('something1', 'something1').replace('somethingElse1', 'somethingElse2')
} | Set-Content $destinationFile
Upvotes: 21
Reputation: 604
To get the post by George Howarth working properly with more than one replacement you need to remove the break, assign the output to a variable ($line) and then output the variable:
$lookupTable = @{
'something1' = 'something1aa'
'something2' = 'something2bb'
'something3' = 'something3cc'
'something4' = 'something4dd'
'something5' = 'something5dsf'
'something6' = 'something6dfsfds'
}
$original_file = 'path\filename.abc'
$destination_file = 'path\filename.abc.new'
Get-Content -Path $original_file | ForEach-Object {
$line = $_
$lookupTable.GetEnumerator() | ForEach-Object {
if ($line -match $_.Key)
{
$line = $line -replace $_.Key, $_.Value
}
}
$line
} | Set-Content -Path $destination_file
Upvotes: 32
Reputation: 2903
Assuming you can only have one 'something1'
or 'something2'
, etc. per line, you can use a lookup table:
$lookupTable = @{
'something1' = 'something1aa'
'something2' = 'something2bb'
'something3' = 'something3cc'
'something4' = 'something4dd'
'something5' = 'something5dsf'
'something6' = 'something6dfsfds'
}
$original_file = 'path\filename.abc'
$destination_file = 'path\filename.abc.new'
Get-Content -Path $original_file | ForEach-Object {
$line = $_
$lookupTable.GetEnumerator() | ForEach-Object {
if ($line -match $_.Key)
{
$line -replace $_.Key, $_.Value
break
}
}
} | Set-Content -Path $destination_file
If you can have more than one of those, just remove the break
in the if
statement.
Upvotes: 14
Reputation: 77530
One option is to chain the -replace
operations together. The `
at the end of each line escapes the newline, causing PowerShell to continue parsing the expression on the next line:
$original_file = 'path\filename.abc'
$destination_file = 'path\filename.abc.new'
(Get-Content $original_file) | Foreach-Object {
$_ -replace 'something1', 'something1aa' `
-replace 'something2', 'something2bb' `
-replace 'something3', 'something3cc' `
-replace 'something4', 'something4dd' `
-replace 'something5', 'something5dsf' `
-replace 'something6', 'something6dfsfds'
} | Set-Content $destination_file
Another option would be to assign an intermediate variable:
$x = $_ -replace 'something1', 'something1aa'
$x = $x -replace 'something2', 'something2bb'
...
$x
Upvotes: 197
Reputation:
A third option, for a pipelined one-liner is to nest the -replaces:
PS> ("ABC" -replace "B","C") -replace "C","D"
ADD
And:
PS> ("ABC" -replace "C","D") -replace "B","C"
ACD
This preserves execution order, is easy to read, and fits neatly into a pipeline. I prefer to use parentheses for explicit control, self-documentation, etc. It works without them, but how far do you trust that?
-Replace is a Comparison Operator, which accepts an object and returns a presumably modified object. This is why you can stack or nest them as shown above.
Please see:
help about_operators
Upvotes: 13