I can create an associative array and assign an integer to a key that contains a single quote in it:
$ declare -A dict
$ var="john's"
$ dict[$var]=1
$ echo ${dict[$var]}
1
$ declare -p dict
declare -A dict=(["john's"]="1" )
But when I try to increment its value:
$ (( 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]++ ")
I always get the same error. What am I doing wrong?
The single quote in key
name is causing the parser to treat it as a un-terminated quote character. One way to fix this would be to escape the '
character in the key
key="john's"
printf -v escKey "%q" "$key"
now because of the %q
specifier, printf()
would have applied required escapes to all shell meta characters i.e. making it "shell-quoted" and re-usable. If you print the contents of escKey
you'll notice the '
escaped
printf '%s\n' "$escKey"
john\'s
Now you can use this keyname in your associative array. Remember you can always manually add the escapes which could be messy. Since %q
is a native way provided by the shell, it is much safe to use it.
(( dict["$escKey"]++ ))
Also in bash
versions >= 4.4 parameter expansion has @Q
which is a shortcut for %q
specifier of printf()
using which you can do
(( dict["${key@Q}"]++ ))
You need to make sure, by escaping $
, that $var
isn't expanded until Bash starts parsing dict[$var]++
as an arithmetic expression.
$ (( dict[\$var]++ ))
$ declare -p dict
declare -A dict=(["john's"]="2" )
On newer versions of Bash, enabling the assoc_expand_once
shell option works too, but this is unsafe when keys are read from an untrusted source. An example of how even when this option is enabled the shell can still be tricked into executing arbitrary commands with a tainted $var
is given in Stephane's answer.
Yes, not only
(( dict[$var]++ ))
Won't work with some values of $var
, but it's also an arbitrary command execution vulnerability.
bash-5.1$ var='1$(uname>&2)'
bash-5.1$ typeset -A dict
bash-5.1$ (( dict[$var]++ ))
Linux
Linux
Quoting the value of $var
with printf %q
or ${var@Q}
is not an option because with some values of $var
, those operators use the $'...'
form of quoting which is not understood in an arithmetic expression:
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[']++ ")
(still an ACE).
Using the new (since 5.0) assoc_expand_once
doesn't help as it still doesn't address the problem with the @
/*
values or the ]
character (and so doesn't fix the ACE vulnerabilities):
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
Another problem specific to bash
(not ksh93
nor zsh
) is that bash associative arrays don't support the empty key, so you just can't use arbitrary strings as associative array keys. A work around is to prefix/suffix all your keys with a fixed string.
Here, you can do:
dict[.$var]=$(( ${dict[.$var]} + 1 ))
Or (assuming the assoc_expand_once
option is not enabled):
let 'dict[.$var]++'
(make sure you use single quotes).
Here using .
as the prefix.
See How to use associative arrays safely inside arithmetic expressions? for all the gory details and more caveats about that.
More generally, shell arithmetic expressions are a minefield. See also: Security Implications of using unsanitized data in Shell Arithmetic evaluation.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With