8

Why does this work:

# a.sh
setEnv() {
    export TEST_A='Set'
}

when this doesn't:

# b.sh
export TEST_B='Set'

Ex:

> source a.sh
> setEnv
> env | grep TEST_A
TEST_A=Set
> b.sh
> env | grep TEST_B

I understand why running the script doesn't work and what to do to make it work (source b.sh etc), but I'm curious to why the function works. This is on OS X if that matters.

2
  • 1
    when you run the script it creates a subshell and the variable will be defined there but not in the parent. Function invocation is still in the current shell. You can source the other script as well. Commented Jan 23, 2017 at 16:06
  • 7
    Treat the scripts symmetrically and it works. source b.sh followed by env | grep TEST_B will be fine. If you didn't source a.sh, you'd not have the function to run. It's all about subshells (running b.sh creates a new shell that gets its environment set) and not (using source does not create a new shell). Commented Jan 23, 2017 at 16:10

3 Answers 3

12

You need to understand the difference between sourcing and executing a script.

  • Sourcing runs the script from the parent-shell in which the script is invoked; all the environment variables are retained until the parent-shell is terminated (the terminal is closed, or the variables are reset or unset), whereas

  • Execute forks a new shell from the parent shell and those variables including your export variables are retained only in the sub-shell's environment and destroyed at the end of script termination.

i.e. the sub-shell ( imagine it being an environment) created in the first case to hold the variables are not allocated in scope of a separate child environment but are just added in the parents' ( e.g. imagine an extra memory cell, maintained by the parent ) environment which is held until you have the session open. But executing a script is, imagine a simple analogy, calling a function whose variables are in stored in stack which loose scope at the end of function call. Likewise, the forked shell's environment looses scope at the end of its termination.

So it comes down to this, even if you have a function to export your variable, if you don't source it to the current shell and just plainly execute it, the variable is not retained; i.e.

# a.sh
setEnv() {
    export TEST_A='Set'
}

and if you run it in the shell as

bash script.sh    # unlike/NOT source script.sh
env | grep TEST_A
                  # empty
Sign up to request clarification or add additional context in comments.

6 Comments

He acknowledges that running a script without source is the problem in his question.
Agreed @chepner: in that case, letting OP know the difference between them will make him source the second script also right? Do you think I should add some other information?
The point of the question seems to be why running a function works when running a script does not.
@chepner: Does it make the answer better?
No; you are focusing on the difference between sourcing and executing, which the OP already understands. The question is entirely about why the change to the variable inside the function is visible after the function completes.
|
9

Executing a function does not, in and of itself, start a new process like b.sh does.

From the man page (emphasis on the last sentence):

FUNCTIONS
       A shell function, defined  as  described  above  under  SHELL  GRAMMAR,
       stores  a  series  of commands for later execution.  When the name of a
       shell function is used as a simple command name, the list  of  commands
       associated with that function name is executed.  **Functions are executed
       in the context of the current shell;  no  new  process  is  created  to
       interpret  them  (contrast  this with the execution of a shell script).**

Comments

6

I understand why running the script doesn't work and what to do to make it work (source b.sh etc)

So you already understand the fact that executing b.sh directly -- in a child process, whose changes to the environment fundamentally won't be visible to the current process (shell) -- will not define TEST_B in the current (shell) process, so we can take this scenario out of the picture.

I'm curious why the function works.

  • When you source a script, you execute it in the context of the current shell - loosely speaking, it is as if you had typed the contents of the script directly at the prompt: any changes to the environment, including shell-specific elements such as shell variables, aliases, functions, become visible to the current shell.

  • Therefore, after executing source a.sh, function setEnv is now available in the current shell, and invoking it executes export TEST_A='Set', which defines environment variable TEST_A in the current shell (and subsequently created child processes would see it).

  • Perhaps your misconception is around what chepner's helpful answer addresses: in POSIX-like shells, functions run in the current shell - in contrast with scripts (when run without source), for which a child process is created.

This is on OS X if that matters.

Not in this case, because only functionality built into bash itself is used.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.