1

I have this code:

Get-ChildItem FOLDERNAMEHERE *.png| ForEach-Object { $_.Name } > fileNames.txt

It prints off a list of file Names, and I want to change it to out just print out an index of numbers instead of Names.

1
  • 2
    Please upvote this PowerShell GitHub issue which proposes to add an automatic $PSIndex variable. :) Commented May 12, 2022 at 16:25

1 Answer 1

6

To output sequence numbers starting with 1 (replace $i = 0 with $i = -1 to start with 0):

Get-ChildItem FOLDERNAMEHERE *.png |
  ForEach-Object -Begin { $i = 0 } -Process { (++$i) }

Note that the variable $i lives on after this command, and, if it preexisted before running the command, effectively changes it, because the script blocks passed to ForEach-Object run directly in the caller's scope.

The increment assignment operation (++) is wrapped in (...) so as to also output the incremented value (assignment statements produce no output by default).

As an aside:

  • GitHub issue #13772 proposes introducing an automatic index variable for use in pipelines, such as $PSIndex, which would simplify the solution to:

    Get-ChildItem FOLDERNAMEHERE *.png | ForEach-Object { $PSIndex + 1 }
    

Non-streaming alternative, using .., the range operator:

if ($count = (Get-ChildItem FOLDERNAMEHERE *.png).Count) {
  1..$count
}

Note: Non-streaming means that the results of the Get-ChildItem call are collected in full, up front in order to determine the upper limit for the sequence numbers.

Similarly, the 1..$count range operation collects the elements between the range endpoints in an array up front before beginning to produce visible output. See the comments for a discussion.

In other words: Go with the streaming, ForEach-Object-based solution if the Get-ChildItem command produces a lot of output objects and you want to start emitting sequence numbers as quickly as possible.

Sign up to request clarification or add additional context in comments.

10 Comments

What surprised me about the range operator, is the fact that it actually creates an array which is then enumerated. I would have thought that it creates a lazy-evaluated sequence instead. But you get a noticable delay when doing 1..100MB for instance. Compare with [System.Linq.Enumerable]::Range(0, 100MB) which starts to enumerate immediately. I wonder why PowerShell choose that design.
@zett42, that foreach( $i in 0..100MB ) { $i } effectively streams is surprising, given that streaming command output is collected in full, up front in foreach statements; e.g.: foreach ($f in Get-ChildItem $HOME -File -Recurse -ErrorAction Ignore) { $f; pause } Therefore, in the case at hand, if a Get-ChildItem call determines the number of sequence numbers to produce, using the pipeline with ForEach-Object is your only option if you want to avoid a delay stemming from up-front collection.
@zett42 that's an interesting question, I was unaware using a loop would start enumerating immediately, 0..[int]::MaxValue will instantly throw but foreach($i in 0..[int]::MaxValue) { $i } enumerates
Correction, @Santiago; Streaming use effectively permits [uint]::MaxValue + 1 elements, namely: ([int]::MinValue)..([int]::MaxValue) | % { $_; pause }
@SantiagoSquarzon Another proof that foreach enumerates ranges: foreach($i in 0..0) { $foreach.GetType().Name } prints RangeEnumerator. Compare with foreach($i in (0..0)) { $foreach.GetType().Name } which prints ArrayEnumerator.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.