4

Running the following code:

a=one; echo $a
a=two echo $a
a=three echo $a >$a

results in

one

one

and a created file with name "one" with content "one"

Why the variable didn't change to two at line 2 and to three at line 3?

Petar D.
  • 307
  • 1
  • 2
  • 18

2 Answers2

10

An assignment does something different when it's used as a command by itself vs. as a prefix to some other command (e.g. echo). When used as a command by itself, it sets a shell variable to that value. When used as a prefix to some other command, it sets an that variable in the environment of the command but not in the shell.

So, look at the first example, a=one; echo $a, where the semicolon makes this two commands on the same line. The first command sets the shell variable a to the value "one", and then for the second command the shell expands a to "one", and then passes that as an argument to echo.

In the second example, a=two echo $a, the assignment is a prefix to the echo command, so echo will be executed with a set to "two" in its environment. But the $a gets expanded by the shell, not by the echo command, and a is still set to "one" as a shell variable, so that value gets used.

The third example, a=three echo $a >$a, is a lot like the second. The shell expands both $as to "one" (since that's the value of the shell variable), then executes echo one with a set to "three" in its environment and output directed to a file named "one".

BTW, there's another complication I haven't mentioned: exporting shell variables. By default, shell variables are not set in the environment of commands that the shell executes. That is, they are shell variables, not environment variables. But if you export a shell variable, it becomes an environment variable and will be inherited by subsequent commands run by that shell. So...

  • This sets a shell variable, which will not be passed on to commands' environments:

    a=one
    
  • This sets an environment variable for this command only, not for the shell or later commands:

    b=two somecommand
    
  • This sets an environment variable in the shell, so it'll be available to the shell and all subsequent commands run from that shell:

    export c=three
    
  • This does the same thing:

    d=four
    export d
    

    You can export shell variables to the environment before, after, or while assigning to them.

Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
3

Run Shellcheck on your script:

In x.sh line 2:
a=two echo $a
^-- SC2097: This assignment is only seen by the forked process.
       ^-- SC2098: This expansion will not see the mentioned assignment.
In x.sh line 3:
a=three echo $a >$a
^-- SC2097: This assignment is only seen by the forked process.
         ^-- SC2098: This expansion will not see the mentioned assignment.
         ^-- SC2094: Make sure not to read and write the same file in the same pipeline.
             ^-- SC2094: Make sure not to read and write the same file in the same pipeline.

Then look up the SC.... codes, e.g., SC2097 as needed.

In this case, your command is var=... someCommand with params so the variable is set just for that command, i.e., in that process.

That's also why the first line with the semicolon (a=one; echo $a) is different. man bash states:

Commands separated by a ; are executed sequentially

So you set the variable, then run the echo as two separate actions.

Robert
  • 7,394
  • 40
  • 45
  • 64
  • Note that shellcheck doesn't give the greatest error message here; `a=two echo $a` doesn't start a forked process, although the assignment to `a` is still temporary until the built-in `echo` completes. – chepner Oct 27 '17 at 16:24
  • @chepner Agreed, but it's better than nothing. – Robert Oct 27 '17 at 16:48