15

Like below command,

if true; then
   IFS=":" read a b c d e f <<< "$test"

The book says when value assignment command (IFS ":") is used before main command(read a b c d e f <<< "$value"), its value is effective on the main command temporarily. So, the read command use delimiter :.

But, like this command,

if true; then
   HOME="hello" echo "$HOME"

Echo message is not hello. What's the real meaning of above command?

terdon
  • 242,166
A.Cho
  • 469

3 Answers3

11

This comes down to a question of how evaluation works. Both examples work in the same way, the problem happens because of how the shell (bash, here) expands variables.

When you write this command:

HOME="foo" echo $HOME

The $HOME is expanded before the command is run. Therefore, it is expanded to the original value and not the new one you have set it to for the command. The HOME variable has indeed been changed in the environment that the echo command is running in, however, you are printing the $HOME from the parent.

To illustrate, consider this:

$ HOME="foo" bash -c 'echo $HOME'
foo
$ echo $HOME
/home/terdon

As you can see above, the first command prints the temporarily changed value of HOME and the second prints the original, demonstrating that the variable was only changed temporarily. Because the bash -c ... command is enclosed in single quotes (' ') instead of double ones (" "), the variable is not expanded and is passed as-is to the new bash process. This new process then expands it and prints the new value it has been set to. You can see this happen if you use set -x:

$ set -x
$ HOME="hello" echo "$HOME"
+ HOME=hello
+ echo /home/terdon
/home/terdon

As you can see above, the variable $HOME is never passed to echo. It only sees its expanded value. Compare with:

$ HOME="hello" bash -c 'echo $HOME'
+ HOME=hello
+ bash -c 'echo $HOME'
hello

Here, because of the single quotes, the variable and not its value are passed to the new process.

terdon
  • 242,166
9

When the shell is parsing a line, it will tokenize the line into words, perform various expansions (in order) on the words, then the execute the command.

Suppose test=1:2:3:4:5:6

Let's look at this command: IFS=":" read a b c d e f <<< "$test"

After tokenizing, and parameter expansion occurs: IFS=":" read a b c d e f <<< "1:2:3:4:5:6"

The shell will set the IFS variable for the duration of the read command, and read knows how to apply $IFS to its input, and give values to the variable names.

This command has a similar story, but different outcome: HOME="hello" echo "$HOME"

Since parameter expansion happens before the command begins, the shell has:

HOME="hello" echo "/home/username"

And then, during the execution of the echo command, the new value of $HOME is not used at all.

To achieve what you're trying to do, pick one of

# Delay expansion of the variable until its new value is set
HOME="hello" eval 'echo "$HOME"'

or

# Using a subshell, so the altered env variable does not affect the parent.
# The semicolon means that the variable assignment will occur before
# the variable expansion
(HOME="hello"; echo "$HOME")

but don't pick the first one.

terdon
  • 242,166
glenn jackman
  • 85,964
  • It's probably better to pick the first one. At least it is significantly faster. When eval is the answer, you are sometimes probably asking the wrong question. But if someone must do that for some reasons, changing the answer doesn't make the question itself any less wrong. Another solution is to wrap it in a function and use local. – user23013 Feb 28 '16 at 18:49
  • Why should the eval solution be avoided? –  Feb 29 '16 at 02:10
  • If you don't strictly control the input, you have to be very careful about code you allow other people to inject into your program. – glenn jackman Feb 29 '16 at 02:19
0

There are two scopes: environment variables and local variables. Environment variables are valid for every process (see setenv, getenv), while the local variables are active only within this shell session. (It's not an obvious distinction...)

Implied env (as in your example) modifies the environment, while echo ... uses the local ones - so env has no effect.

To modify the local variables use, say,

( HOME="foo" ; echo "$HOME" )

Here the parentheses define the scope of this assignment.

Andrew
  • 397
  • 2
    This has nothing to do with variable scope, the problem is that the variable is being expanded before it is passed to the child shell. – terdon Feb 28 '16 at 15:29