Why doesn't foo=bar echo "$foo" print bar?
This is subtle, and has to do with the exact order in which the BashParser performs each step.
Many people, when they first learn about var=value command and how it temporarily sets a variable for the duration of a command, eventually work up an example like this one and become confused why it doesn't do what they expect.
As an illustration:
$ unset -v foo $ foo=bar echo "$foo" $ echo "$foo" $ foo=bar; echo "$foo" bar
The reason the first one prints a blank line is because of the order of these steps:
The parameter expansion of $foo is done first. An empty string is substituted for the quoted expression.
After that, Bash sets up a temporary environment and puts foo=bar in it.
The echo command is run, with an empty string as an argument, and foo=bar in its environment. But since echo doesn't care about environment variables, it ignores that, and prints only a newline.
This version works as we expect:
$ unset -v foo $ foo=bar bash -c 'echo "$foo"' bar
In this case, the following steps are performed:
A temporary environment is set up with foo=bar in it.
bash is invoked within that environment, and given -c and echo "$foo" as its two arguments.
The child Bash process expands $foo using the value from the environment and hands that value to echo.
echo receives bar as its only argument, and prints it, plus a newline.
It's not entirely clear, in all cases, why people ask us this question. Mostly they seem to be curious about the behavior, rather than trying to solve a specific problem; so I won't try to give any examples of "the right way to do things like this", since there's no real problem to solve.
* In The Manual — Simple Command Expansion
There are some special cases in Bash where understanding this can be useful. Take the following example:
arr=('Var 1' 'Var 2' 'Var 3' 'Var 4') # join each array element with a ";" # Traditional solution: set IFS, then unset it afterward IFS=\; joinedVariable="${arr[*]}" unset -v IFS # Alternative one: temporarily set IFS for the duration of eval. # Double-quotes work around a Bash bug in versions < 4.3.0 IFS=\; command eval 'JoinedVariable="${arr[*]}"' # Alternative two: a function. Local variables are not POSIX (as of 2017). # Usage: join joinchar [arg ...] join() { local IFS="$1" shift printf '%s\n' "$*" }
Here, the eval alternative is simpler and more elegant than unsetting IFS (In your opinion! -- GreyCat). Appropriate care must be taken to ensure safety when using eval. The command prefix is required for all shells other than Bash plus Bash POSIX mode (see http://wiki.bash-hackers.org/commands/builtin/eval#using_the_environment). This won't work in recent versions of zsh due to an apparent regression (documented behavior broken for setopt POSIX_BUILTINS), or busybox due to a bug in that environment assignments fail to propagate in this case. (feel free to file bugs if anybody cares enough.)