7

Editor's note:
Perhaps the following, taken from the OP's own answer, better illustrates the surprising behavior:
f() { local b=1; g; echo $b; }; g() { b=2; }; f # -> '2'
I.e., g() was able to modify f()'s local $b variable.


In Zsh and Bash, if I have the following function f() { a=1; g; echo $a; } and the following function g() { a=2; } when I run f, I get the following output instead of the expected:

$ f
2

Is there anyway to disable this variable bleedthrough from function to function?

I'm working on a rather large and important bash/zsh script at work that uses a ton of variables in various functions; many of these functions depend upon a larger master function, however because of the variable bleed through some rather unfortunate and unexpected behavior and bugs have come to the forefront, preventing me from confidently furthering development, since I'd like to address this strange issue first.

I've even tried using local to localize variables, but the effect still occurs.

EDIT: Note that my question isn't about how to use local variables to prevent variable bleed through or about how local variables work, how to set local variables, how to assign a new value to an already declared local variable, or any of that crap: it is about how to prevent variables from bleeding into the scope of caller/called functions.

1
  • @Inian, maybe, but my question really doesn't have anything to do with giving a new value to a variable so much as retaining the old one. I looked through the accepted answer and the actual question in the question you linked and they don't really seem to address my issue. Commented Jan 20, 2017 at 22:36

2 Answers 2

14

Using local creates a variable that is not inherited from the parent scope.

There are useful things to add.

A local variable will be inherited (and can be modified) if the function that declares it calls another function. Therefore, local protects changes to a variable of the same name inherited from higher in the scope, but not lower in the scope. The local declaration must therefore be used at each level, unless of course you actually want to alter the value in the parent scope. This is counter to what most programming languages would do, and has advantages (quick and dirty data sharing) but creates difficult to debug failure modes.

A local variable can be exported with local -x to make it usable by sub-processes (quite useful), or made readonly upon creation with local -r.

One nice trick is you can initialise a variable with the value inherited from the parent scope at the time of creation :

local -r VAR="$VAR"

If, like me, you always use set -u to avoid silently using uninitialized variables, and cannot be sure the variable already is assigned, you can use this to initialize it with an empty value if it is not defined in the parent scope:

local -r VAR="${VAR-}"
Sign up to request clarification or add additional context in comments.

1 Comment

Great answer. As a minor point of interest: dash, bash and zsh all behave the way you describe with respect to inheritance, while ksh is the lone dissenter (aside from having to use function <name> { ... } syntax instead of <name>() { ... } and typeset instead of local), making local variables truly local. The initialize-a-local-variable-with-the-inherited-value technique works in all of them, however.
2

I feel like an idiot for not realizing this sooner; I'm going to go ahead and post this question & answer anyway, just in case other scrubs like me encounter the same issue: you have to declare both variables as local:

f() { local b=1; g; echo $b; }
g() { b=2; }
f
# output: 2

f() { local b=1; g; echo $b; }
g() { local b=2; }
f
# output: 1

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.