64

How do you pass an associative array as an argument to a function? Is this possible in Bash?

The code below is not working as expected:

function iterateArray
{
    local ADATA="${@}"            # associative array

for key in "${!ADATA[@]}"
do
    echo "key - ${key}"
    echo "value: ${ADATA[$key]}"

done

}

Passing associative arrays to a function like normal arrays does not work:

iterateArray "$A_DATA"

or

iterateArray "$A_DATA[@]"
6
  • See here as a start (I'm not honestly sure if it matters that it's an associate array - it may make a big difference or none at all) stackoverflow.com/questions/1063347/…
    – Telemachus
    Commented Nov 1, 2010 at 13:30
  • 2
    @Telemachus: Those techniques won't work since the array elements are being passed without their indices. Commented Nov 1, 2010 at 15:11
  • @Dennis So that means that it does make a big difference that it's an associate array, yes? At least, I think that's what your comment tells me.
    – Telemachus
    Commented Nov 2, 2010 at 12:15
  • @Telemachus: Yes, it does make a big difference since associative arrays are completely dependent on their indices. Using the techniques shown at the linked question discards the index which is OK on a contiguous, numerically-indexed array, but might would also fail on a sparse, numerically-indexed array if the indices are important (the array gets re-indexed contiguously in the receiving function). Commented Nov 2, 2010 at 14:02
  • The answers below don't answer the question: How to pass an associative array as argument to function? Commented Sep 1, 2011 at 15:53

10 Answers 10

62

If you're using Bash 4.3 or newer, the cleanest way is to pass the associative array by name reference and then access it inside your function using a name reference with local -n. For example:

function foo {
    local -n data_ref=$1
    echo ${data_ref[a]} ${data_ref[b]}
}

declare -A data
data[a]="Fred Flintstone"
data[b]="Barney Rubble"
foo data

You don't have to use the _ref suffix; that's just what I picked here. You can call the reference anything you want so long as it's different from the original variable name (otherwise youll get a "circular name reference" error).

8
  • 2
    Thank you so much. This has to be the most simple way of dealing with AAs. You have saved me a lot of angst.
    – shahensha
    Commented Jun 12, 2020 at 3:27
  • 4
    How can I pass an associative array to another script?
    – shahensha
    Commented Jun 30, 2020 at 22:21
  • 1
    I'd use declare -n instead of local -n.
    – Artfaith
    Commented Jun 26, 2021 at 8:30
  • 1
    IMHO Semantic vs stylistic, I'd say, depends on the project. Personally, I wouldn't use both "keywords" at the same time (especially in small code snippets), but only one and while local's functionality is limited, declare provides more features (it's newer). For example, in this example, using less definitions (language words) might highlight the issue better, but it's MHO. Related: stackoverflow.com/a/56628154/5113030 (> They exists because of the history...). For some reason I don't use local, knowing it may confuse another developer when they notice both.
    – Artfaith
    Commented Jun 27, 2021 at 13:12
  • 2
    Now that's interesting! I never tried it because the reference manual seemed to state the opposite: "The nameref attribute cannot be applied to array variables." in gnu.org/software/bash/manual/bash.html But as Galileo said ... "And yet it ... works with arrays!" Commented Dec 15, 2021 at 6:27
51

I had exactly the same problem last week and thought about it for quite a while.

It seems, that associative arrays can't be serialized or copied. There's a good Bash FAQ entry to associative arrays which explains them in detail. The last section gave me the following idea which works for me:

function print_array {
    # eval string into a new associative array
    eval "declare -A func_assoc_array="${1#*=}
    # proof that array was successfully created
    declare -p func_assoc_array
}

# declare an associative array
declare -A assoc_array=(["key1"]="value1" ["key2"]="value2")
# show associative array definition
declare -p assoc_array

# pass associative array in string form to function
print_array "$(declare -p assoc_array)" 
13
  • 1
    Caution: newlines in mapped values are replaced with a space inside the function. Commented May 12, 2012 at 22:47
  • 2
    Extending the double quotes around the ${1#*=} fixes the whitespace issues. That said this isn't at all safe for arbitrary input. It needs to come from declare -p or it allows for arbitrary code execution. The pass-by-name version is safer. Commented Oct 29, 2015 at 1:45
  • 1
    I do not understand why ${1#*=} shouldn't be regular Bash parameter expansion. It's regular substring removal where the parameter is $1 and the pattern is *=. Commented Nov 26, 2015 at 7:21
  • 6
    I couldn't get this to work and apparently since Bash 4.3 there is declare -n. See this answer in another thread: stackoverflow.com/a/30894167/4162356 . Commented Jan 22, 2016 at 12:31
  • 2
    I'd not use eval, ever.
    – Artfaith
    Commented Jun 26, 2021 at 8:31
19

Based on Florian Feldhaus's solution:

# Bash 4+ only
function printAssocArray # ( assocArrayName ) 
{
    var=$(declare -p "$1")
    eval "declare -A _arr="${var#*=}
    for k in "${!_arr[@]}"; do
        echo "$k: ${_arr[$k]}"
    done

}

declare -A conf
conf[pou]=789
conf[mail]="ab\npo"
conf[doo]=456

printAssocArray "conf" 

The output will be:

doo: 456
pou: 789
mail: ab\npo
2
  • This works . Thanks . Can you explain how does it work?
    – KT8
    Commented Nov 21, 2018 at 7:08
  • This was the only example that worked. All the others give me indexes but no keys
    – Don Rhummy
    Commented Jul 31, 2019 at 22:30
9

Update, to fully answer the question, here is an small section from my library:

Iterating an associative array by reference

shopt -s expand_aliases
alias array.getbyref='e="$( declare -p ${1} )"; eval "declare -A E=${e#*=}"'
alias array.foreach='array.keys ${1}; for key in "${KEYS[@]}"'

function array.print {
    array.getbyref
    array.foreach
    do
        echo "$key: ${E[$key]}"
    done
}

function array.keys {
    array.getbyref
    KEYS=(${!E[@]})
}   

# Example usage:
declare -A A=([one]=1 [two]=2 [three]=3)
array.print A

This we a devlopment of my earlier work, which I will leave below.

@ffeldhaus - nice response, I took it and ran with it:

t() 
{
    e="$( declare -p $1 )"
    eval "declare -A E=${e#*=}"
    declare -p E
}

declare -A A='([a]="1" [b]="2" [c]="3" )'
echo -n original declaration:; declare -p A
echo -n running function tst: 
t A

# Output:
# original declaration:declare -A A='([a]="1" [b]="2" [c]="3" )'
# running function tst:declare -A E='([a]="1" [b]="2" [c]="3" )'
2
  • We could remove the duplicate line array.getbyref in array.print function. More performance gain.
    – Gnought
    Commented Jul 2, 2015 at 2:33
  • @Gnought - actually you can't :) Commented Jul 27, 2015 at 4:33
4

You can only pass associative arrays by name.

It's better (more efficient) to pass regular arrays by name also.

1
  • 3
    You would do something like eval echo "\${$1[$key]}" in the function, and pass in the name of the variable, without the $.
    – tripleee
    Commented Sep 6, 2011 at 10:18
3

Two ways to pass associative arrays as parameters in bash

For regular bash indexed arrays, see my two other answers here (by reference) and here (by value). For printing arrays by value or reference, see my answer here.

1. Manual passing (via serialization/deserialization) of the associative array

Here's another way: you can manually serialize the associative array as you pass it to a function, then deserialize it back into a new associative array inside the function:

Here's a full, runnable example from my eRCaGuy_hello_world repo:

array_pass_as_bash_parameter_2_associative.sh:

# Print an associative array using manual serialization/deserialization
# Usage:
#       # General form:
#       print_associative_array array_length array_keys array_values
#
#       # Example:
#       #                          length        indices (keys)    values
#       print_associative_array "${#array1[@]}" "${!array1[@]}" "${array1[@]}"
print_associative_array() {
    i=1

    # read 1st argument, the array length
    array_len="${@:$i:1}"
    ((i++))

    # read all key:value pairs into a new associative array
    declare -A array
    for (( i_key="$i"; i_key<$(($i + "$array_len")); i_key++ )); do
        i_value=$(($i_key + $array_len))
        key="${@:$i_key:1}"
        value="${@:$i_value:1}"
        array["$key"]="$value"
    done

    # print the array by iterating through all of the keys now
    for key in "${!array[@]}"; do
        value="${array["$key"]}"
        echo "  $key: $value"
    done
}

# Let's create and load up an associative array and print it
declare -A array1
array1["a"]="cat"
array1["b"]="dog"
array1["c"]="mouse"

#                         length         indices (keys)    values
print_associative_array "${#array1[@]}" "${!array1[@]}" "${array1[@]}"

Sample output:

  a: cat
  b: dog
  c: mouse

Explanation:

For a given function named print_associative_array, here is the general form:

# general form
print_associative_array array_length array_keys array_values

For an array named array1, here is how to obtain the array length, indices (keys), and values:

  1. array length: "${#array1[@]}"
  2. all of the array indices (keys in this case, since it's an associative array): "${!array1[@]}"
  3. all of the array values: "${array1[@]}"

So, an example call to print_associative_array would look like this:

# example call
#                         length         indices (keys)    values
print_associative_array "${#array1[@]}" "${!array1[@]}" "${array1[@]}"

Putting the length of the array first is essential, as it allows us to parse the incoming serialized array as it arrives into the print_associative_array function inside the magic @ array of all incoming arguments.

To parse the @ array, we'll use array slicing, which is described as follows (this snippet is copy-pasted from my answer here):

# array slicing basic format 1: grab a certain length starting at a certain
# index
echo "${@:2:5}"
#         │ │
#         │ └────> slice length
#         └──────> slice starting index (zero-based)

2. [Better technique than above!] Pass the array by reference

...as @Todd Lehman explains in his answer here

# Print an associative array by passing the array by reference
# Usage:
#       # General form:
#       print_associative_array2 array
#       # Example
#       print_associative_array2 array1
print_associative_array2() {
    # declare a local **reference variable** (hence `-n`) named `array_reference`
    # which is a reference to the value stored in the first parameter
    # passed in
    local -n array_reference="$1"

    # print the array by iterating through all of the keys now
    for key in "${!array_reference[@]}"; do
        value="${array_reference["$key"]}"
        echo "  $key: $value"
    done
}

echo 'print_associative_array2 array1'
print_associative_array2 array1
echo ""
echo "OR (same thing--quotes don't matter in this case):"
echo 'print_associative_array2 "array1"'
print_associative_array2 "array1"

Sample output:

print_associative_array2 array1
  a: cat
  b: dog
  c: mouse

OR (same thing--quotes don't matter in this case):
print_associative_array2 "array1"
  a: cat
  b: dog
  c: mouse

See also:

  1. [my answer] a more-extensive demo of me serializing/deserializing a regular "indexed" bash array in order to pass one or more of them as parameters to a function: Passing arrays as parameters in bash
  2. [my answer] a demo of me passing a regular "indexed" bash array by reference: Passing arrays as parameters in bash
  3. [my answer] array slicing: Unix & Linux: Bash: slice of positional parameters
  4. [my question] Why do the man bash pages state the declare and local -n attribute "cannot be applied to array variables", and yet it can?
  5. [my answer] How to manually pass an associative array to and from a subprocess in Bash
2

Here is a solution I came up with today using eval echo ... to do the indirection:

print_assoc_array() {
    local arr_keys="\${!$1[@]}" # \$ means we only substitute the $1
    local arr_val="\${$1[\"\$k\"]}"
    for k in $(eval echo $arr_keys); do #use eval echo to do the next substitution
        printf "%s: %s\n" "$k" "$(eval echo $arr_val)"
    done
}

declare -A my_arr
my_arr[abc]="123"
my_arr[def]="456"
print_assoc_array my_arr

Outputs on bash 4.3:

def: 456
abc: 123
2

yo:

 #!/bin/bash
   declare -A dict

   dict=(
    [ke]="va"
    [ys]="lu"
    [ye]="es" 
   )

   fun() {
     for i in $@; do
       echo $i
     done
    }

   fun ${dict[@]} # || ${dict[key]} || ${!dict[@] || ${dict[$1]} 

eZ

8
  • You get my vote! This is the simplest, most straightforward answer that actually answers the question - and works. Perhaps some of the heavyweights will take a look at this answer and comment on any potential security risks, expansions, etc. Personally I don't see any, but then I'm not a heavyweight. @Nickotine should add some explanation of the extra parameters that are commented out on the last line. Commented Sep 27, 2017 at 23:27
  • There is one issue I just noticed... my array contains 6 fields per line (key, dbhost, dbuser, dbpasswd, dbname, "String of several words" and the first field is the (string index) key. The above loop processes each field, rather than each line. Any clever ways to have it process each line? I find that I have to rebuild the array by walking through the loop. Is that expected? I am in fact having trouble rebuilding it, and adding the 6th field multi-word string. It overwrites the original 5 field line when try to add the 6th field later. Commented Sep 28, 2017 at 22:21
  • 1
    @Prisoner13, sorry I forgot about this if you've got 6 fields seperated by a space and quoted then just add this at the top and you'll get each line IFS=$'\n'
    – Nickotine
    Commented Jan 12, 2018 at 23:10
  • 3
    It only prints the values. Commented Jul 8, 2020 at 13:06
  • 1
    Boo, Python nomenclature. ;-) Commented Aug 12, 2021 at 5:41
1

Excellent. The simple solution described by @Todd Lehman, solved my associative array passing problem. I had to pass 3 parameters, an integer, an associative array and an indexed array to a function.

A while ago I'd read that since arrays in bash are not first-class entities, arrays could not be passed as arguments to functions. Clearly that's not the whole truth after all. I've just implemented a solution where a function handles those parameters, something like this...

function serve_quiz_question() {
    local index="$1"; shift
    local -n answers_ref=$1; shift
    local questions=( "$@" )

    current_question="${questions[$index]}"
    echo "current_question: $current_question"
    #...
    current_answer="${answers_ref[$current_question]}"
    echo "current_answer: $current_answer"
}

declare -A answers 
answers[braveheart]="scotland"
answers[mr robot]="new york"
answers[tron]="vancouver"
answers[devs]="california"

# integers would actually be assigned to index \
# by iterating over a random sequence, not shown here.
index=2
declare -a questions=( "braveheart" "devs" "mr robot" "tron"  )

serve_quiz_question "$index" answers "${questions[@]}"  

As the local variables get assigned, I had to shift the positional parameters away, in order to end with the ( "$@" ) assigning what's left to the indexed questions array.

The indexed array is needed so that we can reliably iterate over all the questions, either in random or ordered sequence. Associative arrays are not ordered data structures, so are not meant for any sort of predictable iteration.

Output:

current_question: mr robot
current_answer: new york
-1

From the best Bash guide ever:

declare -A fullNames
fullNames=( ["lhunath"]="Maarten Billemont" ["greycat"]="Greg Wooledge" )
for user in "${!fullNames[@]}"
do
    echo "User: $user, full name: ${fullNames[$user]}."
done

I think the issue in your case is that $@ is not an associative array: "@: Expands to all the words of all the positional parameters. If double quoted, it expands to a list of all the positional parameters as individual words."

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.