mirror of
https://github.com/koalaman/shellcheck.git
synced 2025-10-03 19:29:44 +08:00
Created SC2294 (markdown)
86
SC2294.md
Normal file
86
SC2294.md
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
## eval negates the benefit of arrays. Drop eval to preserve whitespace/symbols (or eval as string).
|
||||||
|
|
||||||
|
### Problematic code:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
check() {
|
||||||
|
eval "$@" || exit
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Correct code:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
check() {
|
||||||
|
"$@" || exit
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Rationale:
|
||||||
|
|
||||||
|
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:
|
||||||
|
1. Join the elements on spaces: `sed -i $d my file.txt`
|
||||||
|
2. Split the string on shell word boundaries: `sed`, `-i`, `$d`, `my` `file.txt`
|
||||||
|
3. Perform shell expansions (assuming `$d` is unset): `sed`, `-i`, `my`, `file.txt`
|
||||||
|
4. Execute the first element as the command and the rest as its arguments, as if running `sed -i 'my' 'file.txt'`
|
||||||
|
|
||||||
|
`"$@"` will
|
||||||
|
1. Execute the first element as the command and the rest as its arguments, as if running `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.
|
||||||
|
|
||||||
|
### Exceptions:
|
||||||
|
|
||||||
|
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 doput 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+
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Related resources:
|
||||||
|
|
||||||
|
* Help by adding links to BashFAQ, StackOverflow, man pages, POSIX, etc!
|
Reference in New Issue
Block a user