开发者

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

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


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


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


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


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


You can only pass associative arrays by name.

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


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


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


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?


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


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜