Search code examples
bashfunctionenvironment

How can I change the environment of my bash function without affecting the environment it was called from?


I am working on a bash script that needs to operate on several directories. In each directory it needs to source a setup script unique to that directory and then run some commands. I need the environment set up when that script is sourced to only persist inside the function, as if it had been called as an external script with no persistent effects on the calling script.

As a simplified example if I have this script, sourced.sh:

export foo=old

And I have this as my driver.sh:

export foo=young
echo $foo
source_and_run(){ 
    source ./sourced.sh 
    echo $foo 
}
source_and_run
echo $foo

I want to know how to change the invocation of source_and_run in driver so it will print:

young
old
young

I need to be able to collect the return value from the function as well. I'm pretty sure there's a simple way to accomplish this but I haven't been able to track it down so far.

Creating another script like external.sh:

source ./sourced.sh; echo $foo

and defining source_and_run like

source_and_run(){ ./external.sh; return $? }

would work but managing that extra layer of scripts seems like it shouldn't be necessary.


Solution

  • You said

    Creating another script like external.sh:

    source ./sourced.sh; echo $foo
    

    and defining source_and_run like

    source_and_run(){ ./external.sh; return $? }
    

    would work but managing that extra layer of scripts seems like it shouldn't be necessary.

    You can get the same behavior by using a subshell. Note the () instead of {}.

    But note that return $? is not necessary in your case. By default, a function returns the exit status of its last command. In your case, that command is echo. Therefore, the return value will always be 0.

    source_and_run() (
        source ./sourced.sh
        echo "$foo"
    )
    

    By the way: A better solution would be to rewrite sourced.sh such that it prints $foo. That way you could call the script directly instead of having to source it and then using echo $foo.