check() {
eval "$@" || exit
}
check() {
"$@" || exit
}
ShellCheck found eval
used on an array (or equivalently,
"$@"
). This is problematic because it effectively throws
away all boundary information and rebuilds it from shell words.
Let's say you invoke
check sed -i '$d' "my file.txt"
:
eval "$@"
will:
sed -i $d my file.txt
sed
,
-i
, $d
, my
file.txt
$d
is unset):
sed
, -i
, my
,
file.txt
sed -i 'my' 'file.txt'
"$@"
will
sed -i '$d' 'my file.txt'
Note that while "$@"
is essentially always better than
eval "$@"
, it's easy to unintentionally introduce a
dependency on bad behavior through the shell debugging anti-strategy of
"adding quotes until it works":
# Works with problematic example because of double-escaping, fails with correct example
check ls -l "'My File.txt'"
# Works with correct example the way it was always intended:
check ls -l "My File.txt"
The correct example is still better, but the function invocation has to be tweaked as well.
If each of the array elements is a carefully escaped shell command or
word, use *
instead of @
to explicitly join
the elements on spaces which is what would happen anyways:
on_exit=(
'rm /tmp/myfile; '
'echo "Finished on $(date)" > log.txt; '
)
# Equivalent to `eval "${on_exit[@]}"`, but more explicit
eval "${on_exit[*]}"
# Even better in this case, as it does not require
# semicolons and commands don't interfere:
for cmd in "${on_exit[@]}"
do
eval "$cmd"
done
If you require eval
for another part of the command,
explicitly transform the array into a series of escaped shell words.
This ensures that the array elements will eval
back to
themselves:
# Assumed to be outside of our control,
# otherwise we would output this in an array as well:
COMMAND='dialog --menu "Choose file:" 15 40 4'
# Our array:
array=(
1 "My File.txt"
2 "My Other File.txt"
)
eval "$COMMAND ${array[*]@Q}" # Bash 4+
eval "$COMMAND $(printf "%q " "${array[@]}")" # Bash 1+
ShellCheck is a static analysis tool for shell scripts. This page is part of its documentation.