Bash associative array examples

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

I made a little game: Play Rabbit Escape!

Update: 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)
...

Links

30 thoughts on “Bash associative array examples”

  1. 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

  2. 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).

  3. 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
    $

  4. 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.

  5. 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!

  6. 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).

  7. 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!

  8. 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. :-)

  9. 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)

  10. 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

  11. 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 (C) 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.

  12. 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

  13. 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 <<FRUITS
    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'

  14. 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

  15. 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!

  16. 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.

Leave a Reply

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