Increment counter inside Foreach-Object loop

14,049

Solution 1

In your case, you can use the ReadCount property returned by the Get-Content cmdlet:

get-content "artists.txt" | Foreach-Object {$_ | set-content "artists\$($_.ReadCount).txt" }

Solution 2

Martin Brandl's helpful answer provides an effective solution based on the - obscurely named - ReadCount property that Get-Content adds to each input line, which reflects the 1-based line number.

Using a delay-bind script block enables a solution that is both shorter and noticeably faster:

Get-Content artists.txt | Set-Content -LiteralPath { "artists\$($_.ReadCount).txt" }

As for what you tried:

Variable $counter must be incremented in the ForEach-Object command's -Process block, i.e., the block that is executed for each input object (and is typically the only block specified, positionally).

The 2nd script block you passed, {$counter++}, binds to the -End parameter, meaning the block to be executed once, after all pipeline objects have been received.

Therefore, you could have used the following:

$counter = 0
Get-Content artists.txt | Foreach-Object { 
  $_ | Set-Content "artists\$((++$counter)).txt"
}

The increment operation is embedded in the expandable string for brevity, but you could make it a separate statement.

Note the use of an extra pair of (...) around expression ++$counter, so as to ensure that the expression's value is also output; by default, ++ just increments a variable's value, but doesn't produce output.
The outer $(...) - the subexpression operator - is needed in order to embed expressions or commands in expandable strings ("...").

Solution 3

In one-liners, the For-Each object has three positional blocks: begin, process and end:

...<piped objects>... | For-Each {...begin...} {...process...} {...end...}

An easy and general way to set up a counter is:

...<piped objects>... | For-Each {$counter = 0} {...process...} {...end...}

because if you need to reuse the one-liner, the $counter variable will not be reset. You can also add code to the begin block to start the counter at something else as well.

For your particular case, we just have to initialize $counter in a begin block and use mklement0's answer:

get-content "artists.txt" | Foreach-Object {$counter = 0} {$_ | set-content "artists\$((++$counter.txt))"}

You could even use the end block to output a summary:

For-Each {...} {...} {Write-Host "There were $counter lines read and $counter files created"}
Share:
14,049
Sharert Fukae
Author by

Sharert Fukae

Updated on June 04, 2022

Comments

  • Sharert Fukae
    Sharert Fukae almost 2 years

    I want to output each line in a text document into its own document where the output documents are named 1.txt, 2.txt, etc. This is my initial code that reads the text documents and outputs the lines one by one.

    get-content "artists.txt" | Foreach-Object {$_ | set-content "artists\$counter.txt"}
    

    But as you can imagine, this outputs always to the same document.

    I have tried

    get-content "artists.txt" | 
      Foreach-Object {$_ | set-content "artists\$counter.txt" } {$counter++}
    

    as I have seen suggested online and while this increases the counter, it doesn't do what I want (since it still outputs to the same file).

    Not really sure where to insert the counter++ here as inside the Foreach-Object loop itself gives me an error (unless I'm inserting it wrong).

    Thanks.