The third form is not at all like the other two — but to understand why, we need to go into the order of operations when bash in interpreting a command, and look at which of those are followed when each method is in use.
Bash Parsing Stages
- Quote Processing
- Splitting Into Commands
- Special Operator Parsing
- Expansions
- Word Splitting
- Globbing
- Execution
Using eval "$string"
eval "$string"
follows all the above steps starting from #1. Thus:
- Literal quotes within the string become syntactic quotes
- Special operators such as
>()
are processed - Expansions such as
$foo
are honored - Results of those expansions are split on characters into whitespace into separate words
- Those words are expanded as globs if they parse as same and have available matches, and finally the command is executed.
Using sh -c "$string"
…performs the same as eval
does, but in a new shell launched as a separate process; thus, changes to variable state, current directory, etc. will expire when this new process exits. (Note, too, that that new shell may be a different interpreter supporting a different language; ie. sh -c "foo"
will not support the same syntax that bash
, ksh
, zsh
, etc. do).
Using $string
…starts at step 5, “Word Splitting”.
What does this mean?
Quotes are not honored.
printf '%s\n' "two words"
will thus parse as printf
%s\n
"two
words"
, as opposed to the usual/expected behavior of printf
%s\n
two words
(with the quotes being consumed by the shell).
Splitting into multiple commands (on ;
s, &
s, or similar) does not take place.
Thus:
s="echo foo && echo bar"
$s
…will emit the following output:
foo && echo bar
…instead of the following, which would otherwise be expected:
foo
bar
Special operators and expansions are not honored.
No $(foo)
, no $foo
, no <(foo)
, etc.
Redirections are not honored.
>foo
or 2>&1
is just another word created by string-splitting, rather than a shell directive.