bash中的嵌套关​​联数组

时间:2014-08-09 17:12:38

标签: bash nested associative-array

可以构造一个关联数组,其元素在bash中包含数组吗?例如,假设有一个具有以下数组:

a=(a aa)
b=(b bb bbb)
c=(c cc ccc cccc)

可以创建一个关联数组来访问这些变量吗?例如,

declare -A letters
letters[a]=$a
letters[b]=$b
letters[c]=$c

然后通过

等命令访问各个元素
letter=${letters[a]}
echo ${letter[1]}

这种用于创建和访问关联数组元素的模拟语法不起作用。是否存在实现相同目标的有效表达式?

3 个答案:

答案 0 :(得分:7)

这是最好的非hacky方式,但你只限于访问单个元素。使用间接变量扩展引用是另一种,但您仍然需要将每个元素集存储在数组中。如果你想要某种形式的匿名数组,你需要有一个随机参数名称生成器。如果你没有为数组使用随机名称,那么在关联数组中引用它是没有意义的。当然,我不喜欢使用外部工具来生成随机匿名变量名。无论谁做到这一点都会很有趣。

#!/bin/bash

a=(a aa)
b=(b bb bbb)
c=(c cc ccc cccc)

declare -A letters

function store_array {
    local var=$1 base_key=$2 values=("${@:3}")
    for i in "${!values[@]}"; do
        eval "$1[\$base_key|$i]=\${values[i]}"
    done
}

store_array letters a "${a[@]}"
store_array letters b "${b[@]}"
store_array letters c "${c[@]}"

echo "${letters[a|1]}"

答案 1 :(得分:6)

我认为更简单的答案是"不,bash数组不能嵌套。" 模拟嵌套数组的任何东西实际上只是为(单层)数组的键空间创建奇特的映射函数。

不是那么糟糕:它可能正是你想要的,但特别是当你不控制你的阵列中的键时,正确地做它会变得更难。 虽然我喜欢@konsolebox给出的使用分隔符的解决方案,但如果您的密钥空间包含"p|q"之类的密钥,它最终会失效。 它有一个很好的好处,因为你可以透明地操作你的密钥,就像在array[abc|def]中查找def中的密钥array[abc]一样,这是非常清晰和可读的。 因为它依赖于不出现在键中的分隔符,所以当您知道键空间现在以及将来的所有代码使用情况时,这只是一种很好的方法。当您严格控制数据时,这只是一个安全的假设。

如果您需要任何类型的健壮性,我建议您连接数组键的哈希值。这是一种非常有可能消除冲突的简单技术,但如果您使用非常精心设计的数据,它们是可行的。

为了从Git处理哈希的方法中借鉴一下,让我们将sha512sums键的前8个字符作为哈希键。 如果你对此感到紧张,你总是可以使用整个sha512sum,因为sha512没有已知的碰撞。 使用整个校验和确保您是安全的,但这有点麻烦。

所以,如果我想要在array[abc][def]中存储元素的语义我应该做的是将值存储在array["$(keyhash "abc")$(keyhash "def")"] keyhash看起来像这样:

function keyhash () {
    echo "$1" | sha512sum | cut -c-8
}

然后,您可以使用相同的keyhash函数拉出关联数组的元素。 有趣的是,你可以编写一个memoized版本的keyhash,它使用一个数组来存储哈希值,防止额外调用sha512sum,但是如果脚本需要很多键,它在内存方面会变得很昂贵:

declare -A keyhash_array
function keyhash () {
    if [ "${keyhash_array["$1"]}" == "" ];
    then
        keyhash_array["$1"]="$(echo "$1" | sha512sum | cut -c-8)"
    fi
    echo "${keyhash_array["$1"]}"
}

对给定键进行长度检查会告诉我它在数组中看到了多少层,因为它只是len/8,我可以看到"嵌套数组的子键&# 34;通过列出键并修剪具有正确前缀的键。 因此,如果我想要array[abc]中的所有密钥,我应该做的是:

for key in "${!array[@]}"
do
    if [[ "$key" == "$(keyhash "abc")"* ]];
    then
        # do stuff with "$key" since it's a key directly into the array
        :
    fi
done

有趣的是,这也意味着第一级密钥有效并且可以包含值。因此,array["$(keyhash "abc")"]完全有效,这意味着这个"嵌套数组"构造可以有一些有趣的语义。

在一种形式或另一种形式中,Bash中嵌套数组的任何解决方案都提取了这个完全相同的技巧:产生一个(希望是单射的)映射函数f(key,subkey),它产生可以用作数组键的字符串。 这可以始终作为f(f(key,subkey),subsubkey)进一步应用,或者在上面keyhash函数的情况下,我更喜欢定义f(key)并将子项应用为concat(f(key),f(subkey))和{{1 }}。 与concat(f(key),f(subkey),f(subsubkey))的memoization结合使用,效率更高。 对于分隔符解决方案,当然需要f的嵌套应用程序。

知道这一点,我所知道的最佳解决方案是对fkey值进行简短的哈希。


我认识到一般不喜欢类型的答案"你做错了,使用这个其他工具!"但是bash中的关联数组在很多层次上都很混乱,当你试图将代码移植到一个平台上时会遇到麻烦(因为某种愚蠢的理由或其他原因)它没有bash,或者有一个古老的(前面的) -4.x)版本。 如果您愿意根据脚本需求调查另一种语言,我建议您选择一些awk。

它提供了shell脚本的简单性以及更多功能丰富的语言带来的灵活性。 我认为这是一个好主意有几个原因:

  • GNU awk(最流行的变体)具有完全成熟的关联数组,可以使用subkey
  • 的直观语法正确嵌套
  • 你可以在shell脚本中嵌入awk,所以当你真正需要它时你仍然可以获得shell的工具
  • awk有时很简单,这与Perl和Python等其他shell替换语言形成鲜明对比

那并不是说awk没有失败。当你第一次学习它时很难理解,因为它主要面向流处理(很像sed),但它是很多任务的好工具在shell的范围之外。

注意上面我说过" GNU awk" (gawk)有多维数组。其他awks实际上是通过明确定义的分隔符array[key][subkey]来分离键的技巧。您可以自己执行此操作,就像使用bash中的SUBSEP解决方案一样,但是如果执行array[a|b],则nawk会内置此功能。它比bash的数组语法更流畅,更清晰。

答案 2 :(得分:0)

对于那些在寻找在命令行参数中传递命令行参数的方法时遇到问题的人,只要消费者同意使用该编码,JSON之类的编码就可能有用。

# Usage: $0 --toolargs '["arg 1", "arg 2"]' --otheropt
toolargs="$2"
v=()
while read -r line; do v+=("${line}"); done < <(jq -r '.[]' <<< "${toolargs}")
sometool "${v[@]}"
nestenc='{"a": ["a", "aa "],
"b": ["b", "bb", "b bb"],
"c d": ["c", "cc ", " ccc", "cc cc"]
}'
index="c d"
letter=()
while read -r line; do
  letter+=("${line}")
done < <(jq -r ".\"${index}\"[]" <<< "${nestenc}") 
for c in "${letter[@]}" ; do echo "<<${c}>>" ; done

输出如下。

<<c>>
<<cc>>
<<ccc>>
<<cc cc>>