Okay, so I've set up a hash table with names being what to replace and keys being what to replace with, like this:
$r = @{
"dog" = "canine";
开发者_开发技巧 "cat" = "feline";
"eric" = "eric cartman"
}
What should I do next? I've tried this:
(Get-Content C:\scripts\test.txt) | Foreach-Object {
foreach ( $e in $r.GetEnumerator() ) {
$_ -replace $e.Name, $e.Value
}
} | Set-Content C:\scripts\test.txt.out
But it doesn't work at all, it just writes each line three times, without replacing anything.
EDIT: Contains of test.txt:
dog
cat
eric
test.txt.out:
dog
dog
dog
cat
cat
cat
eric
eric
eric
Here's one way to do it:
$file = Get-Content C:\scripts\test.txt
foreach ($e in $r) {
$file = $file -replace $e.Name, $e.Value
}
Set-Content -Path C:\scripts\test.txt.out -Value $file
The reason you were seeing each line three times is because of the nested foreach loop. A replace operation was running once per hashtable entry for every line in the file. That doesn't change the source file, but by default it does output the result of the replace (even if nothing is changed).
You can get the desired functionality by reading the file into a variable first, and then using your looping replace to update that variable. You also don't need a separate foreach loop for the file contents; the replace can run against the full text in one pass per hashtable entry.
I got it work this way
foreach ($i in $HashTable.Keys) {
$myString = $myString -replace $i, $HashTable[$i]
}
Depending on your file and hashtable, there are various optimizations you could consider:
You may be able to build a regex from the hashtable key collection like so:
$regexes = $r.keys | foreach {[System.Text.RegularExpressions.Regex]::Escape($_)} $regex = [regex]($r.Keys -join '|')
In doing this you wouldn't to iterate every key, but now you need to know which key you matched in order to get the replacement. On the other hand, it may be faster to do string replacement instead of regex replacement (or something more complex like a string split and join process).
In Powershell you can call the .NET
Regex::Replace
function:string Replace(string input, System.Text.RegularExpressions.MatchEvaluator evaluator)
Calling this method you can define a
MatchEvaluator
with a scriptblock like so:$callback = { $r[$args[0].Value] }
In the scriptblock,
$args[0]
is aSystem.Text.RegularExpressions.Match
, so you can use itsValue
property to index into the$r
hashtable.Get-Content
returns an array of strings which is fine for the-replace
operator, but also implies an extra loop running.[System.IO.File]::ReadAllText
will instead return a single string, so the regex only needs to be parsed once.$file = [System.IO.File]::ReadAllText("C:\scripts\test.txt")
If you used
Get-Content
, to use$regex.Replace
(instead of-replace
) you would need a loop:$file = $file | % { $regex.Replace($_, $callback) }
Since I am not I can use a single replace call:
$file = $regex.Replace($file, $callback)
Thus the full script:
$r = @{
"dog" = "canine";
"cat" = "feline";
"eric" = "eric cartman"
}
$regexes = $r.keys | foreach {[System.Text.RegularExpressions.Regex]::Escape($_)}
$regex = [regex]($regexes -join '|')
$callback = { $r[$args[0].Value] }
$file = [System.IO.File]::ReadAllText("C:\scripts\test.txt")
$file = $regex.Replace($file, $callback)
Set-Content -Path C:\scripts\test.txt.out -Value $file
精彩评论