1

So I have a Pipe that will search a file for a specific stream and if found will replace it with a masked value, I am trying to have a counter for all of the times the oldValue is replaced with the newValue. It doesn't necessarily need to be a one liner just curious how you guys would go about this. TIA!

Get-Content -Path $filePath |
    ForEach-Object {
        $_ -replace "$oldValue", "$newValue"
    } |
    Set-Content $filePath
Maximilian Burszley
  • 18,243
  • 4
  • 34
  • 63

2 Answers2

5

I suggest:

  • Reading the entire input file as a single string with Get-Content's -Raw switch.

  • Using -replace / [regex]::Replace() with a script block to determine the substitution text, which allows you to increment a counter variable every time a replacement is made.

Note: Since you're replacing the input file with the results, be sure to make a backup copy first, to be safe.

In PowerShell (Core) 7+, the -replace operator now directly accepts a script block that allows you to determine the substitution text dynamically:

$count = 0
(Get-Content -Raw $filePath) -replace $oldValue, { $newValue; ++$count } |
  Set-Content -NoNewLine $filePath

$count now contains the number of replacements, across all lines (including multiple matches on the same line), that were performed.


In Windows PowerShell, direct use of the underlying .NET API, [regex]::Replace(), is required:

$count = 0
[regex]::Replace(
  '' + (Get-Content -Raw $filePath), 
  $oldValue, 
  { $newValue; ++(Get-Variable count).Value }
) | Set-Content -NoNewLine $filePath

Note:

  • '' + ensures that the call succeeds even if file $filePath has no content at all; without it, [regex]::Replace() would complain about the argument being null.

  • ++(Get-Variable count).Value must be used in order to increment the $count variable in the caller's scope (Get-Variable can retrieve variables defined in ancestral scopes; -Scope 1 is implied here, thanks to PowerShell's dynamic scoping). Unlike with -replace in PowerShell 7+, the script block runs in a child scope.


As an aside:

  • For this use case, the only reason a script block is used is so that the counter variable can be incremented - the substitution text itself is static. See this answer for an example where the substitution text truly needs to be determined dynamically, by deriving it from the match at hand, as passed to the script block.
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • Glad to hear it, @UncommonReefer. This implies that the file was completely empty. Please see my update, which now guards against this case. Also, allow me to give you the standard advice to newcomers in the next comment: – mklement0 Oct 20 '21 at 19:32
0

Changing my answer due to more clarifications in comments. The best way I can think of is to get the count of the $Oldvalue ahead of time. Then replace!

$content = Get-Content -Path $filePath

$toBeReplaced = Select-String -InputObject $content -Pattern $oldValue -AllMatches
$replacedTotal = $toBeReplaced.Matches.Count

$content | ForEach-Object {$_ -replace "$oldValue", "$newValue"} | Set-Content $filePath
austdav0
  • 11
  • 3
  • Thanks for the response @austdav0 I actually had tried something similar and was having issues with doing it that way. Would your function increment count if there were two instances of the oldValue in the same line? Sorry new to stack trying to figure out formatting ha ' function countUpdates{ Param($filePath, $oldValue) $fileToChange = Get-Content $filePath foreach($line in $fileToChange) { if($line -like "*$oldValue*") { $count = $count + 1 } }write-host $count } – UncommonReefer Oct 19 '21 at 20:38
  • wow I feel dumb can't figure out how to add the grey code box lol – UncommonReefer Oct 19 '21 at 20:44
  • Hey @UncommonReefer! i have changed my answer to better fit your solution. I would check for what needs to be replace ahead of time. Then replace. Let me know if you have any questions! – austdav0 Oct 19 '21 at 20:53
  • @UncommonReefer in comments just put code in between backticks – Theo Oct 19 '21 at 21:20