6

I'm trying to obtain the index of the array I'm iterating within a for loop. I am doing something like

arr=( foo bar baz )
for i in $arr; do
  echo "index ${#arr}";
done

But that only gives me the number of elements in the array, not the position of the current value in the array. How to do this?

4 Answers 4

8

Since nobody seems to have suggested the obvious answer, I will.

Why not just use a numeric for-loop as used in mainstream languages like C and Java?

for ((i = 1; i <= $#arr; i++)); do
    echo "Index: $i, value: ${arr[i]}"
done

Or, even shorter (and slower, and possibly a memory hog with large arrays):

for i in {1..$#arr}; do
    echo "Index: $i, value: ${arr[i]}"
done
1
  • The other answer, did suggest a counter "Otherwise you would need to use some kind of counter."
    – Braiam
    Commented Jan 1, 2017 at 22:24
4

You can use the array literal matching along with a flag that returns the current index to do this:

arr=( foo bar baz )
for i in $arr; do
  echo "index ${arr[(ie)$i]}";
done

The i flag returns the index and the e flag modifies i behavior to match the string literally. The expression reads: for arr which match string $i literally return the index. This method has the disadvantage that you can only trust it, if all elements of the array are unique. Otherwise you would need to use some kind of counter.

1
  • 4
    Note that this only works correctly if there are no repeated values in the array. Commented Jan 1, 2017 at 1:18
3

Since zsh 5.0.6, you could expand the array and generate indexes with zipping:

for k v in "${(@)${=${(eQ):-'$( (( $#ary )) && echo {1..$#ary})'}}:^ary}"; do
  [[ -z $k ]] && continue
  echo "index: $k - value: $v"
done

I wouldn't actually use this (out of fear of future self stabbing me), and it will be slow for arrays with an large number of elements. Incrementing an counter would be more efficient since arrays in zsh can't be sparse.

1
  • It's cool, and complicated.
    – DawnSong
    Commented May 11, 2022 at 3:17
-1

A "practical" equivalent (see limitation in comments) but simpler way of doing most of what llua did, using paste and process substitution:

arr=( foo "bar 't' ttt" baz )
paste <(echo {1..$#arr}"\n") <(echo ${^arr}"\n") | head -n -1 | while read i v; do
  echo index $i, val : $v;
done

EDIT : This one works with previous versions of zsh and handles spaces and quotes (well, it does not use a for but the op said "iterate" not "for loop"

arr=( foo "bar 't' ttt" baz )
paste <(echo {1..$#arr}"\n") <(echo ${^arr}"\n") | head -n -1 | while read i v; do
  echo index $i, val : $v;
done

4
  • This does not work with elements that contain whitespace, mine does.
    – llua
    Commented Dec 7, 2019 at 5:52
  • Edited :) thanks for the comment, I did not know shells were that broken ^^' @llua
    – hl037_
    Commented Dec 7, 2019 at 10:40
  • They unfortunately are, the second one doesn't work with the array arr=( foo 'bar ''t'' ttt' baz $'\t' 'qux ' '' ) either. the element containing just a tab character will get stripped by read and will be represented with an empty string and the 'qux ' will also have it's trailing space removed by read too.
    – llua
    Commented Dec 7, 2019 at 18:32
  • Well, fair enough. I added a disclaimer. I think simplicity may sometimes be valuable over completude... Specially with shell scripting that IMHO nowaday should be limited to "McGyver" hacks to do quick batch processing form command line. Any other advanced stuff and script should be written in more modern language such as python (here, coding the input of the for would be a one liner as I will add as a Note)
    – hl037_
    Commented Dec 8, 2019 at 12:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .