Hallo Thorsten,

On Mon, 13 Mar 2017 18:20:11 +0000 (UTC), Thorsten Glaser wrote:
> Jean Delvare dixit:
> 
> >A customer of ours has reported the following difference in behavior
> >between mksh and ksh-93: unsetting a local variable causes mksh to
> 
> Do note that ksh93 has static scoping, unlike ALL other shells
> which have hierarchical dynamic scoping comparable to other
> programming languages.
> 
> The scoping model in ksh93 is fundamentally incompatible to
> anything you know from anywhere else.

Yeah, the ksh93 variable scoping is... interestingly confusing.

> >destroy that local variable, so any further reference to its name will
> >relate to the global variable by the same name. Example:
> 
> Yes, that is correct. You can then “local” it again to
> recreate the local variable of said name.
> 
> >mksh will print 2, while ksh-93 will print 42. Note that bash will
> >print 42 as well, and so does dash after adjusting the shell syntax so
> >that dash understands it. So it seems that mksh handles this case
> >differently from all other shells.
> 
> Interesting… so it seems that GNU bash and dash don’t really
> unset it but only clean it.

Yes and no. See the difference between:

$ foo=
$ echo foo is ${foo+set}
foo is set

and:

$ unset foo
$ echo foo is ${foo+set}
foo is

So internally bash does make a difference between a variable being null
and a variable being unset. A bash variable can actually have 4 distinct
states: non-existent, existent but unset, set but null, and non-null.
"typeset", "local" and "unset" all put the variable into state
"existent but unset" (unlike mksh.)

Looking at the bash manual page, in most cases unset and null
are treated the same, however there are a few exceptions like the
example above (discovered by accident, doesn't appear to be documented)
or when you run bash with option -u.

> >I see that one can use "unset foo[*]" in mksh to achieve the "unset
> >foo" of other shells. I guess this is a side effect of the
> 
> You can? Hm, interesting… but makes sense.

Well I found it because it is documented in the man page, but the
syntax looks odd to me.

> >impossibility to preserve the variable attributes without preserving
> >the variable itself. But this doesn't really help anyway, as this
> >syntax is not portable.
> 
> if [[ $KSH_VERSION = *@(MIRBSD|LEGACY)\ KSH* ]]; then
>       function unset {
>               local __foo
> 
>               for __foo in "$@"; do
>                       eval "unset $__foo[*]"
>               done
>       }
> fi

Does that actually work? It doesn't seem like special shell builtins
can be overloaded with functions, and unset is a special shell builtin.

> >So, is the behavior of unset on local variables a design decision, or
> 
> I believe the way mksh does it to be of the greatest benefit to
> the user, although I never actively decided either way. It *is*
> however what I’d expect when writing scripts.
> 
> (Note that pdksh also prints 2.)

I have to admit I was not sure myself what I would prefer as a script
author. I cowardly decided that unsetting local variables shouldn't be
needed, so I did not have to answer the question ;-)

However if I really had to make a choice... My initial feeling was that
mksh was right, as unset is exactly undoing the effect of typeset. But
my second thought was that it makes it way too easy to get things wrong
and accidentally access global variables. In particular, the fact that
calling "unset" twice on a local variable actually destroys the global
variable by the same name is kind of frightening.

In doubt, I guess I would have sticked to what other shells are doing,
for consistency. Ideally there would be an "unlocal" builtin to achieve
mksh's "unset" effect in functions. But then again I just can't figure
out a case where I would first declare a local variable and then want to
access a global variable by the same name instead.

-- 
Jean Delvare
SUSE L3 Support

Reply via email to