开发者

Powershell: replacing in strings using a hashtable

开发者 https://www.devze.com 2023-04-05 11:46 出处:网络
Okay, so I\'ve set up a hash table with names being what to replace and keys being what to replace with, like this:

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:

  1. 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).

  2. 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 a System.Text.RegularExpressions.Match, so you can use its Value property to index into the $r hashtable.

  3. 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")
    
  4. 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
0

精彩评论

暂无评论...
验证码 换一张
取 消