1

I read this today

"Local can only be used within a function; it makes the variable name have a visible scope restricted to that function and its children." The ABS Guide author considers this behavior to be a bug.

§ Local Variables

and I came up with this script

begin () {
  local foo
  alpha
}
alpha () {
  foo=333 bar=444
  bravo
}
bravo () {
  printf 'foo %3s bar %s\n' "$foo" "$bar"
}
begin
bravo

Output

foo 333 bar 444
foo     bar 444

So as you can see, because I did not local bar, it leaked out into global scope. Questions:

  • Is a local variable being available to a child function actually a bug, or was that just his opinion?
  • Does Bash have a way to mark everything local, similar to how set -a marks everything for export?
  • Failing that, does Bash have a way I can check for these leaked global variables?
1
  • 1
    To be clear dynamic scope is almost always considered a language design flaw. In the case of Bash (and most other shells) it's intentional, not an accidental "bug". Commented Dec 29, 2014 at 4:43

2 Answers 2

3

Is a local variable being available to a child function actually a bug, or was that just his opinion?

No, it's not a bug. That's just his opinion.

Does Bash have a way to mark everything local, similar to how set -a marks everything for export?

No.

Failing that, does Bash have a way I can check for these leaked global variables?

Yes. Just try "set" or "declare", both without any parameter.

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

Comments

1

Failing that, does Bash have a way I can check for these leaked global variables?

No. Bash has an undocumented concept called "hidden variables" that make it impossible to test for whether a local is set without disturbing the variable.

This test demonstrates a hidden variable together with the scope-sensitive nature of the unset builtin.

function f {
    case $1 in 1)
        typeset x=1
        f 2
        ;;
    2)
        typeset x
        unset -v x # Does nothing (demonstrates hidden local)
        f 3
        ;;
    [345])
        printf "x is %sunset\n" ${x+"not "}
        unset -v x
        f $(($1 + 1))
    esac
}

f 1

# output:
# x is unset
# x is not unset
# x is unset

Bash has a way to force setting a global using declare -g, however there is no way to force bash to dereference it, or test whether it is set, making that feature of very limited utility.

This hopefully demonstrates the problem clearly

f() {
    local x="in x"      # Assign a local
    declare -g x=global # Assign a global
    declare -p x        # prints "in x"
    unset -v x          # try unsetting the local
    declare -p x        # error (x is invisible)
}
f
declare -p x # x is visible again, but there's no way to test for that before now.

5 Comments

@StevenPenny Did I misunderstand? Variables can't "leak" in a way that accidentally sets a local where a global was intended (except with declare -g). The most common reason for intentionally setting a variable declared in a parent scope is to simulate pass-by-reference, where the fact that you can't test for a localized variable or guarantee a particular instance is being referred to is a hard problem. If the intention is to set a global then the same problem applies. declare -g guarantees the right instance was set but there is no way to directly refer to the global after setting it.
That's right, unset peeled away all the layers by the time the script is finished. When the last branch of the case is hit for the first time, x is set within the first scope but the test (using ${x+word}) displays the opposite. There's no way to distinguish between x being set or not until you actually unset it from a child scope. That's the exact problem you're trying to avoid (or detect) in your example question.
The code / comments are correct. If your bash is older than 4.2 alpha then you don't have declare -g. It's just there for demonstration. defining the global before calling f gives the same effect.
So I tried this again with Bash 4.3. Only the global value of x is available outside the function, so what is the problem?
@StevenPenny That's the pitfall with declare -g described in the first comment (and a big problem with dynamic scope in general). The only function of declare -g is to allow unconditional assignment to the global scope even if a local with the same name has been set in any parent of the current scope. It doesn't make that variable usable from the current scope until you either return from the first function that localized it, or call unset from any child scope of the last function that localized it until all locals with conflicting names have been unset.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.