How to define hash tables in Bash?

associative-array, bash, dictionary, hashtable

Solution

Bash 4

Bash 4 natively supports this feature. Make sure your script's hashbang is `#!/usr/bin/env bash` or `#!/bin/bash` so you don't end up using `sh`. Make sure you're either executing your script directly, or execute `script` with `bash script`. (Not actually executing a Bash script with Bash does happen, and will be really confusing!)

You declare an associative array by doing:

declare -A animals

You can fill it up with elements using the normal array assignment operator. For example, if you want to have a map of `animal[sound(key)] = animal(value)`:

animals=( ["moo"]="cow" ["woof"]="dog")

Or declare and instantiate in one line:

declare -A animals=( ["moo"]="cow" ["woof"]="dog")

Then use them just like normal arrays. Use

`animals['key']='value'` to set value

`"${animals[@]}"` to expand the values

`"${!animals[@]}"` (notice the `!`) to expand the keys

Don't forget to quote them:

echo "${animals[moo]}"
for sound in "${!animals[@]}"; do echo "$sound - ${animals[$sound]}"; done

Bash 3

Before bash 4, you don't have associative arrays. Do not use `eval` to emulate them. Avoid `eval` like the plague, because it is the plague of shell scripting. The most important reason is that `eval` treats your data as executable code (there are many other reasons too).

First and foremost: Consider upgrading to bash 4. This will make the whole process much easier for you.

If there's a reason you can't upgrade, `declare` is a far safer option. It does not evaluate data as bash code like `eval` does, and as such does not allow arbitrary code injection quite so easily.

Let's prepare the answer by introducing the concepts:

First, indirection.

$ animals_moo=cow; sound=moo; i="animals_$sound"; echo "${!i}"
cow

Secondly, `declare`:

$ sound=moo; animal=cow; declare "animals_$sound=$animal"; echo "$animals_moo"
cow

Bring them together:

# Set a value:
declare "array_$index=$value"

# Get a value:
arrayGet() { 
    local array=$1 index=$2
    local i="${array}_$index"
    printf '%s' "${!i}"
}

Let's use it:

$ sound=moo
$ animal=cow
$ declare "animals_$sound=$animal"
$ arrayGet animals "$sound"
cow

Note: `declare` cannot be put in a function. Any use of `declare` inside a bash function turns the variable it creates local to the scope of that function, meaning we can't access or modify global arrays with it. (In bash 4 you can use `declare -g` to declare global variables - but in bash 4, you can use associative arrays in the first place, avoiding this workaround.)

Summary:

- Upgrade to bash 4 and use `declare -A` for associative arrays.

- Use the `declare` option if you can't upgrade.

- Consider using `awk` instead and avoid the issue altogether.

Problem

What is the equivalent of Python dictionaries but in Bash (should work across OS X and Linux).

Original source

Related problems