Learn Docker With My Newest Course

Dive into Docker takes you from "What is Docker?" to confidently applying Docker to your own projects. It's packed with best practices and examples. Start Learning Docker →

Associative Arrays in Bash (AKA Key-Value Dictionaries)

blog/cards/associative-arrays-in-bash-aka-key-value-dictionaries.jpg

Starting with Bash 4+ you can assign arrays with keys and values. Here's how to declare, update and loop over them.

Quick Jump: Working with Associative Arrays | Real World Use Case | Demo Video

I do try my best to create POSIX compliant shell scripts but once in a while you have to break out and use Bash features. This is one of those cases where it’s completely worth it because trying to do this is a lot more complex otherwise.

Associative arrays are great for when you have a number of key / value pairs that you want to work with, such as looping over them to reduce duplication.

You’ll need Bash 4+ to do this, you can check what you have by running: bash --version

If you’re using macOS, by default you’ll have Bash 3.2 but you can update by running brew install bash. Bash 4 has been available since 2009! It’s available just about everywhere. macOS has an old version due to licensing issues.

Working with Associative Arrays

Let’s go over working with them for a number of common use cases.

Declaring the Variable

declare -A colors

The -A flag denotes that we want an associative array as opposed to -a which is to declare a regular array.

Setting Values (Hard Coded)

colors["red"]="#ff0000"
colors["green"]="#00ff00"
colors["blue"]="#0000ff"

Technically the quotes for keys are optional but I like adding them in case your keys have spaces. In general explicitly using quotes is a good idea in my opinion.

Looking up Values by Key (Hard Coded)

echo "${colors["red"]}"

=> #ff0000

It works as you expect but keep in mind using $colors["red"] will return back [red]. That’s one more reason to always use curly braces when accessing variables!

Setting Values (Variables)

red="r"

colors["${red}"]="#FF0000"

This example is a bit contrived but you get the idea. We’ll see a more general use case of this when we loop over colors.

Looking up Values by Key (Variables)

echo "${colors[${red}]}"

=> #FF0000

Since ${red} is really r that means you can also do ${colors["r"]} to get the same value.

Updating Values by Key

colors["green"]="#0f0"

echo "${colors["green"]}"

=> #0f0

For the most part everything works similar to what you may have seen in other languages.

Deleting Values by Key

unset colors["r"]

echo "${colors["r"]}"
=>

Notice how when unsetting the variable you don’t use $ or curly braces. That’s just how unset works. You pass in the name of the variable, not a reference to it as if you were echoing it. The value is empty because nothing exists there anymore.

Checking if a Key Exists

# This won't echo that it exists because we deleted this key previously.
[ -v colors["r"] ] && echo "'r' exists"

# But this exists so we'll get the echo statement.
[ -v colors["red"] ] && echo "'red' exists"

Listing all Values

echo "${colors[@]}"

=> #0000ff #ff0000 #00ff00

Using @ will list them as an array. Using [*] will convert them to a single string. When echoing them the output will look the same but technically they are different data types.

One gotcha here is notice the ordering of the values. That’s not the same order as we defined them. We defined RGB but it got output as BRG. The ordering is deterministic in the sense that if you ran this 100 times you’ll always get the same order with these keys but it’s not necessarily the order you supplied.

If you want to preserve ordering there’s a couple of solutions in https://stackoverflow.com/a/29161460. I don’t have a preference or opinion here.

If ordering is important I won’t use associative arrays. If something starts to get complex then I’ll use other standard command line tools to help manipulate the data or consider using Python instead of Bash.

Listing all Keys

echo "${!colors[@]}"

=> blue red green

The ! in this context is documented as “indirect expansion”. To be honest this is one of those things where I know to add the ! because that’s what you need to make it work.

In this context is gets the indexes of the array, which in our case are the keys.

Looping over Key / Values

for color in "${!colors[@]}"; do
  echo "${color} => ${colors[${color}]}"
done

# The above produces this output:
blue => #0000ff
red => #ff0000
green => #0f0

Instead of having both the key and value available in the loop we reach in and grab the value by looking it up by key. If you didn’t want to access the value then you wouldn’t reference it, ${color} gives you the key by itself.

Real World Use Case

This came up recently for client work where I wanted to quickly check which Helm versions were available for the charts we use.

Rather than manually check each one I used an associative array with a few loops that ran a couple of different Helm commands. This ended up making it easy to add Helm repos and check a number of chart versions.

We had 6 different charts, but here’s a minimal version to showcase how it works:

declare -A HELM_REPOS

HELM_REPOS["sealed-secrets"]="https://bitnami-labs.github.io/sealed-secrets"
HELM_REPOS["eks"]="https://aws.github.io/eks-charts"
for repo in "${!HELM_REPOS[@]}"; do
  helm repo add "${repo}" "${HELM_REPOS[${repo}]}"
done

for repo in "${!HELM_REPOS[@]}"; do
  echo
  helm repo update "${repo}"
done

for repo in "${!HELM_REPOS[@]}"; do
  echo
  helm search repo "${repo}"
done

The above will add, update and search through all of your repos. In the real solution these loops were put into separate commands. The end result was being able to run ./run helm:search-all-repos and it would perform an update and search in 1 loop.

The add loop had its own command since that’s a 1 off task that’s usually only run when new repos are added.

Now it takes 1 command and about 5 seconds to see if new versions are available to use. A human (AKA me) can review that, assess the changelogs and perform the updates in a controlled manner. These commands are basic but it brings me developer happiness.

Demo Video

Timestamps

  • 0:14 – You need at least Bash 4 to use them
  • 1:02 – Defining the variable
  • 1:21 – Defining our keys and values
  • 1:55 – Looking up a value by key
  • 2:50 – Using a variable as a key name
  • 4:37 – Updating a value for a specific key
  • 5:20 – Deleting a key
  • 6:13 – Checking if a key exists
  • 7:01 – Listing all values
  • 7:50 – Listing all keys
  • 8:02 – Ordering is not guaranteed
  • 10:07 – Looping over the keys and values
  • 12:05 – Going over a real world use case with Helm
  • 14:47 – When was the last time you used an associate array?

How often do you use associative arrays with Bash? Let me know below.

Never Miss a Tip, Trick or Tutorial

Like you, I'm super protective of my inbox, so don't worry about getting spammed. You can expect a few emails per month (at most), and you can 1-click unsubscribe at any time. See what else you'll get too.



Comments