Home > OS >  How to add counter into Powershells ForEach-Object function
How to add counter into Powershells ForEach-Object function

Time:10-20

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

CodePudding user response:

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 the need to use (Get-Variable count).Value in order to increment the $count variable in the caller's scope. 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.

CodePudding user response:

Changing my answer due to more claifications 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
  • Related