在Bash中如何将关联数组作为参数传递给函数?

64

如何将关联数组作为参数传递给函数?在Bash中是否可能实现?

以下代码未按预期工作:

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

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

done

}

像普通数组一样将关联数组传递给函数是不起作用的:

iterateArray "$A_DATA"
或者
iterateArray "$A_DATA[@]"

作为一个开始,可以看这里(我不确定它是否是关联数组并不重要——它可能会有很大的区别,也可能没有任何区别):https://dev59.com/BXNA5IYBdhLWcg3wL6oc - Telemachus
2
@Telemachus:那些技巧行不通,因为数组元素是没有索引传递的。 - Dennis Williamson
@Dennis 这意味着它是一个关联数组确实很重要,对吗?至少,我认为这就是你的评论告诉我的内容。 - Telemachus
@Telemachus:是的,这确实有很大的区别,因为关联数组完全依赖于它们的索引。使用链接问题中显示的技术会丢弃索引,在连续的、数字索引的数组上是可以的,但如果索引很重要(数组在接收函数中被重新索引为连续的),那么在稀疏的、数字索引的数组上也可能会失败。 - Dennis Williamson
以下答案没有回答问题:如何将关联数组作为参数传递给函数? - lecodesportif
这是关于常规bash“索引”数组的相应问题:在bash中将数组作为参数传递 - Gabriel Staples
10个回答

61

如果您正在使用Bash 4.3或更高版本,则最干净的方法是通过名称引用传递关联数组,然后在函数内部使用带有local -n的名称引用来访问它。例如:

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

你不必使用 _ref 后缀;这只是我在这里选择的。只要参考名称与原变量名称不同(否则会出现“循环名称引用”错误),您可以将参考命名为任何您想要的名称。


2
非常感谢。这一定是处理AAs最简单的方法。您为我节省了很多烦恼。 - shahensha
4
我该如何将关联数组传递给另一个脚本? - shahensha
1
我会使用declare -n而不是local -n - Artfaith
2
现在这很有趣!我从未尝试过,因为参考手册似乎表明相反的情况:“nameref属性不能应用于数组变量。”在https://www.gnu.org/software/bash/manual/bash.html但正如伽利略所说……“然而它……可以与数组一起使用!” - Colas Nahaboo
1
@ColasNahaboo,我也看到了!手册上说这在数组上是行不通的!所以我提出了一个后续问题:为什么man bash页面声称declarelocal-n属性"不能应用于数组变量",但实际上却可以? - Gabriel Staples
显示剩余3条评论

51

上周我遇到了完全相同的问题,思考了相当长一段时间。

看起来,关联数组无法被序列化或复制。有一个很好的Bash FAQ条目关于关联数组,其中详细解释了它们。最后一节给了我以下的想法,对我有用:

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

1
注意:映射值中的换行符在函数内部将被替换为空格。 - Werner Lehmann
2
${1#*=}周围加上双引号可以解决空格问题。尽管如此,这对于任意输入来说都不安全。它必须来自declare -p,否则允许任意代码执行。按名称传递的版本更安全。 - Etan Reisner
1
我不明白为什么 ${1#*=} 不应该是常规的 Bash 参数扩展。它是针对参数 $1 和模式 *= 的常规子字符串删除。 - Florian Feldhaus
6
自从 Bash 4.3 版本后,出现了 declare -n 命令。参考这个帖子中的回答:https://dev59.com/_3TYa4cB1Zd3GeqPskTE#30894167。我试过但没成功。 - James Brown
2
我永远不会使用eval。 - Artfaith
显示剩余8条评论

18

基于 Florian Feldhaus 的解决方案:

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

输出结果将为:

doo: 456
pou: 789
mail: ab\npo

这个有效。谢谢。你能解释一下它是如何工作的吗? - KT8
这是唯一一个有效的示例。其他所有示例都给我索引,但没有键。 - Don Rhummy

9

更新,为了完全回答这个问题,这里是我库中的一个小节:

通过引用迭代关联数组

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

这是我之前工作的开发成果,以下是原始内容。

@ffeldhaus - 很好的回答,我借鉴了您的思路:

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

我们可以在array.print函数中删除重复的line array.getbyref,以获得更高的性能收益。 - Gnought
@Gnought - 实际上你不能 :) - Orwellophile

4

你只能通过名称传递关联数组。

将普通数组也通过名称传递更好(更有效率)。


3
在函数中,您可以执行类似于 eval echo "\${$1[$key]}" 的操作,并传入变量的名称,不需要 $ 符号。 - tripleee

3

在bash中传递关联数组的两种方法

对于普通的bash索引数组,请参见我的另外两个答案这里(通过引用)这里(通过值)。要打印数组的值或引用,请参见我的答案这里

1. 手动传递(通过序列化/反序列化)关联数组

还有一种方法:您可以在将关联数组传递给函数时手动序列化关联数组,然后在函数内部将其反序列化为一个新的关联数组:

以下是来自我的eRCaGuy_hello_world存储库的完整可运行示例:

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

示例输出:
  a: cat
  b: dog
  c: mouse

解释:

对于一个名为print_associative_array的函数,其一般形式如下:

# general form
print_associative_array array_length array_keys array_values

对于一个名为array1的数组,以下是如何获取数组的长度、索引(键)和值的方法:
  1. 数组长度:"${#array1[@]}"
  2. 所有数组索引(在这种情况下是键,因为它是一个关联数组):"${!array1[@]}"
  3. 所有数组值:"${array1[@]}"
因此,调用print_associative_array的示例如下:
# example call
#                         length         indices (keys)    values
print_associative_array "${#array1[@]}" "${!array1[@]}" "${array1[@]}"

将数组的长度放在前面是必要的,因为它允许我们在传入的序列化数组到达print_associative_array函数时解析它,该函数位于魔术@数组的所有传入参数中。
为了解析@数组,我们将使用数组分片,其描述如下(此片段从此处我的答案中复制粘贴):
# 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. 【比上面更好的技巧!】通过引用传递数组

...正如Todd Lehman在这里的回答中所解释的那样

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

示例输出:

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

另请参阅:

  1. [我的回答] 一个更详细的演示,展示我如何将常规的“索引”bash数组序列化/反序列化,以便将它们作为参数传递给函数:在bash中传递数组作为参数
  2. [我的回答] 一个演示,展示我如何通过引用传递常规的“索引”bash数组:在bash中传递数组作为参数
  3. [我的回答] 数组切片:Unix & Linux: Bash: 位置参数的切片
  4. [我的问题] 为什么man bash页面声明declarelocal-n属性“不能应用于数组变量”,但实际上可以?
  5. [我的回答] 如何在Bash中手动传递关联数组到子进程并返回

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


我刚刚注意到一个问题...我的数组每行包含6个字段(关键字、数据库主机、数据库用户、数据库密码、数据库名、“多个单词的字符串”),第一个字段是(字符串索引)关键字。上述循环处理每个字段,而不是每一行。有聪明的方式让它处理每一行吗?我发现我必须通过遍历循环来重建数组。这是否是预期的?事实上,我在重建它并添加第六个复合词字符串时遇到了麻烦。当我尝试添加第6个字段时,它会覆盖原始的5个字段行。 - Prisoner 13
1
@Prisoner13,抱歉我忘记了这个。如果你有6个用空格和引号分隔的字段,只需在顶部添加以下内容,就可以得到每行。IFS=$'\n' - Nickotine
3
它只会打印数值。 - Kevin Whitefoot
1
Boo,Python命名法。;-) - Anthony Rutledge
@kevinwhitefoot... 你看到 # 了吗?这是一个注释,井号后面的所有选项用 || 分隔,意思是或者这个选项等等... 尝试一下 fun ${!dict[@]} - Nickotine
显示剩余3条评论

2

今天我想到了一种使用eval echo ...进行间接操作的解决方案:

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

在bash 4.3上的输出:

def: 456
abc: 123

1

很好。由@Todd Lehman描述的简单解决方案,解决了我的关联数组传递问题。我需要将一个整数、一个关联数组和一个索引数组传递给一个函数。

一段时间以前,我读到过在bash中,由于数组不是一级实体,数组不能作为参数传递给函数。显然这并不是全部真相。我刚刚实现了一个处理这些参数的函数,类似于这样...

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

随着本地变量被分配,我必须将位置参数移开,以便最终使用(“$@”)将剩下的内容分配给索引问题数组。
索引数组是必需的,这样我们就可以可靠地迭代所有问题,无论是随机还是有序的顺序。关联数组不是有序的数据结构,因此不适用于任何可预测的迭代。
输出:
current_question: mr robot
current_answer: new york

-1

从有史以来最好的Bash指南中:

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

我认为你的问题在于$@不是关联数组:“@:展开所有位置参数的所有单词。如果使用双引号,它将扩展为所有位置参数的单独单词列表。”


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接