Is there a bash iterator equivalent to the python enumerate?

Asked

Viewed 77 times

5

It is common situations in which, to iterate, I need not only the elements of a list, but also their respective indexes. In python, this type of iteration is facilitated with the use of iterator enumerate. Example:

for index, element in enumerate(["Walter", "Jesse","Skyler", "Gus"]):
    print("O personagem {} chama-se {}".format(index+1,element))

Returns:

O personagem 1 chama-se Walter
O personagem 2 chama-se Jesse
O personagem 3 chama-se Skyler
O personagem 4 chama-se Gus

In bash, I can do the normal iteration using syntax

for VAR in Walter Jesse Skyler Gus
do
   echo "O nome do personagem é $VAR"
done

But I don’t know how to get the index of each element. There is an equivalent of enumerate bash native?

  • 1

    Ah, for the record, at enumerate you can set the initial value using the parameter start, so you don’t need to add 1 when printing: https://ideone.com/U9bDGE

  • I didn’t know that. Thank you

3 answers

7

You can do it like this:

pessoas=(Walter Jesse Skyler Gus)

for indice in "${!pessoas[@]}"
do
   echo "$indice = ${pessoas[$indice]}"
done

Exit:

0 = Walter
1 = Jesse
2 = Skyler
3 = Gus

Explanations

[@]: Indexed array

!: Access to the Internet

Official reference by clicking here

  • Cool. That’s what it was. But what do the symbols @, $,{ ,} mean, etc ?

  • @Lucas I will edit the question and put the meaning, along with the official reference

5

You can even do it, I just don’t know if it’s as practical as simply accessing the indexes directly, as indicated by another answer.

So let’s take parts. First we start from your array:

pessoas=(Walter Jesse Skyler Gus)

In the documentation we see that there is the syntax ${pessoas[@]} to get the array values and ${!pessoas[@]} to get the indexes. But if I do only echo "${pessoas[@]}", it prints the values on the same line. So I do:

echo "${pessoas[@]}" | tr ' ' '\n'

To swap the spaces for line breaks, so each one stays on a line. And why did I do so? To use the command paste:

paste <(echo "${!pessoas[@]}" | tr ' ' '\n') <(echo "${pessoas[@]}" | tr ' ' '\n')

I put each command echo (one for the indices, another for the values) within <( ), which is called process substitution. In a nutshell, paste takes the output of these commands, line by line, and prints the first line of each of them, then the second line of each, and so on.

The exit will be:

0       Walter
1       Jesse
2       Skyler
3       Gus

Now that I already have the indexes and their values, just read them in a loop:

pessoas=(Walter Jesse Skyler Gus)
paste <(echo "${!pessoas[@]}" | tr ' ' '\n') <(echo "${pessoas[@]}" | tr ' ' '\n') | while read indice valor
do
    echo "$indice=$valor"
done

But as I said at the beginning, it’s quite a turn just to simulate the enumerate. The solution to the other answer seems simpler to me.


Remember that this approach fails if one of the values has space. For example, if it is an array with 3 elements:

pessoas=(Walter Jesse "Gus Fring")

If I use the code above, the tr will replace the space in the third element ("Gus Fring"), separating it into 2 lines.

To avoid this problem, we can exchange the echo by a for which prints the elements one per line:

paste <(for i in "${!pessoas[@]}"; do echo $i ; done) <(for i in "${pessoas[@]}"; do echo $i ; done) | while ... # o restante é igual
  • 1

    Sensational! Thank you

1


By the way, another variant:

i=0;  for a in Harry Ron Voldermort ; do echo "$((++i)): $a"; done

producing the expected

1: Harry
2: Ron
3: Voldermort 

Browser other questions tagged

You are not signed in. Login or sign up in order to post.