尝试递增关联数组元素时出现错误的数组下标错误

Bad array subscript error when trying to increment an associative array element

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

$ 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]++ ")

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

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

中的 ' 字符
key="john's"
printf -v escKey "%q" "$key"

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

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

现在您可以在关联数组中使用这个键名了。请记住,您始终可以手动添加可能会很混乱的转义符。由于 %q 是 shell 提供的本地方式,因此使用它非常安全。

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

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

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

您需要通过转义 $ 来确保 $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 执行任意命令。

是的,不仅如此

(( 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[@]}"
\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 关联数组不支持空键,所以你可以' t 使用任意字符串作为关联数组键。解决方法是 prefix/suffix 使用固定字符串的所有键。

在这里,你可以做:

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.