Bash: Iterate over array elements

Human beings always try to keep things ordered and group objects in a box, in a bag but also in folders on a computer system. In programming languages such a grouping is also known as a collection and is mostly done by using arrays, lists or a mix of both if objects of the same type are handled.

As human beings keep a certain order a developer tries to map his programming skills from one language to another. Thus he might think he knows how to use arrays correctly in Bash if he knows how use them in e.g. Java. In some cases this pattern doesn’t work. Especially if something works straightforward in a language but not in a different one.

So once you’re working with arrays in Bash or in Zsh you need to iterate over the elements sooner than later. With your background knowledge you might know that with ${var} the value of a certain variable is accessed. You maybe also know the # character as the amount of operator and thus you probably are writing code as follows.

source=("1" "2" "3" "4" "5")
for ((i=0; i < ${#source}; i++))
do
  echo ${source[$i]}
done

The intention of this snippet was getting the amount of array elements and then looping through the array to output each element to the console. Unfortunately the above script is outputting the first element only and you probably wouldn't know what's causing this misbehavior. The reason for this issue is that ${#source} is not the number of elements but the number of characters of the first element.

To get the correct amount of array elements in Bash or in Zsh you must append the subscript [@] to your variable name which addresses the array itself and not the first element.

source=("1" "2" "3" "4" "5")
for ((i=0; i < ${#source[@]}; i++))
do
  echo ${source[$i]}
done

With this corrected version you'll get what you want, the elements printed to the console. Before leaving this blog and maybe switching to Zsh instead of Bash, be aware that both are only partly compatible. In Zsh arrays are one-indexed which means that the index is starting from 1. For more compatibility issues read the blog post Compatibility between Zsh and Bash.

To get more information about array subscripts read the chapter about arrays in the Advanced Bash-Scripting Guide.

7 Comments

  1. It’s much simpler in Bash and Zsh, if you want only have the element printed. This works in Bash and Zsh

    source = ("1" "2" "3" "4" "5")
    for i in $source
    do
        echo $i
    done
    
  2. slopjong

    That’s correct but as soon as you need the index to operate on two different arrays at the same time your proposal isn’t suited anymore. Consider this code snippet:

        for ((i=0; i < ${#source[@]}; i++))
        do
            source[$i]=${source[$i]/${_updatesite}}
            folder=${source[$i]/\/${noextract[$i]}}
            source[$i]=${source[$i]/${folder}\/}
            install -m644 ${source[$i]} ${_dest}/${folder}
        done 
    

    This is some code of this template of my PKGBUILD generator eclipse-artifacts which does some string manipulation based on two arrays within the same loop.

    Here's an example of the generator's output: eclipse-codecover PKGBUILD.

  3. slopjong

    I’ve just noticed that your snippet is incorrect. First of all there’s a syntax error, ommit the whitespaces in variable initializations. Second your loop runs one iteration only so one must use the subscript.

    Here’s the corrected version of your example:

    source=("1" "2" "3" "4" "5")
    for i in ${source[@]}
    do
      echo $i
    done
    
  4. This works:

    source=""a" "b" "c""
    for i in $source
        do echo $i
    done
    
  5. slopjong

    I’ve played around with that snippet and it seems to be a proper array. Would you please tell me more about what original shell it’s coming from? Is that an old bourne shell style?

  6. kholan

    i’d like to add that slopjong’s syntax works for arrays with whitespace in their elements aswell, which the other one doesen’t (it splits up elements containing whitespace)

  7. slopjong

    @the_metalgamer, your solution simply doesn’t work. For some reason I used the simple solution to send emails but the loop didn’t work properly. Only the first email got processed.

    Bad:

    #!/bin/bash
    
    emails=( "me@example.com" "he@example.com" )
    
    function sendmail()
    {
        for email in $emails
        do
          echo ${emails[$i]}
        done
    }
    
    sendmail
    

    Good:

    #!/bin/bash
    
    emails=( "me@example.com" "he@example.com" )
    
    function sendmail()
    {
        for ((i=0; i < ${#emails[@]}; i++))
        do
          echo ${emails[$i]}
        done
    }
    
    sendmail
    

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>