0

Context:

  • OS: openSUSE Leap 15.1
  • Kernel: Linux 4.12.14-lp151.27-default
  • shell: GNU bash, version 4.4.23(1)-release (x86_64-suse-linux-gnu)

Given the following script, let's call it test.sh:

#!/bin/bash -e
echo "$TEST_VAR"

On different runs, it returns:

TEST_VAR=1234; source test.sh -> Output: 1234

source test.sh -> Output: 1234

Question:

  • Why does the value of env TEST_VAR persists between runs ( env | grep -i test_var) returns nothing also)?
  • Is there any way to prevent that rather than using something like exec bash?
  • If the previous answer is yes, is there any solution that is at least to some degree cross shell?

Thank you.

  • 2
    Is there a reason why you have to use source instead of running the script directly? That makes a difference to the answer. – kaylum Jan 04 '20 at 09:12

2 Answers2

2

source (a synonym to . (dot) will

execute commands in the current environment

that is, it will not execute test.sh in a new shell with its own variables, but just run every command in it in your current shell. In particular, this makes your #!/bin/bash -e shebang ineffective, it's treated as a comment. If you were to change your script to

set -e
echo "$TEST_VAR"
false

sourceing it would immediately exit your interactive session because of the return code of false. If you are not saving the output of your current shell somewhere, any error messages will be lost by this (that's not so important for just false, but important to consider for real scripts). I would recommend against using source in combination with set -e to run any scripts that might fail.

TEST_VAR=1234; source test.sh persists the value of TEST_VAR because the ; simply delimits commands, making TEST_VAR=1234; source test.sh the same as

TEST_VAR=1234
source test.sh

That's just an assignment to TEST_VAR followed by a call to source. Since source runs commands in the current environment, TEST_VAR is available in that environment.

If the ; wasn't there, that would change by making the assignment to TEST_VAR only effective for the invokation of the source command:

$ cat test.sh
#!/bin/bash -e
echo $TEST_VAR
$ TEST_VAR=1234 source test.sh
1234
$ source test.sh

env | grep -i test_var returns nothing also

The reason for that is explained in https://unix.stackexchange.com/a/268258/4699:

env is an external command (in contrary to shell built in commands) and for this reason, env only prints variables that are exported from the shell.

set on the other side lists all shell variables. Some of them are exported.

export lists the shell variables that are exported by the shell.

export can also be used to export an already-set variable:

$ TEST_VAR=1234; source test.sh
1234
$ set | grep TEST
TEST_VAR=1234
$ env | grep TEST
$ export TEST_VAR
$ env | grep TEST
TEST_VAR=1234
Wieland
  • 6,489
2

Using source or . on a script will run it in the current shell's environment.

This means that any shell variables available in the current shell will also be available in the dot-script (the script being sourced). It also means that the #!-line of your script will be totally ignored.

The commands that you show sets a variable, TEST_VAR, to some value. Then you source you dot-script, which will have access to that shell variable, since it's sourced, so it prints its value.

Then you source the same dot-script again. Since the shell variable hasn't ben explicitly unset since you last sourced the script, the value is printed again.

You get no output from your env+grep pipeline because you have no environment variable matching that pattern. Note that TEST_VAR is never exported to the environment, it is strictly a shell variable and will therefore not be inherited by child processes (e.g. env).

If you run your script as an ordinary script, i.e. through making it executable with chmod +x test.sh and then doing ./test.sh, you will notice that the value of the TEST_VAR variable in the script is empty. This is due to the script not having access to the calling shell's shell variables, and there's no environment variable called TEST_VAR in the current environment (just like there wasn't for your env+grep pipeline).

You can import the value of TEST_VAR into your script as an environment variable, either by exporting the existing shell variable using export TEST_VAR, or by starting your script like

TEST_VAR=$TEST_VAR ./test.sh

or

env TEST_VAR="$TEST_VAR" ./test.sh
Kusalananda
  • 333,661