Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash shell variables lost when script sourced from within a function

I've encountered a strange (to me) problem in Cygwin bash version 4.3.42(4). Shell variables that are declared in a called script do not persist in the calling script when the former is called from within a function.

I have the following two scripts to illustrate the issue. script1.sh calls script2.sh which sets two variables. If script2 is invoked via a function in script1, the variables are lost, whereas if script2 is invoked without the function call, the variables persist as expected. All invocations of script2 are done via "source".

script1.sh:

#!/usr/bin/bash
#
# calling script
#
function sourceit()
{
    source scripts/script2.sh
}

sval=1
echo "$0 before sourceit(); rval=$rval sval=$sval PID=$$"
sourceit
echo "$0 after sourceit(); rval=$rval sval=$sval PID=$$"

sval=3
echo "$0 before source; rval=$rval sval=$sval PID=$$"
source scripts/script2.sh
echo "$0 after source; rval=$rval sval=$sval PID=$$"

script2.sh

#!/usr/bin/bash
#
# called script
#
echo "$0 before declare; rval=$rval sval=$sval PID=$$"
sval=2
declare -r rval=2
echo "$0 after declare; rval=$rval sval=$sval PID=$$"

The results:

scripts/script1.sh before sourceit(); rval= sval=1 PID=1752
scripts/script1.sh before declare; rval= sval=1 PID=1752
scripts/script1.sh after declare; rval=2 sval=2 PID=1752
scripts/script1.sh after sourceit(); rval= sval=2 PID=1752
scripts/script1.sh before source; rval= sval=3 PID=1752
scripts/script1.sh before declare; rval= sval=3 PID=1752
scripts/script1.sh after declare; rval=2 sval=2 PID=1752
scripts/script1.sh after source; rval=2 sval=2 PID=1752

I don't see any subshells being created (the same PID is shown everywhere).

Am I missing a finer point of bash scripting?

like image 435
Tom the Toolman Avatar asked Dec 22 '15 20:12

Tom the Toolman


2 Answers

I had started answering this question but I was interrupted while putting together the information and have only just came back to it now. I see that John Bollinger has already more than adequately answered the questions about functions and variable scope so I’ll leave out that part of my answer.

Subshells and $$

I don't see any subshells being created (the same PID is shown everywhere).

If commands were to run in a subshell, they would be running in a child process with a different PID than the parent shell.

However, it’s worth noting that with Bash, subshells actually inherit the value of the $$ special variable from the parent shell. This caused me some confusion when I was experimenting with subshells.

However, Bash sets the BASHPID special variable to the actual PID of the shell process (which does change in subshells). The following command sequences demonstrate this.

Show the PID of the current shell:

$ echo $$
1469
$ echo $BASHPID
1469

Within the parentheses, these two commands are run in a subshell. Only the Bash-specific BASHPID special variable shows the actual PID of the subshell process.

$ (echo $$)
1469
$ (echo $BASHPID)
8806

Relevant links

  • This article on Compound commands is relevant to your question as it explains both subshells and shell functions.

  • Sourcing shell commands

like image 188
Anthony Geoghegan Avatar answered Nov 06 '22 22:11

Anthony Geoghegan


I have the following two scripts to illustrate the issue. script1.sh calls script2.sh which sets two variables. If script2 is invoked via a function in script1, the variables are lost,

Well no, your output shows that the value of variable rval is lost after return from the function, but the value of variable sval is retained.

whereas if script2 is invoked without the function call, the variables persist as expected. All invocations of script2 are done via "source".

Since there is a distinction between the behavior of rval and sval, you should look in script2.sh for a difference in how they are handled, et voilà! it turns out that rval is assigned its value via the declare built-in, whereas sval is assigned its value directly. Looking at the documentation for declare, then, we find:

When used in a function, makes each name local, as with the local command.

Local variables in bash are just like local variables in most other languages -- they are distinct from other variables having the same name, and within their scope they shadow like-named variables from containing scopes. Thus, your code sets and examines the same variable sval everywhere, in each case, but it sets and examines a different rval inside the function than it does anywhere else.

like image 41
John Bollinger Avatar answered Nov 07 '22 00:11

John Bollinger