【问题标题】:Bad array subscript error when trying to increment an associative array element尝试递增关联数组元素时出现错误的数组下标错误
【发布时间】:2020-08-25 08:19:43
【问题描述】:

我可以创建一个关联数组并将一个整数分配给其中包含单引号的键:

$ declare -A dict
$ var="john's"
$ dict[$var]=1
$ echo ${dict[$var]}
1
$ declare -p dict
declare -A dict=(["john's"]="1" )

但是当我尝试增加它的值时:

$ (( dict[$var]++ ))
bash: ((: dict[john's]++ : bad array subscript (error token is "dict[john's]++ ")
$ (( dict["$var"]++ ))
bash: ((: dict[john's]++ : bad array subscript (error token is "dict[john's]++ ")
$ (( dict["${var}"]++ ))
bash: ((: dict[john's]++ : bad array subscript (error token is "dict[john's]++ ")

我总是遇到同样的错误。我做错了什么?

【问题讨论】:

    标签: bash associative-array


    【解决方案1】:

    key 名称中的单引号导致解析器将其视为未终止的引号字符。解决此问题的一种方法是转义键中的 ' 字符

    key="john's"
    printf -v escKey "%q" "$key"
    

    现在由于 %q 说明符,printf() 将对所有 shell 元字符应用所需的转义,即使其“被 shell 引用”并且可重复使用。如果您打印 escKey 的内容,您会注意到 ' 已转义

    printf '%s\n' "$escKey"
    john\'s
    

    现在您可以在关联数组中使用此键名。请记住,您始终可以手动添加可能很混乱的转义符。由于%q是shell提供的原生方式,所以使用起来非常安全。

    (( dict["$escKey"]++ ))
    

    同样在bash 版本>= 4.4 parameter expansion 有@Q 这是%q 说明符printf() 的快捷方式,你可以使用它

    (( dict["${key@Q}"]++ ))
    

    【讨论】:

    • 如果使用declare -iA dict 将整数标志添加到关联数组,则可以使用dict[$var]+=1 递增条目,而无需引用/转义键。仅在算术表达式中才需要转义键。很高兴知道这个怪癖。
    • 那些%q / ${var@Q} 不适用于$var 的值,bash 采用$'...' 引用形式。看我的回答。
    【解决方案2】:

    您需要通过转义 $ 来确保 $var 在 Bash 开始将 dict[$var]++ 解析为算术表达式之前不会扩展。

    $ (( dict[\$var]++ ))
    $ declare -p dict
    declare -A dict=(["john's"]="2" )
    

    在较新版本的 Bash 上,启用 assoc_expand_once shell 选项也可以,但当从不受信任的来源读取密钥时,这是不安全。在Stephane's answer 中给出了一个示例,说明即使启用了此选项,shell 仍然可以被欺骗执行带有受污染$var 的任意命令。

    【讨论】:

    【解决方案3】:

    是的,不仅

    (( dict[$var]++ ))
    

    不适用于$var 的某些值,但它也是一个任意命令执行漏洞。

    bash-5.1$ var='1$(uname>&2)'
    bash-5.1$ typeset -A dict
    bash-5.1$ (( dict[$var]++ ))
    Linux
    Linux
    

    用printf %q 或${var@Q} 引用$var 的值不是一个选项,因为对于$var 的某些值,这些运算符使用$'...' 的引用形式,这在算术表达式中是无法理解的:

    bash-5.1$ typeset -A dict
    bash-5.1$ var=$(seq 2)
    bash-5.1$ (( dict[${var@Q}]++ ))
    bash-5.1$ echo "${!dict[@]}"
    $1\n2
    
    bash-5.1$ var=$'\n\'$(uname >&2)],x['
    bash-5.1$ (( dict["${var@Q}"]++ ))
    Linux
    bash: ((: dict[$'\n\'$(uname >&2)],x[']++ : bad array subscript (error token is "x[']++ ")
    

    (仍然是 ACE)。

    使用新的(自 5.0 起)assoc_expand_once 无济于事,因为它仍然无法解决 @/* 值或 ] 字符的问题(因此无法修复ACE 漏洞):

    bash-5.1$ shopt -s assoc_expand_once
    bash-5.1$ var='x],b[1$(uname>&2)'
    bash-5.1$ typeset -A dict
    bash-5.1$ (( dict[$var]++ ))
    Linux
    

    bash(不是ksh93 也不是zsh)特有的另一个问题是 bash 关联数组不支持空键,因此您不能使用任意字符串作为关联数组键。一种解决方法是使用固定字符串为所有键添加前缀/后缀。

    在这里,你可以做到:

    dict[.$var]=$(( ${dict[.$var]} + 1 ))
    

    或者(假设assoc_expand_once 选项未启用):

    let 'dict[.$var]++'
    

    (确保使用单引号)。

    这里使用.作为前缀。

    请参阅 How to use associative arrays safely inside arithmetic expressions? 了解所有血腥细节和更多警告。

    更一般地说,shell 算术表达式是一个雷区。另见:Security Implications of using unsanitized data in Shell Arithmetic evaluation。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-02-12
      • 1970-01-01
      • 2022-01-21
      • 1970-01-01
      • 2020-12-29
      • 1970-01-01
      相关资源
      最近更新 更多