Bash associative array examples

October 17, 2012 [bash, Programming Languages, Tech]

Quick reference of things I discovered about how to use associative arrays in bash. Note: bash version 4 only.

(See also Bash Arrays.)

Basics

$ declare -A MYMAP     # Create an associative array
$ MYMAP[foo]=bar       # Put a value into an associative array
$ echo ${MYMAP[foo]}   # Get a value out of an associative array
bar
$ echo MYMAP[foo]      # WRONG
MYMAP[foo]
$ echo $MYMAP[foo]     # WRONG
[foo]

Creating

$ declare -A MYMAP     # Explicitly declare
$ MYMAP[foo]=bar       # Or this line implicitly makes it an associative array (in global scope, bash 4.2+ only)
$ MYMAP[baz]=quux      # Can add multiple values one by one
$ MYMAP[corge]=grault
$ declare -A MYMAP=( [foo]=bar [baz]=quux [corge]=grault ) # Initialise all at once

$ echo ${MYMAP[foo]} bar $ echo ${MYMAP[baz]} quux

$ declare -A MYMAP                              # Or declare separately
$ MYMAP=( [foo]=bar [baz]=quux [corge]=grault ) # Then initialise
$ echo ${MYMAP[foo]}
bar
$ echo ${MYMAP[baz]}
quux

Variables as keys

$ K=baz
$ MYMAP[$K]=quux       # Use a variable as key to put a value into an associative array
$ echo ${MYMAP[$K]}    # Use a variable as key to extract a value from an associative array
quux
$ echo ${MYMAP[baz]}   # Obviously the value is accessible via the literal key
quux

Quoting keys

$ declare -A MYMAP
$ MYMAP[foo A]="bar B"         # Quoting keys makes no difference
$ MYMAP["corge X"]="grault Y"  # Quoting keys makes no difference
$ echo ${MYMAP["corge X"]}     # You can access by quoting
grault Y
$ echo ${MYMAP[corge X]}       # Or not bother
grault Y
$ echo ${MYMAP[foo A]}
bar B

$ MYMAP['waldo 1']="fred 2"    # Single quotes also make no difference
$ echo ${MYMAP['waldo 1']}
fred 2
$ echo ${MYMAP[waldo 1]}
fred 2

$ K=plugh
$ MYMAP['$K']=xyzzy   # Except single quotes prevent variable expansion, as usual
$ echo ${MYMAP[plugh]}

$ echo ${MYMAP['$K']}
xyzzy

Missing keys

$ MYMAP[foo]=bar
$ echo ${MYMAP[missing]}   # Accessing a missing value gives ""

$ # Testing whether a value is missing from an associative array
$ if [ ${MYMAP[foo]+_} ]; then echo "Found"; else echo "Not found"; fi
Found
$ if [ ${MYMAP[missing]+_} ]; then echo "Found"; else echo "Not found"; fi
Not found

Looping

$ declare -A MYMAP=( [foo a]=bar [baz b]=quux )
$ echo "${!MYMAP[@]}"  # Print all keys - quoted, but quotes removed by echo
foo a baz b

$ # Loop through all keys in an associative array
$ for K in "${!MYMAP[@]}"; do echo $K; done
foo a
baz b

$ for K in ${!MYMAP[@]}; do echo $K; done     # WRONG
foo
a
baz
b

$ # Looping through keys and values in an associative array
$ for K in "${!MYMAP[@]}"; do echo $K --- ${MYMAP[$K]}; done
foo a --- bar
baz b --- quux

$ # Loop through all values in an associative array
$ for V in "${MYMAP[@]}"; do echo $V; done
bar
quux

Clearing

$ declare -A MYMAP
$ MYMAP[foo]=bar
$ echo ${MYMAP[foo]}
bar
$ declare -A MYMAP    # Re-declaring DOES NOT clear an associative array
$ echo ${MYMAP[foo]}
bar
$ unset MYMAP         # You need to unset and re-declare to get a cleared associative array
$ declare -A MYMAP
$ echo ${MYMAP[foo]}

Deleting keys

$ MYMAP[foo]=bar
$ echo ${MYMAP[foo]}
bar
$ unset ${MYMAP[foo]} # WRONG
$ echo ${MYMAP[foo]}
bar
$ unset MYMAP[foo]    # To delete from an associative array, use "unset" with similar syntax to assigning
$                     # BUT see next section if key contains spaces
$ echo ${MYMAP[foo]}

$ MYMAP[baz]=quux
$ echo ${MYMAP[baz]}
quux
$ K=baz
$ unset MYMAP[$K]       # Can unset using a variable for the key too
$                       # BUT see next section if variable may contain spaces - always better to quote
$ echo ${MYMAP[baz]}

Deleting keys containing spaces

$ declare -A MYMAP
$ MYMAP[foo Z]=bar
$ echo ${MYMAP[foo Z]}
bar
$ unset MYMAP[foo Z]     # WRONG
bash: unset: `MYMAP[foo': not a valid identifier
bash: unset: `Z]': not a valid identifier
$ unset MYMAP["foo Z"]    # You must quote keys containing spaces when you unset in an associative array
$ echo ${MYMAP[foo Z]}

$ MYMAP[foo Z]=bar
$ unset MYMAP['foo Z']    # Single quotes work too
$ echo ${MYMAP[foo Z]}

$ MYMAP[baz A]=quux
$ echo ${MYMAP[baz A]}
quux
$ K="baz A"
$ unset MYMAP[$K]         # WRONG
bash: unset: `MYMAP[baz': not a valid identifier
bash: unset: `A]': not a valid identifier
$ unset MYMAP["$K"]       # You must quote variables whose values may contain spaces when you unset in an associative array
$ echo ${MYMAP[baz A]}

$ MYMAP[baz A]=quux
$ unset MYMAP['$K']       # Curiously, single quotes work here too, although I'd suggest avoiding them
$ echo ${MYMAP[baz A]}

Length

$ declare -A MYMAP=( [foo a]=bar [baz b]=quux )
$ echo ${#MYMAP[@]}  # Number of keys in an associative array
2
$ echo $#MYMAP[@]  # WRONG
0MYMAP[@]
$ echo ${#MYMAP}   # WRONG
0

Numeric indexing

$ declare -A MYMAP=( [foo a]=bar [baz b]=quux )
$ KEYS=("${!MYMAP[@]}")      # Make a normal array containing all the keys in the associative array
$ echo ${KEYS[0]}            # Find a key via an index
foo a
$ echo ${MYMAP[${KEYS[0]}]}  # Find a value via an index
bar

$ # Loop through using an index
$ for (( I=0; $I < ${#MYMAP[@]}; I+=1 )); do KEY=${KEYS[$I]};  echo $KEY --- ${MYMAP[$KEY]}; done
foo a --- bar
baz b --- quux

Scope

$ unset MYMAP
$ function createmap() { MYMAP[foo]=bar; }  # Implicit creation puts it in the global scope
$ echo ${MYMAP[foo]}

$ createmap
$ echo ${MYMAP[foo]}
bar
$ unset MYMAP

$ function createmaplocal() { declare -A MYMAP; MYMAP[foo]=bar; } # Explicit creation puts it in the local scope $ echo ${MYMAP[foo]}

$ createmaplocal $ echo ${MYMAP[foo]}

Checking Bash version

$ bash --version   # Must be at least version 4 to have associative arrays
GNU bash, version 4.2.24(1)-release (x86_64-pc-linux-gnu)
...

Comments

Walter 2015-06-09

Wow, just have learned how to use associative arrays, via this very handy page! A clear HowTo.
And what I also especially like about it, is that along with examples how to do things, it also gives the examples how to NOT do certain things. It caught me before falling into a few pitfalls: you have predictive mind. :–)

Mark Allan 2015-10-22

Great site… but I am looking for an explanation of the code below?
HOW DOES THIS WORK WITHOUT AN ASSIGN??? I am totally confused, it works, it inits and declares, it’s simple you can see the values but well… it’s like an awk 1 to me??? Except I can’t see the syntax in any manual or search I’ve done.

echo 1 | awk ‘{ sorex["W"]
sorex["B"]
sorex["TH"]
sorex["FR"]
for (i in sorex) print i }’

Many thanks in case you can explain it.

Mark (Manchester)

Andy Balaam 2015-10-23

Hi Mark, that code inside the single quotes is all Awk code, not bash. This might help: https://www.gnu.org/software/gawk/manual/gawk.html

coderofsalvation 2016-02-13

Use this higher order function to prevent the pyramid of doom:

foreach(){
arr="$(declare -p $1)" ; eval "declare -A f="${arr#*=};
for i in ${!f[@]}; do $2 "$i" "${f[$i]}"; done
}

example:

$ bar(){ echo "$1 -> $2"; }
$ declare -A foo["flap"]="three four" foo["flop"]="one two"
$ foreach foo bar
flap -> three four
flop -> one two

Bash association arrays | Jacek Kowalczyk MyBlog 2016-02-24

[…] I found a very good article about bash association arrays. Here is a link to it : http://www.artificialworlds.net/blog/2012/10/... […]

Craig Strickland 2013-07-28

For the benefit of future visitors to this page (like me) that are running pre-4.2 bash, the comment in your statement:

"$ MYMAP[foo]=bar # Or this line implicitly makes it an associative array (in global scope)"

is not true for bash versions <4.2 wherein associative arrays MUST be explicitly created with "declare -A".

If not pre-declared, then your example (if NOT preceded by "declare -A"):

"$ MYMAP[foo]=bar"

implicitly performs arithmetic evaluation of the expression "foo", which produces a numeric result of "0", thereby assigning element "0" of indexed array "MYMAP".

You can see the result:

$ declare -p MYMAP
declare -a MYMAP='([0]="bar")'

The subscript is "0", not the string "foo".

This is in:

$ echo $BASH_VERSION
4.1.5(1)-release

—tgi

Andy Balaam 2013-09-12

Hi Craig, thanks for the very informative addition.

Dave 2016-09-22

I’m confused about scope. The following doesn’t work as I expect. Even though I explicitly declare fruit to be an associative array, and it acts like it inside the while loop, the values added during the loop are not present outside the loop. Thanks for any clarification.

$ cat /tmp/t.bash
#!/bin/bash
echo "a apple" > /tmp/fruit
echo "b banana" >> /tmp/fruit
echo "c cranberry" >> /tmp/fruit

declare -A fruit
fruit[p]=pumpkin
cat /tmp/fruit
| while read line;
do
t=$(echo $line|sed -e ’s/ .//‘);
f=$(echo $line|sed -e ’s/.
//’);
fruit[$t]=$f ;
echo "fruit[$t] = ‘${fruit[${t}]}’; fruit[p]=${fruit[p]}." ;
done

echo ""

echo "fruit[a]=${fruit[‘a’]}"
echo "fruit[b]=${fruit[‘b’]}"
echo "fruit[c]=${fruit[‘c’]}"
$ /tmp/t.bash
fruit[a] = ‘apple’; fruit[p]=pumpkin.
fruit[b] = ‘banana’; fruit[p]=pumpkin.
fruit[c] = ‘cranberry’; fruit[p]=pumpkin.

fruit[a]=
fruit[b]=
fruit[c]=
fruit[p]=pumpkin
$ bash —version
GNU bash, version 4.3.11(1)-release (x86_64-pc-linux-gnu)
Copyright © 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Andy Balaam 2016-09-24

Hi Dave, if you set a variable value inside the do .. done it does not leak out of the scope:

$ cat /tmp/t.bash
#!/bin/bash
echo "a apple" > /tmp/fruit
x=2
cat /tmp/fruit | while read line; do x=3; done
echo $x

$ /tmp/t.bash
2

daveadams 2016-09-24

In order to get the scope to work how you expect, @Dave, you need to invert the operations. It doesn’t work because you are piping the output of cat /tmp/fruit into a while loop, but the second command in the pipe (the while loop) spawns in a new process. So in that subprocess, the variables are being set, but when the while loop terminates the subprocess terminates and the changes to the variables are lost. So in order to do what you want, the while loop needs to be in the process with the rest of the script. So, instead you can do:

$ cat test.sh
#!/bin/bash

cat >/tmp/fruit < a apple
b banana
c cranberry
FRUITS

declare -A fruit=( [p]=pumpkin )

while read t f; do
fruit[$t]="$f"
echo "fruit[$t] = '${fruit[${t}]}'; fruit[p]=${fruit[p]}."
done < /tmp/fruit

echo ""
for i in "${!fruit[@]}"; do
echo "fruit[$i] = '${fruit[$i]}'"
done

$ bash test.sh
fruit[a] = 'apple'; fruit[p]=pumpkin.
fruit[b] = 'banana'; fruit[p]=pumpkin.
fruit[c] = 'cranberry'; fruit[p]=pumpkin.

fruit[a] = 'apple'
fruit[b] = 'banana'
fruit[c] = 'cranberry'
fruit[p] = 'pumpkin'

Andy Balaam 2016-09-27

Excellent, thanks Dave!

mop 2013-11-24

Passing to a function and how to assign between variables (both arrays) is missing IMHO.

Regards, and thank you, Matteo.

Andy Balaam 2013-12-02

Hi Matteo, thanks – yes those would be useful.

Harro Merkus 2013-12-23

Thanks for your post.

I was looking for a way to delete a variable key from an associative array, where that variable may be a single space.
Then these do not work:
unset MYMAP[ ]
unset MYMAP[’ ‘]
unset MYMAP[" "]
K=’ ‘
unset MYMAP[$K]
unset MYMAP["$K"]

However, this one does work:
unset MYMAP[’$K’]

Also, if K is a single or double quote, only the latter one works!

Using GNU bash, version 4.2.25(1)-release (x86_64-pc-linux-gnu).

Andy Balaam 2014-01-02

Hi Harro, thanks – useful information.

Sharon 2017-02-27

Great post!!! Thanks a lot,

Can you please explain why do you add "+_" when you trying to test value existing?
It works for me without this addition:
/home/ubuntu# if [ ${MYMAP[blablabla]} ]; then echo yes; else echo no;fi

no

Andy Balaam 2017-02-27

Hi Sharon, I don’t actually know why I added +_ – I am wondering whether this is an artefact of copying and pasting from somewhere else… Thanks for the comment!

Greywolf 2014-01-07

Default variable test/expansion rules apply:

$ declare -A ax;
$ ax[foo]="xkcd";
$ echo ${ax[foo]:-MISSING};
xkcd
$ echo ${ax[bar]:-MISSING};
MISSING
$ echo ${ax[foo]:+SET};
SET
$

Andy Balaam 2014-01-08

Thanks Greywolf, very helpful.

Sameer Oak 2014-02-11

Thank you very much for such a priceless post.

At present, I’m struggling to find solution to either of the following problems:
1> how to convert a nornal array (indexed array with index starting at 0) into an associative array where value becomes a key and value itself is the value.
2> Create a new assoc array from indexed array where values are keys. And this in a single statement. I know it can very well be done using a loop but for a huge sized array containing almost 500,000 elements,
a loop is an overhead.
3> Create an assoc array from the result of sql query. I normally create an indexed array from the sql query result as below:
mapfile -t a_dummy <<< "$(mysql -u root —disable-column-names —silent -B -e "select * from dummy_tbl;" "$DB_NAME")"
where $DB_NAME is the variable pointing to DB name string.

Thank you very much in advance.

just_an_abstraction 2017-05-09

Really useful, I was stuck declaring an associative implicitly inside a function, apparently you need declare -A for that to work fine. And it apparently stays in local scope too.

Andy Balaam 2017-05-09

Thanks, glad it was useful!

CPRitter 2014-03-17

Andy:
Thanks for the informative write-up! I wish I had found it before I spent an hour figuring it out myself. Now, I was brought to your site while searching for a solution to this …

Is there a less clumsy method of sorting keys than this (spaces in keys must be preserverd)…

bash-4.1$ declare -A ARY=( [fribble]=frabble [grabble]=gribble [co bb le]=cribble [babble]=bibble [zibble]=zabble [n o bbl e]=nibble [mobble]=mibble )
bash-4.1$ keys=( ${!ARY[@]} )
bash-4.1$ IFS=$‘\n’ sorted_keys=( $( echo -e "${keys[@]/%/\n}" | sed -r -e ’s/^ *//’ -e ‘/^$/d’ | sort ) )
bash-4.1$ for key in "${sorted_keys[@]}"; do echo "$key: ${ARY[$key]}"; done
babble: bibble
co bb le: cribble
fribble: frabble
grabble: gribble
mobble: mibble
n o bbl e: nibble
zibble: zabble
bash-4.1$

Thanks!

Andy Balaam 2014-03-18

Hi CPRitter, that looks like a pretty good way to do this, but I think at this point I’d be reaching for Perl or Python…

Mac OS X Bash – upgrade – Open Source Refinery 2017-06-25

[…] Bash associative array examples […]

Doron 2018-01-11

@Sharon, Andy,

Re Missing Keys and the "+_" in the examples: this is in fact quite important, and it’s a good thing you quoted it in this guide.

It differentiates between the case where a key does not exist, and the case where it does exist but its value is null.

To use Sharon’s example, this indeed seems to work:

if [ ${MYMAP[blablabla]} ]; then echo yes; else echo no;fi

no

BUT if you

MYMAP[blablabla]=""

Then

if [ ${MYMAP[blablabla]} ]; then echo yes; else echo no;fi

no

But

if [ ${MYMAP[blablabla]+_} ]; then echo yes; else echo no;fi

yes

Et voila.

Rahul 2018-01-12

Nice Way to show examples.
Other examples of Array Basics Shell Script:
https://blog.prakhar.info/array-basics-shell-...

Avi 2018-12-01

declare -A MYMAP doesn’t work and throws an error:
declare: -A: invalid option
declare: usage: declare [-afFirtx] [-p] [name[=value] …]

using the quotes around the values throws an error like this:
array[wow]: command not found
san francisco

Andy Balaam 2018-12-03

Avi, are you sure you are using bash? I just tried declare -A MYMAP here and it worked.

Will 2014-05-03

I’m jealous of this. Thanks for the write up but would you consider wrapping "bash version 4 only" at the start of the article in strong tags? :) I just bashed (cough) my head against the keyboard for 10 minutes because I’m on bash 3.2.8 (OSX 10.7.5).

david 2019-06-07

Will people ever learn that

bash --version

is not the way to check the version of your current bash? You’re only checking the version of the bash which is found first in your path, not necessarily the one you’re currently running. You can and should use

echo $BASH_VERSION

or just the keyboard shortcut C-x C-v.

Andy Balaam 2019-06-07

Thanks david, good point. I would prefer it phrased less rudely though.

david 2019-06-07

Maybe, but in these attention dearth times formulating in sharply pointed way is often the only way to get people notice and remember. I found the rest of the article quite good, so it was a disappointment to see this tip at the end. As you can guess it was not the first time I saw it, but in an article like this, people will copy it, as you can also see in the comments above.

The problem with such tips is that they will give the right answer most of the time, leading to even more confusion and frustration when they don’t.

Akito 2020-03-09

Amazing! Explains everything about associative arrays in a single article. Answered all my questions at once.

Andy Balaam 2020-03-10

Hi Akito, glad it helped!

Self-Perfection 2020-07-10

There is an error in "Numeric indexing" section
Example
KEYS=(${!MYMAP[@]})

Should actually be
KEYS=("${!MYMAP[@]}")

otherwise keys with spaces would split to separate array items.

Andy Balaam 2020-07-10

Awesome, thank you Self-Perfection – I have fixed it.

Andy Balaam 2014-05-08

Thanks Will, updated. Sorry you can’t use it!

R Sriram 2020-12-02

Thanks a million for the page and examples. It’s been a L.O.N.G time since I went to the net for ‘just bash’ questions (:=), so it was great to hear that bash now has ass.arrays. I used to do a lot of bash+cmdline-perl (perl -e) to do what a simple ass.array in bash could have done. Now, I have my task cut out.
The way you have written the examples is just as one developer talks to another.. Simple, neat, to the point.
Thanks again.

BC 2020-12-19

Nice article – thanks

Associative Arrays in Shell Scripts 2022-06-14

[…] I found a nice, short tutorial on this here. […]

osirisgothra 2014-10-30

Its good to note that:

>item=( [item1]="one" [item2]="two )

if done on a un[define]d variable, will treat it like an -a instead of an -A, which causes the last entry only to be recognized as the first indexer (zero) unless, of course, those items have value,

in the above example, if the variables $item1 and $item2 are un[define]d, then the result would be:

> declare -p item
item=([0]="two")

this happened because undeclared variables have an implicit value of 0 when used as an indexer, it would be so these two lines are identical:

>item=( [item1]="one" [item2]="two )
>declare -p item
item=([0]="two")

>item=( [0]="one" [0]="two )
>declare -p item
item=([0]="two")

As you can see on the second line, the index ‘0’ gets defined twice, of course the last being the final value for that index.

The case is quite different if you have defined values for $item1 and $item2:

>item1=12
>item2=24
>item=( [item1]="one" [item2]="two )

then you get this:

> declare -p item
item=( [12]="one" [24]="two )

and verify it like this:

>echo ${item[12]}
one
>echo ${item[24]}
two

Of course, if you had already had values in the other index 0, it would have been erased by this though not touching index 0 you are still resetting the value of the variable —– unless you used += instead of =. Furthermore, if the values of $item1 and $item2 were not integers (strings), the values would go back to being implicitly 0 again.

This is important because many programmers expect that because integer arrays are implicit, that the associative arrays should be too. And it even appears that way if the array was [declare]d one previously.

As a RULE, it is good to just declare ALL variables. I make it a habit to use "shopt -o -s nounset" in my scripts. However, interactive scripts like .bashrc or completion scripts do not always have this luxury, because it’s a pain to set it, and then unset it, also saving the value which is overhead in the sense of time taken to implement/reimplement each time. In those cases, hopefully the habit of doing it in scripts rubs off on you enough to have it done in the interactive ones as well :)

Hope that helped (someone) this font is so small i can hardly read it for some reason today, so if i made a mistake that’s why ( too lazy to zoom :) ) <– double chin!

Andy Balaam 2014-10-30

Thanks osirisgothra, very helpful.

qwe 2015-01-10

great article!