Home > Software engineering >  Incrementing a number each time it replaces a string
Incrementing a number each time it replaces a string

Time:05-31

So I working with a script that looks like this. It works just fine the only issue is that it is counting the line numbers and I just wanted it to replace num each time with 1 2 3 4 etc insted it is looking like this 5 20 25 etc and that is because it seems to be counting and incremnting $c for each line not each time it replaces the string num.

$c=0
(Get-Content C:\Users\H\Desktop\3.txt) | 
Foreach-Object {$_ -replace "num", $((  $c))}  | 
Out-File C:\Users\H\Desktop\4.txt

CodePudding user response:

Try this:

$c = [ref] 0
$text = Get-Content 'C:\Users\H\Desktop\3.txt' -Raw
[regex]::Replace( $text, 'num', { (  $c.Value) } ) |
    Set-Content 'C:\Users\H\Desktop\4.txt'

# With PowerShell 6  you could write:
# (Get-Content 'C:\Users\H\Desktop\3.txt' -Raw) -replace 'num', { (  $c.Value) } |
#    Set-Content 'C:\Users\H\Desktop\4.txt'

Copy-pastable demo:

$text = @'
num foo num
bar num bar
num baz num
'@ 

$c = [ref] 0
[regex]::Replace( $text, 'num', { (  $c.Value) } )

# With PowerShell 6  you could write:
# $text -replace 'num', { (  $c.Value) }

Output:

1 foo 2
bar 3 bar
4 baz 5

Explanation:

  • Use a reference type ([ref]) variable instead of a plain variable. The scriptblock passed to [regex]::Replace() or -replace (PS 6 only) runs in a child scope, so it can't modify variables from the parent scope directly. You can modify the members of a reference type, so this is why the [ref] trick works. Instead of [ref] you could also use a [PSCustomObject] or a [Hashtable] (which are both reference types) with a counter member.
  • The parenthesis around the expression $c.Value are required to output the value of the expression, which doesn't produce output by default. You already had that, I'm just explaining it for other visitors.
  • Using Get-Content with parameter -Raw can be faster, because the file's content gets output as a single multiline string instead of splitting it up into one string per line, at the expense of requiring more memory.

As for what you have tried:

$_ -replace "num", $((  $c))

You are passing an expression instead of a script block as the RHS argument to the -replace operator. Furthermore, a script block argument for the -replace operator is only supported beginning with PowerShell 6. For older versions you have to use the .NET function [Regex]::Replace.

This expression is evaluated once for each iteration of the ForEach-Object "loop", before the -replace operator is evaluated. So you are effectively just counting the lines of the file, not the number of occurences of the pattern.

Only a script block's execution can be delayed. It doesn't get called immediately in the place where you define it1, but when the function or operator that has a [ScriptBlock] parameter decides to call it, possibly multiple times as happens when the pattern has multiple matches. To define a script block, use {} as I did in my sample at the beginning.


[1] Unless you use the call or dot source operator, e.g. &{'foo'}

  • Related