bash中的嵌套关 联数组

问题描述 投票:10回答:3

可以构造一个关联数组,其元素包含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]}

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

bash nested associative-array
3个回答
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函数提取关联数组的元素。有趣的是,您可以编写一个记忆形式的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,我可以通过列出键并修剪具有正确键的键来查看“嵌套数组”的子键字首。因此,如果我想要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))concat(f(key),f(subkey),f(subsubkey))。与f的备注结合使用,效率更高。对于定界符解决方案,当然需要f的嵌套应用程序。

[据我所知,最好的解决方案是对keysubkey值进行简短的哈希处理。


我认识到,人们普遍不喜欢“您做错了,请使用其他工具!”类型的答案。但是bash中的关联数组在许多级别上都是混乱的,当您尝试将代码移植到一个平台(出于某种愚蠢的原因或其他原因)上没有bash或具有较旧的(pre-4 .x)版本。如果您愿意根据您的脚本需求使用另一种语言,则建议您选择一些awk。

它提供了更多功能丰富的语言所带来的灵活性,并简化了外壳脚本的编写。我认为这是一个好主意,原因有几个:

  • GNU awk(最流行的变体)具有成熟的关联数组,可以使用array[key][subkey]的直观语法正确嵌套。>
  • 您可以将awk嵌入到shell脚本中,因此当您确实需要它们时仍可以得到shell的工具
  • awk有时非常简单,这使其与其他Shell替换语言(如Perl和Python)形成鲜明对比。>
  • 并不是说awk没有它的缺点。当您初次学习它时,可能很难理解,因为它非常注重流处理(与sed相似),但是它对于许多几乎不在shell范围之内的任务来说是一个很好的工具。 >

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

这是最好的非骇客方式,但您仅限于访问单个元素。使用间接变量扩展引用是另一种方法,但是您仍然必须将每个元素集存储在数组中。如果您想要某种形式的匿名数组,则需要一个随机参数名称生成器。如果不对数组使用随机名称,则在关联数组上引用它是没有意义的。当然,我不希望使用外部工具来生成随机的匿名变量名称。谁做都会很有趣。

#!/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]}"

对于那些在寻找在命令行参数中传递命令行参数的方法时遇到问题的人,只要消费者同意使用该编码,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>>

8
投票

这是最好的非骇客方式,但您仅限于访问单个元素。使用间接变量扩展引用是另一种方法,但是您仍然必须将每个元素集存储在数组中。如果您想要某种形式的匿名数组,则需要一个随机参数名称生成器。如果不对数组使用随机名称,则在关联数组上引用它是没有意义的。当然,我不希望使用外部工具来生成随机的匿名变量名称。谁做都会很有趣。

#!/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]}"

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[@]}"
© www.soinside.com 2019 - 2024. All rights reserved.