Thanks much for all the insights, everyone! Indeed, the man page isn't clear on how to reason about `local -g`. I'm now left with the following understanding:
1. There is *nothing local* about `local -g`; it operates entirely at the global level. In particular, it does *not* create a local reference to a global var, which is the natural assumption given the typical behaviour of `declare/local`. 2. Because of this, there is no way to *read* a global var if a calling scope declares a shadowing var, short of unsetting that shadow var. 3. This is NOTABUG. Have I got that right? If so, can I suggest a documentation enhancement for `declare`, from: The `-g` option forces variables to be created or modified at the global scope, even when `declare` is executed in a shell function. to: The `-g` option forces variables to be created or modified at the global scope, even when `declare` is executed in a shell function, *and no references to these variables are created in that function's scope*. Thanks again! On Mon, Mar 11, 2024 at 12:08 PM Kerin Millar <k...@plushkava.net> wrote: > On Sun, 10 Mar 2024 16:01:10 -0400 > Lawrence Velázquez <v...@larryv.me> wrote: > > > On Sun, Mar 10, 2024, at 1:51 PM, Kerin Millar wrote: > > > Dynamic scoping can be tremendously confusing. The following examples > > > should help to clarify the present state of affairs. > > > > > > $ x() { local a; y; echo "outer: $a"; } > > > $ y() { local a; a=123; echo "inner: $a"; } > > > $ x; echo "outermost: $a" > > > inner: 123 > > > outer: > > > outermost: > > > > > > This is likely as you would expect. > > > > > > $ y() { local -g a; a=123; echo "inner: $a"; } > > > $ x; echo "outermost: $a" > > > inner: 123 > > > outer: 123 > > > outermost: > > > > > > This may not be. There, the effect of the -g option effectively ends > at > > > the outermost scope in which the variable, a, was declared. Namely, > > > that of the x function. > > > > This doesn't seem to be accurate; the assignment is performed at > > the *innermost* declared scope (other than the "local -g" one): > > > > $ x() { local a; y; echo "outer: $a"; } > > $ y() { local a; z; echo "inner: $a"; } > > $ z() { local -g a; a=123; echo "innermost: $a"; } > > $ x; echo "outermost: $a" > > innermost: 123 > > inner: 123 > > outer: > > outermost: > > > > Basically, without an assignment, "local -g" does nothing. > > It might be tempting to think that the criteria for being "ignored" are > fulfilled but it is not the case. Below is something that I should also > have tried before initially posting in this thread. > > $ z() { a=123; echo "innermost: $a"; }; unset -v a; x; declare -p a > innermost: 123 > inner: 123 > outer: > bash: declare: a: not found > > $ z() { local -g a; a=123; echo "innermost: $a"; }; unset -v a; x; declare > -p a > innermost: 123 > inner: 123 > outer: > declare -- a > > $ z() { local -g a=456; a=123; echo "innermost: $a"; }; unset -v a; x; > declare -p a > innermost: 123 > inner: 123 > outer: > declare -- a="456" > > I think that Greg has it right. The use of the -g option, alone, is > sufficient to reach into the global scope, though dynamic scoping behaviour > otherwise remains in effect. That is, one would otherwise still need to pop > scopes - so to speak - to reach the outermost/bottommost scope. > > Speaking of which, to do both of these things has some interesting effects > ... > > $ z() { local -g a; unset -v a; a=123; echo "innermost: $a"; }; unset -v > a; x; declare -p a > innermost: 123 > inner: 123 > outer: 123 > declare -- a > > $ z() { local -g a; unset -v a; unset -v a; a=123; echo "innermost: $a"; > }; unset -v a; x; declare -p a > innermost: 123 > inner: 123 > outer: 123 > declare -- a="123" > > $ x() { local a; y; local +g a; a=456; echo "outer: $a"; }; unset -v a; x; > declare -p a > innermost: 123 > inner: 123 > outer: 456 > declare -- a="123" > > I remain somewhat uncertain that the manual conveys enough information to > be able to perfectly reason with all of this. > > -- > Kerin Millar > -- Regards, Adrian