bash中的嵌套关​​联数组

use*_*001 10 bash associative-array nested

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

a=(a aa)
b=(b bb bbb)
c=(c cc ccc cccc)
Run Code Online (Sandbox Code Playgroud)

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

declare -A letters
letters[a]=$a
letters[b]=$b
letters[c]=$c
Run Code Online (Sandbox Code Playgroud)

然后通过诸如的命令访问各个元素

letter=${letters[a]}
echo ${letter[1]}
Run Code Online (Sandbox Code Playgroud)

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

kon*_*box 8

这是最好的非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]}"
Run Code Online (Sandbox Code Playgroud)


sir*_*sen 6

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

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

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

借用Git处理哈希值的方法,让我们将sha512sums键的前8个字符作为哈希键.如果你对此感到紧张,你总是可以使用整个sha512sum,因为sha512没有已知的碰撞.使用整个校验和确保您是安全的,但它有点繁琐.

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

function keyhash () {
    echo "$1" | sha512sum | cut -c-8
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用相同的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"]}"
}
Run Code Online (Sandbox Code Playgroud)

对给定键进行长度检查会告诉我它在数组中看到了多少层,因为这只是len/8,我可以通过列出键并修剪具有正确前缀的键来查看"嵌套数组"的子键.所以,如果我想要所有的键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
Run Code Online (Sandbox Code Playgroud)

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

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

有了这个,我所知道的最好的解决方案是对值keysubkey值进行简短的哈希.


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

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

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

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

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