Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Variable assignment in nested function call unexpectedly changes local variable in the caller's scope

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.

like image 988
Alexej Magura Avatar asked Jan 20 '17 18:01

Alexej Magura


People also ask

How do you use variables in nested functions?

Nested functions can access variables of the enclosing scope. In Python, these non-local variables are read-only by default and we must declare them explicitly as non-local (using nonlocal keyword) in order to modify them. Following is an example of a nested function accessing a non-local variable.

What is non local variable in Python?

The nonlocal keyword is used to work with variables inside nested functions, where the variable should not belong to the inner function. Use the keyword nonlocal to declare that the variable is not local.

What is the scope of variables in nested functions in Python?

Please note that unlike other languages like C, Python doesn't have any separate scope for loops, if/else, etc. Thus, changing variables inside a loop will change the global variable also. After understanding global and local variables, let's dive into the scope of variables in nested functions.

How do nested functions look up variables?

Show activity on this post. The nested function looks up variables from the parent scope when executed, not when defined.

What is a function scoped variable?

The function or class variables defined inside the function/class etc are function scoped. They are available to use anywhere within the function. You cannot use them outside the function. In the following example, the variable fnVar is defined inside the someFn function. This makes it function scoped. You can access it from within the someFn.

How to change the value of the local variable of outer function?

There are different ways to change the value of the local variable of the outer function from the inner function. The first way is to use an iterable. def f1(): #outer function a = def f2(): #outer function a = 2 print (a) #prints 2 f2() print (a) #prints 2 f1()


2 Answers

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-}"
like image 88
Fred Avatar answered Oct 13 '22 04:10

Fred


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
like image 36
Alexej Magura Avatar answered Oct 13 '22 03:10

Alexej Magura