[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Variables getting silently unset on redeclaration
From: |
Chet Ramey |
Subject: |
Re: Variables getting silently unset on redeclaration |
Date: |
Fri, 16 Mar 2018 11:35:05 -0400 |
User-agent: |
Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:52.0) Gecko/20100101 Thunderbird/52.6.0 |
On 3/15/18 10:30 PM, dtr@home.homenet wrote:
> Bash Version: 4.4
> Patch Level: 12
> Release Status: release
>
> Description:
> Variables mistakenly declared as local and then set as global
> happen to be unset for the “nounset†option and known
> to “declare -p†at the same time. Moreover, whatever value was
> assigned to them is lost.
It's not. You've just encountered dynamic scoping, and the fact that a
variable is not set until it has been assigned a value, even if it has
attributes.
>
> Re-declaration doesn’t cause an error, and such variables may cause
> silent failure, because the error can only be caught on the first
> use and only in two cases:
> - nounset option is on;
> - a test on variable existence [ -v is called and errexit is on.
> The unset variable isn’t unset for declare, which is confusing.
Oh, but it is.
>
> Expected behaviour:
> Variables should either change their type to global and maintain
> the value assigned to them, or trigger SIGERR on re-declaration,
> (and not when they’re caught much later by “errexit†or
> “nounsetâ€).
This isn't useful or intuitive.
>
> Repeat-By:
So the call stack (and list of variable scopes) is main -> func -> set_var.
Variable references are satisfied from the current scope or the previous
scope `nearest' the current scope, which may be another local scope.
> #! /usr/bin/env bash
>
> set -eu
>
> set_var() {
> local name="$1" value="$2"
> # Fixing names
> name=${name//-/_}
> name=${name//\//_}
> # Here should be some work
> # to retrieve data…
> declare -g $name="$value"
You've created a variable in the global scope. The local declaration in
`func' still exists, is still unset (because you haven't given it a
value), and satisfies references to my_poor_var due to dynamic scoping
rules.
> # my_poor_var is already unset here
> return 0
> }
>
> func() {
> # mistakenly left among local variables
> local my_poor_var
> set_var my-poor/var 2
You've created a global variable named `my_poor_var' with value 2 whose
existence is shadowed by the local instance with with same name.
>
> # (1)
> [ -v my_poor_var ]
> # quits on SIGERR
Reference to an unset variable causes the command to fail. It's debatable
whether this should trigger an exit because of -u being set, but it doesn't
right now.
> # (2)
> echo "$my_poor_var"
> # line 18: my_poor_var: unbound variable
The variable is unset because you haven't given it a value.
> # (3)
> declare -p my_poor_var
> # Reports, that it exists(!), but has no value.
It's unset because it has no value. It exists, because it has a name
and attributes (the local attribute).
> # Calling declare -p doesn’t make the program quit,
> # unlike with really unset variables
declare -p exists to say how to recreate a variable with its current
state. If a variable has attributes but no value, it should be possible
to recreate it.
>
> return 0
> }
>
> func
If you were to turn off -e and -u, and run `declare -p my_poor_var' after
the call to `func', you would find you have a global variable.
Chet
--
``The lyf so short, the craft so long to lerne.'' - Chaucer
``Ars longa, vita brevis'' - Hippocrates
Chet Ramey, UTech, CWRU chet@case.edu http://tiswww.cwru.edu/~chet/