Code-Specific Demonstration
IFS=:
var=path
path=foo:bar:baz
printf '%s\n' "Example 1: Eval Running Unquoted Echo"
printf ' - %s\n' $(eval echo \$$var)
printf '%s\n' '' "Example 2: Eval Running Quoted Echo"
printf ' - %s\n' $(eval "echo \"\$$var\"")
printf '%s\n' '' "Example 3: Indirect Expansion Syntax With Unquoted Echo"
printf ' - %s\n' $(echo ${!var})
printf '%s\n' '' "Example 4: Indirect Expansion Syntax Without Unquoted Expansions"
printf ' - %s\n' ${!var}
...with the output:
Example 1: Eval Running Unquoted Echo
- foo bar baz
Example 2: Eval Running Quoted Echo
- foo
- bar
- baz
Example 3: Indirect Expansion Syntax With Unquoted Echo
- foo bar baz
Example 4: Indirect Expansion Syntax Without Unquoted Expansions
- foo
- bar
- baz
The difference between the working examples and the broken examples has nothing whatsoever to do with whether eval
is used; it has only anything to do with whether there is an echo
subprocess without quotes.
Generalized Answer
Explaining The Difference: Unquoted Command Substitutions
The difference in your output between the cases given is nothing to do with the indirection, and everything to do with the unquoted expansions; see BashPitfalls #14.
Consider the following example, which uses no indirection at all:
var='foo
bar
baz'
echo "Correct version:"
echo "$var"
echo
echo "Incorrect version:"
echo $var
...its output is:
Correct version:
foo
bar
baz
Incorrect version:
foo bar baz
There's no indirection at all here -- the only difference is that between echo $foo
and echo "$foo"
.
In exactly the same way, echo $(...)
removes your newlines, where echo "$(...)"
would retain them.
So Why Are There Multiple Command Substitution Mechanisms?
Because the ones using eval
are insecure. Really, dangerously, never-ever-use-this insecure.
Consider:
## THIS IS INSECURE; NEVER DO THIS
varname='foo$(touch /tmp/evil)'
foo="Value"
eval "echo \"\$$varname\""
When this is run, it echos Value
-- but also creates /tmp/evil
. It could instead of run any other attacker-chosen command.
Compare to:
varname='foo$(touch /tmp/evil)'
foo="Value"
echo "${!foo}"
...which does not actually run the command substitution. In pretty much any case where shellshock would have been exploitable (ie. where an attacker could have set an arbitrary environment variable value), using the eval
approach would break your security; using the indirection approach is safe(r).