Consider this snippet:
$ SOMEVAR=AAA
$ echo zzz $SOMEVAR zzz
zzz AAA zzz
Here I've set $SOMEVAR
to AAA
on the first line - and when I echo it on the second line, I get the AAA
contents as expected.
But then, if I try to specify the variable on the same command line as the echo
:
$ SOMEVAR=BBB echo zzz $SOMEVAR zzz
zzz AAA zzz
... I do not get BBB
as I expected - I get the old value (AAA
).
Is this how things are supposed to be? If so, how come then you can specify variables like LD_PRELOAD=/... program args ...
and have it work? What am I missing?
Here's one alternative:
The Problem, Revisited
Quite frankly, the manual is confusing on this point. The GNU Bash manual says:
If you really parse the sentence, what it's saying is that the environment for the command/function is modified, but not the environment for the parent process. So, this will work:
because the environment for the env command has been modified before it executed. However, this will not work:
because of when parameter expansion is performed by the shell.
Interpreter Steps
Another part of the problem is that Bash defines these steps for its interpreter:
What's happening here is that builtins don't get their own execution environment, so they never see the modified environment. In addition, simple commands (e.g. /bin/echo) do get a modified ennvironment (which is why the env example worked) but the shell expansion is taking place in the current environment in step #4.
In other words, you aren't passing 'aaa $TESTVAR ccc' to /bin/echo; you are passing the interpolated string (as expanded in the current environment) to /bin/echo. In this case, since the current environment has no TESTVAR, you are simply passing 'aaa ccc' to the command.
Summary
The documentation could be a lot clearer. Good thing there's Stack Overflow!
See Also
http://www.gnu.org/software/bash/manual/bashref.html#Command-Execution-Environment
What you see is the expected behaviour. The trouble is that the parent shell evaluates
$SOMEVAR
on the command line before it invokes the command with the modified environment. You need to get the evaluation of$SOMEVAR
deferred until after the environment is set.Your immediate options include:
SOMEVAR=BBB eval echo zzz '$SOMEVAR' zzz
.SOMEVAR=BBB sh -c 'echo zzz $SOMEVAR zzz'
.Both these use single quotes to prevent the parent shell from evaluating
$SOMEVAR
; it is only evaluated after it is set in the environment (temporarily, for the duration of the single command).Another option is to use the sub-shell notation (as also suggested by Marcus Kuhn in his answer):
The variable is set only in the sub-shell
The reason is that this sets an environment variable for one line. But,
echo
does not do the expansion,bash
does. Hence, your variable is actually expanded before the command is executed, even thoughSOME_VAR
isBBB
in the context of the echo command.To see the effect, you can do something like:
Here the variable is not expanded until the child process executes, so you see the updated value. if you check
SOME_VARIABLE
again in the parent shell, it's stillAAA
, as expected.Use a ; to separate statements that are on the same line.
To achieve what you want, use
Reason:
You must separate the assignment by semicolon or new line from the next command, otherwise it is not executed before parameter expansion happens for the next command (echo).
You need to make the assignment inside a subshell environment, to make sure it does not persist beyond the current line.
This solution is shorter, neater and more efficient than some of the others suggested, in particular it does not create a new process.