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

28,175

Solution 1

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 2

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 3

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

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

You can only pass associative arrays by name.

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

Share:
28,175
Admin
Author by

Admin

Updated on July 09, 2022

Comments

  • Admin
    Admin almost 2 years

    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[@]"
    
  • tripleee
    tripleee almost 13 years
    You would do something like eval echo "\${$1[$key]}" in the function, and pass in the name of the variable, without the $.
  • Werner Lehmann
    Werner Lehmann about 12 years
    +1 for the only solution which also works with locals (unlike pass by name). I think Bash 5 should improve on parameter passing constructs...
  • Werner Lehmann
    Werner Lehmann about 12 years
    Caution: newlines in mapped values are replaced with a space inside the function.
  • Gnought
    Gnought almost 9 years
    We could remove the duplicate line array.getbyref in array.print function. More performance gain.
  • Orwellophile
    Orwellophile almost 9 years
    @Gnought - actually you can't :)
  • Etan Reisner
    Etan Reisner over 8 years
    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.
  • Noldorin
    Noldorin over 8 years
    What sort of evil hack is ${1#*=}? Surely isn't regular Bash parameter expansion!
  • Florian Feldhaus
    Florian Feldhaus over 8 years
    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 *=.
  • James Brown
    James Brown over 8 years
    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 .
  • theosp
    theosp about 8 years
    @JamesBrown approach should be used for Bash >= 4.3 use help declare for more details. You might want to read help local as well.
  • Prisoner 13
    Prisoner 13 almost 7 years
    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.
  • Prisoner 13
    Prisoner 13 over 6 years
    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.
  • Nickotine
    Nickotine over 6 years
    @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'
  • solstice333
    solstice333 over 6 years
    is the eval necessary? Couldn't you just do declare -A local func_assoc_array=${1#*=}
  • Florian Feldhaus
    Florian Feldhaus over 6 years
    @solstice333 I tried your suggestion and it seemed to work. I'm not sure if there are any sideeffects of using it, but a solution without using evalis better for sure.
  • Benjamin West
    Benjamin West almost 6 years
    @solstice333 without the eval, there is no declaration. admin@admin:~$ function test () { declare -A ports=${1#*=}; echo -e "\nports = ${ports[@]}\n"; } admin@admin:~$ test "$(declare -p testPorts)" ports = '([extDb]="8900" [pmaDisplay]="9000" [intDb]="3306" )' page22 ${var#pattern} "Use value of var after removing text matching pattern from the left. Remove the shortest matching piece." page 73 "eval forces variable expansion to happen first and then runs the resulting command..." inspirit.net.in/books/linux/…
  • KT8
    KT8 over 5 years
    This works . Thanks . Can you explain how does it work?
  • Don Rhummy
    Don Rhummy almost 5 years
    This was the only example that worked. All the others give me indexes but no keys
  • shahensha
    shahensha about 4 years
    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
    shahensha almost 4 years
    How can I pass an associative array to another script?
  • Kevin Whitefoot
    Kevin Whitefoot almost 4 years
    It only prints the values.
  • Nickotine
    Nickotine almost 4 years
    @KevinWhitefoot Hmm, it’s supposed to print do you see the echo? This is equivalent, all the items in the array get printed 1 by 1, enlighten me if you can please?
  • Kevin Whitefoot
    Kevin Whitefoot almost 4 years
    @Nickotine. There doesn't seem to b a way of formatting code properly in comments but here goes ` kwhit@PonderStibbons ~ $ cat z #!/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]} kwhit@PonderStibbons ~ $ ./z lu va es kwhit@PonderStibbons ~ $ `
  • Faither
    Faither about 3 years
    I'd use declare -n instead of local -n.
  • Faither
    Faither about 3 years
    I'd not use eval, ever.
  • Todd Lehman
    Todd Lehman about 3 years
    @F8ER — Interesting. Just looked that up; didn't realize that declare automatically creates a local variable when it's used in a function. Thanks! But what wasn't clear on the reference I found is how it's better than local. Is there a semantic difference or is it only a stylistic difference?
  • Faither
    Faither about 3 years
    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.
  • Anthony Rutledge
    Anthony Rutledge almost 3 years
    Boo, Python nomenclature. ;-)
  • Anthony Rutledge
    Anthony Rutledge almost 3 years
    Use of eval. Could be bad, given the right scenario.
  • Nickotine
    Nickotine almost 3 years
    @kevinwhitefoot… do you see the #? It’s a comment, all the options after the hash sign separated by || mean or this option etc… try fun ${!dict[@]}
  • Colas Nahaboo
    Colas Nahaboo over 2 years
    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!"
  • Gabriel Staples
    Gabriel Staples over 2 years
    @ColasNahaboo, I saw that too! The manual says this should NOT work on arrays! So here is a follow-up question I just asked: Why do the man bash pages state the declare and local -n attribute "cannot be applied to array variables", and yet it can?
  • Gabriel Staples
    Gabriel Staples about 2 years
    It seems, that associative arrays can't be serialized or copied. This isn't correct. You can serialize them and pass them manually to bash functions, kind of like passing arrays in C, where you need to specify the length of the array too. Here, I demonstrate manually passing associative arrays by value in bash by passing the length, all keys, and all values. I also show passing associative arrays by reference.