How to pass an associative array as argument to a function in Bash?

ArraysBashAssociative ArrayAssociative

Arrays Problem Overview


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[@]"

Arrays Solutions


Solution 1 - Arrays

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)" 

Solution 2 - Arrays

If you're using Bash 4.3 or newer, the cleanest way is to pass the associative array by name 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).

Solution 3 - Arrays

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

Solution 4 - Arrays

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" )'

Solution 5 - Arrays

You can only pass associative arrays by name.

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

Solution 6 - Arrays

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

Solution 7 - Arrays

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

Solution 8 - Arrays

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:

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

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?

Solution 9 - Arrays

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionniksfireflyView Question on Stackoverflow
Solution 1 - ArraysFlorian FeldhausView Answer on Stackoverflow
Solution 2 - ArraysTodd LehmanView Answer on Stackoverflow
Solution 3 - ArrayslingView Answer on Stackoverflow
Solution 4 - ArraysOrwellophileView Answer on Stackoverflow
Solution 5 - ArraysaksView Answer on Stackoverflow
Solution 6 - ArraysNickotineView Answer on Stackoverflow
Solution 7 - ArraysSamuel PowellView Answer on Stackoverflow
Solution 8 - ArraysGabriel StaplesView Answer on Stackoverflow
Solution 9 - Arraysl0b0View Answer on Stackoverflow