Red Green Repeat Adventures of a Spec Driven Junkie

Bash Scripting: Array Syntax

I find I learn best when I have to write out a post, not only to force me to think through everything in a structured way, but also as a reference for others (and sometimes, myself!)

I will breakdown the concept of arrays in bash using a simple script to clone repositories from github. Instead of writing out each git clone command, I create a script using git clone once and iterate through an array of repository names (keeping things DRY!) As bash’s syntax is foreign to me, I dive deeper in understanding it.

Payoff for you (or future me!) will be an understanding and appreciation for bash script’s array syntax. This article should take you less than six minutes to read.

Double-Eagle Pendant

Introduction

In my previous article, I had worked with loops of values in a bash script and zero padded it.

Now, I want to work with arrays of values that have assigned values to them, specifically, to clone a specific set of git repositories.

I could easily clone them by hand when there are three:

#!/usr/bin/env bash

git clone git@github.com:rubocop-hq/rubocop
git clone git@github.com:rails/rails
git clone git@github.com:rails/rails-dev-box

For three repositories, this approach is fine, but when setting up micro-services where there are can be more than ten with each service has its own repository, this gets tedious. Again, I ask:

Can I do better?

With bash, one way I have done better is to create an array entry of all the repositories and loop through them.

Arrays and loops in bash are a bit funny, at least this format, so I am writing this here to use for future reference.

I will share the code first and then go through each section, I’ll have the following in a file named: clone_repositories.sh

#!/usr/bin/env bash

repository_list=(
  rubocop-hq/rubocop
  rails/rails
  rails/rails-dev-box
)

for repository in ${repository_list[@]}
do
  echo $repository
done

Let’s run and test this out:

$ ./clone_repositories.sh
rubocop-hq/rubocop
rails/rails
rails/rails-dev-box

Remember, I use echo here to test things out, as the key mechanics are with the loops, not the command itself.

To make this into a working script to clone repositories,

#!/usr/bin/env bash

repository_list=(
  rubocop-hq/rubocop
  rails/rails
  rails/rails-dev-box
)

for repository in ${repository_list[@]}
do
  git clone git@github.com:$repository
done
$ ./clone_repositories.sh 
vagrant@ubuntu-xenial:~$ ./clone_repositories.sh
Cloning into 'rubocop'...
Enter passphrase for key '/home/vagrant/.ssh/id_rsa':
remote: Enumerating objects: 87139, done.
remote: Total 87139 (delta 0), reused 0 (delta 0), pack-reused 87139
Receiving objects: 100% (87139/87139), 28.08 MiB | 2.72 MiB/s, done.
Resolving deltas: 100% (65970/65970), done.
Checking connectivity... done.
Cloning into 'rails'...
Enter passphrase for key '/home/vagrant/.ssh/id_rsa':
remote: Enumerating objects: 32, done.
remote: Counting objects: 100% (32/32), done.
remote: Compressing objects: 100% (31/31), done.
remote: Total 698932 (delta 2), reused 5 (delta 1), pack-reused 698900
Receiving objects: 100% (698932/698932), 185.93 MiB | 2.60 MiB/s, done.
Resolving deltas: 100% (513566/513566), done.
Checking connectivity... done.
Cloning into 'rails-dev-box'...
Enter passphrase for key '/home/vagrant/.ssh/id_rsa':
remote: Enumerating objects: 1613, done.
remote: Total 1613 (delta 0), reused 0 (delta 0), pack-reused 1613
Receiving objects: 100% (1613/1613), 855.85 KiB | 0 bytes/s, done.
Resolving deltas: 100% (409/409), done.
Checking connectivity... done.

Remember: Always test out code with echo! PSA: Always use a passphrase with your SSH keys!

Bash Element Highlights

I find these to be the new parts of the above script:

  • Array creation
  • Array access

Other programming languages I know influences my reading and writing of bash scripts, these parts trip me up the most.

Array Creation

To create a variable that’s an array, the bash syntax is:

array_variable=(
  item1
  item2
  item3
)

I find the syntax of other languages familiar, where the format is:

array_variable = [ 
  item1, 
  item2, 
  item3 
]

It looks the same but not having space around = throws me off all the time and using () instead of [] is just different enough to make me always look up a reference.

Array Access

To access an array item within a loop, the syntax is:

for repository in ${repository_list[@]}
do
  # command using $repository here
done

Two pieces of syntax that gets me:

  1. ${}
  2. [@]

Requiring both of these to loop through the array kind of makes my head spin as it’s just foreign enough for me to tripped up.

Bash Syntax: ${}

${} is for parameter expansion - similar to $VARIABLE form, array always require {}.

In the form with braces, ${}, there’s a whole world of options that can use this in bash.

Bash Syntax: [@]

The [@] form directs the array to return all elements, space separated. Put in context of a loop, this makes sense.

Single element access for an array syntax: array[n], is familiar and works here. It’s the first time in a programming language I’ve seen @ as a valid array value. In other languages, the equivalent for this would be to pass the array variable itself.

Conclusion

From breaking down code I found online, I learn there is more bash syntax for arrays:

  • assignment: array_variable=(item1 item2 ...)
  • parameter expansion: ${array_variable}
  • array access: array_variable[@]

Now there is less voodoo and better understanding on how bash arrays and how to use them in a loop. Now I can copy-paste less bash looping code and just write it.