90

Given a list of items in PowerShell, how do I find the index of the current item from within a loop?

For example:

$letters = { 'A', 'B', 'C' }

$letters | % {
  # Can I easily get the index of $_ here?
}

The goal of all of this is that I want to output a collection using Format-Table and add an initial column with the index of the current item. This way people can interactively choose an item to select.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Brian Vallelunga
  • 9,869
  • 15
  • 60
  • 87

6 Answers6

77

.NET has some handy utility methods for this sort of thing in System.Array:

PS> $a = 'a','b','c'
PS> [array]::IndexOf($a, 'b')
1
PS> [array]::IndexOf($a, 'c')
2

Good points on the above approach in the comments. Besides "just" finding an index of an item in an array, given the context of the problem, this is probably more suitable:

$letters = { 'A', 'B', 'C' }
$letters | % {$i=0} {"Value:$_ Index:$i"; $i++}

Foreach (%) can have a Begin sciptblock that executes once. We set an index variable there and then we can reference it in the process scripblock where it gets incremented before exiting the scriptblock.

Keith Hill
  • 194,368
  • 42
  • 353
  • 369
  • 11
    Also you probably don't want to look up each index of an array item while iterating those items. That'd be linear search for every item; sounds like making one iteration O(n^2) :-) – Joey Nov 24 '09 at 07:39
  • `IndexOf` works if your sure you always have an array but the `$letters | % {$i=0}...` method is safe even if `$letters` ends up being only one object and not an array. – Chris Magnuson Jul 12 '18 at 18:37
73

I am not sure it's possible with an "automatic" variable. You can always declare one for yourself and increment it:

$letters = { 'A', 'B', 'C' }
$letters | % {$counter = 0}{...;$counter++}

Or use a for loop instead...

for ($counter=0; $counter -lt $letters.Length; $counter++){...}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Cédric Rup
  • 15,468
  • 3
  • 39
  • 30
  • Example: `> "A,B,C,D,E,F,G" -split "," | % { $i = 0 } { if ( $i -gt 3 ) { $_ }; ++$i }` Output: `E F G` – Andrew Oct 28 '17 at 03:23
  • It worked like a charm when I had to rename files with a counter~ `dir | % {$i = 46}{ move-item $_ ("ARM-{0:00000}.pdf" -f $i++)}` – dance2die Dec 15 '17 at 20:22
  • 1
    The `$letters | % {$counter...` option is safe even if `$letters` is only one object and not an array where as the `for` option doesn't handle that properly. – Chris Magnuson Jul 12 '18 at 18:36
  • 2
    Thanks for this - I'd never seen that syntax with two sets of curly braces following foreach. It solved the problem of matching up two arrays like this: `$selected = $true,$false,$true; @('first','second','third') | % {$i=0}{If($selected[$i]){$_};$i++ } ` to return 'first' and 'third' – Rich Moss Apr 26 '19 at 23:43
  • So the trick here is to use `%{} {}` right? What does the first bracket do? And second? Where can I read on it? Thanks – Shayan May 07 '20 at 13:09
  • 4
    It's a "shortcut" for foreach-object -Begin {first block} -Process {second block}. The first block is run once before processing, the second is run for each item to process. see get-help foreach-item -full for more info – Cédric Rup May 12 '20 at 08:40
57

For PowerShell 3.0 and later, there is one built in :)

foreach ($item in $array) {
    $array.IndexOf($item)
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Chrissy LeMaire
  • 1,367
  • 12
  • 14
  • 68
    This is finding the index of the first item matching every item in the array, not iterating with an index value. This is slower, and if the array contains duplicate items you will get the wrong indexes. – mdonoughe Apr 30 '17 at 19:51
16
0..($letters.count-1) | foreach { "Value: {0}, Index: {1}" -f $letters[$_],$_}
Shay Levy
  • 121,444
  • 32
  • 184
  • 206
2

I found Cédric Rup's answer very helpful but if (like me) you are confused by the '%' syntax/alias, here it is expanded out:

$letters = { 'A', 'B', 'C' }
$letters | ForEach-Object -Begin {$counter = 0} -Process {...;$counter++}
codeulike
  • 22,514
  • 29
  • 120
  • 167
-4

For those coming here from Google like I did, later versions of Powershell have a $foreach automatic variable. You can find the "current" object with $foreach.Current

Jared Forth
  • 1,577
  • 6
  • 17
  • 32
Justin Grote
  • 119
  • 2
  • 3
    most people use `$_` for the current object. Also, OP's question was about finding the index of the current object, not the object itself. – Max Cascone Jun 18 '20 at 21:02