6

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?

3
  • Possibly a cross-site duplicate of this Super User question Commented Dec 22, 2015 at 20:50
  • Regarding Am I missing a finer point? Probably yes. Sourcing the script does not create a new shell and thus the PID stays the same. Commented Dec 22, 2015 at 20:52
  • @sjsam Note that both invocations of script2 are "sourced". I was displaying the PID to prove to myself that a subshell was not the issue. The only difference here is whether a function call is used in the calling script. Commented Dec 22, 2015 at 21:28

2 Answers 2

4

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

Sign up to request clarification or add additional context in comments.

5 Comments

Do you mean "Only the Bash-specific $BASHPID special variable shows the actual PID of the subshell process"?
@JohnBollinger Yes. I noticed that mistake and edited it but I was away from the computer for the past hour and only just saw your comment now. For some reason, I have to reload the page to see new edits or comments. Thanks, anyway.
@AnthonyGeoghegan Thanks for those useful pointers. I've learned several interesting things today.
@TomtheToolman Shell scripting isn't the most intuitive and I've learned this the hard way. The way you asked your question question clearly showed an interest in learning how things work. These are the kinds of questions I like to see on Stack Overflow; what better way to welcome a newcomer than to answer their question?
My original effort was to have a script that sets up some logging functions and their related read-only variables (both intended for use by shell scripts only) when I encountered this situation. In script2, I've tried using 'var=val' followed by 'typeset -r var', but that only affects script2 - script1 is still able to change it.I know I can 'export' the variables but that clutters the environment space. Is there a way to do this such that it works whether the script is sourced directly or from within a function call? Should this be a separate question?
3

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.

4 Comments

> the value of variable sval is retained True indeed. > When used in a function, makes each name local, as with the local command. Interesting; the declare is in a script which is invoked by a function, so it wasn't obvious to me that that aspect of declare would be important. Thanks for spotting that.
@TomtheToolman, you're using source, which is not well described as "invoking" the designated script. source "Read[s] and execute[s] commands from filename in the current shell environment and return[s] the exit status of the last command executed from filename." This is precisely the distinction you intentionally drew between what you are doing and running the second script in a subshell.
BTW how did you do the quoting? As you can see, I didn't get it right.
@TomtheToolman, less markup is available in comments than in questions / answers. Where available, you do quoting with the >, as you attempted to do in your comment.

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.