解剖这段代码,解释 - 检查另一个数组的数组子集

时间:2014-10-22 00:37:37

标签: arrays bash expansion

我在wiki.bash-hackers.org找到了这个例子,但是没有解释 细节,所以我希望也许有人在这里,可以对此有所了解,并解释一下 发生了什么。

我理解isSubset函数的第一行,因为它正在传递args,并使用间接 引用,将密钥存储到内部数组xkeysykeys

第二行是设置参数,但我不明白${@/%/[key]}正在做什么? 看起来像替换,将%更改为[key],我不知道这里会发生什么。

然后在下一行中它比较了元素数量的数组,但不应该是相反的, 如果第一个数组有更多元素,则返回1,因为它不能成为第二个数组的子集?

最后[[ ${!2+_} && ${!1} == ${!2} ]] || return 1,非常令人困惑。

isSubset() {
    local -a 'xkeys=("${!'"$1"'[@]}")' 'ykeys=("${!'"$2"'[@]}")'
    set -- "${@/%/[key]}"

    (( ${#xkeys[@]} <= ${#ykeys[@]} )) || return 1

    local key
    for key in "${xkeys[@]}"; do
        [[ ${!2+_} && ${!1} == ${!2} ]] || return 1
    done
}

main() {
    # "a" is a subset of "b"
    local -a 'a=({0..5})' 'b=({0..10})'
    isSubset a b
    echo $? # true

    # "a" contains a key not in "b"
    local -a 'a=([5]=5 {6..11})' 'b=({0..10})'
    isSubset a b
    echo $? # false

    # "a" contains an element whose value != the corresponding member of "b"
    local -a 'a=([5]=5 6 8 9 10)' 'b=({0..10})'
    isSubset a b
    echo $? # false
}

main

2 个答案:

答案 0 :(得分:2)

第二行:

    ${@/%/[key]}

%作为模式的第一个字符表示模式必须在末尾匹配。模式中没有其他内容,因此其含义为&#34;最后用“#key”&#39;&#34;替换空字符串。之后,位置参数如下所示:

    1 = a[key]
    2 = b[key]

下一行:

  

但不应该是反向的,如果第一个数组有更多的元素,则返回1,

但它确实如此。请注意,||运算符已被使用,因此如果条件不符合,它将返回1。条件是:&#34; x.size&lt; = y.size&#34;,如果&#34; x.size&gt;,它将返回1 y.size&#34;

最后:

    [[ ${!2+_} && ${!1} == ${!2} ]] || return 1

说实话,我不知道+_的用途。至于其余部分,请注意我们处于一个带有key变量的循环中。我们的位置变量中也有key,所以:

    ${!1}

变为

    ${a[key])

key变量获取数组a中的键值。因此整个测试验证第二个数组中存在给定键的值:

    [[ ${!2+_} && ...

并且第一个数组中该键的值与第二个数组中该键的值相同:

    ... && ${!1} == ${!2} ]]

当您传递数组a时,第一个条件是必要的,它在索引i处有空字符串而数组b没有索引{{1} }}:

i

答案 1 :(得分:1)

${@/%/[key]}的解释是bash手册页的这一部分:

  

$ {参数/模式/字符串}

     

扩展模式以生成与路径名一样的模式      扩张。参数被扩展并且最长匹配      对其值的替换用字符串替换。如果是Ipattern      以/开头,模式的所有匹配都用字符串替换。      通常只替换第一场比赛。如果模式开始      使用#,它必须在扩展值的开头匹配      参数。如果pattern以%开头,则必须在结尾处匹配      参数的扩展值。如果string为null,则匹配      删除模式,并且可以省略/以下模式      特德。如果参数是@或*,则替换操作为      依次应用于每个位置参数,并进行扩展      是结果列表。如果参数是一个数组变量子      使用@或*编写脚本,替换操作适用于      依次是数组的每个成员,而扩展是      结果清单。

特别是中间位置%。因此,${@/%/[key]}匹配数组中每个值的字符串结尾,并将[key]附加到其中。

假设isSubset isSubset a b a='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5")'b='([0]="0" [1]="1" [2]="2" [3]="3" [4]="4" [5]="5" [6]="6" [7]="7" [8]="8" [9]="9" [10]="10")'isSubsetisSubset() { local -a 'xkeys=("${!'"$1"'[@]}")' 'ykeys=("${!'"$2"'[@]}")' 中发生的事情是这样的:

$1

$2 local -a 'xkeys=("${!a[@]}")' 'ykeys=("${!b[@]}")' 插入上一行,我们得到

${!arr[@]}

扩展为(通过 local -a 'xkeys=(0 1 2 3 4 5)' 'ykeys=(0 1 2 3 4 5 6 7 8 9 10)' 数组索引扩展)

xkeys

此时我们现在传入了数组键的ykeys set -- "${@/%/[key]}" 数组。

$@

回想一下,@=(a b) set -- 'a[key]' 'b[key]' ,并且从上面的手册页代码段中我们知道这变为

set --

@=('a[key]' 'b[key]')然后为我们设置函数位置参数,因此我们有 (( ${#xkeys[@]} <= ${#ykeys[@]} )) || return 1

xkeys

如果ykeys大于set --,那么它就不能成为一个子集,所以要纾困。 (这可以在 local key for key in "${xkeys[@]}"; do 行之前完成,而且我想象的效率会略高一些,但除了最热的循环外,其他任何内容都不太重要。)

xkeys

循环遍历key中的每个键(值为 [[ ${!2+_} && ${!1} == ${!2} ]] || return 1 )。 (注意这里的变量名称,这是至关重要的。)[1]

        [[ ${b[key]+_} && ${a[key]} == ${b[key]} ]] || return 1

更多间接,这次是关于位置参数。以上扩展到

${b[key]+_}
如果b[key]有值,则

${!2}扩展为_,否则为空字符串。 (我不确定为什么这会使用替代值扩展而不仅仅使用set -u而烦恼,但可能有一个原因。面对[[它可能是安全的,或者可能是安全的反对[解释生成的字符串,虽然我不认为它会这样做,但b[key]会有。)因此,此测试在${a[key]} == ${b[key]}具有值时传递,并在其发生时失败不

[]测试两个数组中该索引中的值是否相同,并且当任一部分失败时,整个表达式从函数返回失败。

@danadam正确地解释了,这里也包含这样的关键细节,这里的关键细节是数组查找中的a[key]索引进行变量扩展,因此位置参数1中的a不是寻找&#34;关键&#34;数组a[$key]中的索引,而是 done }

{{1}}

我希望一切都有道理。 (我希望我没事。=)

  1. 真的想要写#34;注意这里的变量名,它是......键。&#34;但随之而来的混乱并不值得开玩笑。