It’s because in your first case, your current shell expands the $HELLO
variable before running the commands. And there’s no HELLO
variable set in your current shell.
env HELLO='Hello World' echo $HELLO
will do this:
- expand any variables given, in this case
$HELLO
- run env with the 3 arguments
'HELLO=Hello World'
,'echo'
and''
(an empty string, since there’s noHELLO
variable set in the current shell) - The
env
command will run and set theHELLO='Hello World'
in its environment env
will runecho
with the argument''
(an empty string)
As you see, the current shell expanded the $HELLO
variable, which isn’t set.
HELLO='Hello World' bash -c 'echo $HELLO'
will do this:
- set the variable
HELLO='Hello World
for the following command - run bash with the 2 arguments
'-c'
and'echo $HELLO'
- since the last argument is enclosed in single quotes, nothing inside it is expanded
- the new bash in turn will run the command
echo $HELLO
- To run echo
$HELLO
in the new bash sub-shell, bash first expands anything it can,$HELLO
in this case, and the parent shell set that toHello World
for us. - The subshell runs
echo 'Hello World'
If you tried to do e.g. this:
env HELLO='Hello World' echo '$HELLO'
- The current shell would expand anything it can, which is nothing since
$HELLO
is enclosed in single quotes - run env with the 3 arguments
'HELLO=Hello World'
,'echo'
and'$HELLO'
- The env command will run and set the
HELLO='Hello World'
in its environment - env will run echo with the argument
'$HELLO'
In this case, there’s no shell that will expand the $HELLO
, so echo
receives the string $HELLO
and prints out that. Variable expansion is done by shells only.