How to pass an associative array as argument to a function in Bash?
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.
![Admin](/assets/logo_square_200-5d0d61d6853298bd2a4fe063103715b4daf2819fc21225efa21dfb93e61952ea.png)
Admin
Updated on July 09, 2022Comments
-
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 almost 13 yearsYou would do something like
eval echo "\${$1[$key]}"
in the function, and pass in the name of the variable, without the$
. -
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 about 12 yearsCaution: newlines in mapped values are replaced with a space inside the function.
-
Gnought almost 9 yearsWe could remove the duplicate line array.getbyref in array.print function. More performance gain.
-
Orwellophile almost 9 years@Gnought - actually you can't :)
-
Etan Reisner over 8 yearsExtending 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 fromdeclare -p
or it allows for arbitrary code execution. The pass-by-name version is safer. -
Noldorin over 8 yearsWhat sort of evil hack is
${1#*=}
? Surely isn't regular Bash parameter expansion! -
Florian Feldhaus over 8 yearsI 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 over 8 yearsI 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 about 8 years@JamesBrown approach should be used for Bash >= 4.3 use
help declare
for more details. You might want to readhelp local
as well. -
Prisoner 13 almost 7 yearsYou 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 over 6 yearsThere 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 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 over 6 yearsis the eval necessary? Couldn't you just do
declare -A local func_assoc_array=${1#*=}
-
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
eval
is better for sure. -
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 over 5 yearsThis works . Thanks . Can you explain how does it work?
-
Don Rhummy almost 5 yearsThis was the only example that worked. All the others give me indexes but no keys
-
shahensha about 4 yearsThank you so much. This has to be the most simple way of dealing with AAs. You have saved me a lot of angst.
-
shahensha almost 4 yearsHow can I pass an associative array to another script?
-
Kevin Whitefoot almost 4 yearsIt only prints the values.
-
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 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 about 3 yearsI'd use
declare -n
instead oflocal -n
. -
Faither about 3 yearsI'd not use eval, ever.
-
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 thanlocal
. Is there a semantic difference or is it only a stylistic difference? -
Faither about 3 yearsIMHO 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 uselocal
, knowing it may confuse another developer when they notice both. -
Anthony Rutledge almost 3 yearsBoo, Python nomenclature. ;-)
-
Anthony Rutledge almost 3 yearsUse of eval. Could be bad, given the right scenario.
-
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… tryfun ${!dict[@]}
-
Colas Nahaboo over 2 yearsNow 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 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 thedeclare
andlocal
-n
attribute "cannot be applied to array variables", and yet it can? -
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.