Increment counter inside Foreach-Object loop
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"}
Sharert Fukae
Updated on June 04, 2022Comments
-
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.