On 03/17/2017 07:21 PM, Stephane Chazelas wrote:
> I don't expect the need to have to add "local var" in
> (
>    unset -v var
>    echo "${var-OK}"
> )

True. I would pretty much never use a subshell command group when I know
that locals are available though. And if I know locals are available then
(except dash) I know arrays are available, in which case I'd almost never
use field splitting. This is only like the millionth screwy gotcha with
IFS. Everybody knows IFS is broken beyond repair :o)

Then again, you could easily write a similar bug with any other special
variable that has a side-effect.

> would be obvious to many people beside you though.
> very
> People writing function libraries meant to be used by several
> POSIX-like shells need to change their code to:
> split() (
>   [ -z "$BASH_VERSION" ] || local IFS # WA for bash bug^Wmisfeature
>   unset -v IFS
>   set -f 
>   split+glob $1
> )
> if they want them to be reliable in bash.

Even if the inconsistent effect of unset isn't obvious, it should be
obvious that a subshell isn't equivalent to setting a local, because it
doesn't just make dynamic scope go away.

I'm far more surprised by the behavior of mksh and dash, in which it's
the subshell rather than the unset builtin that's inconsistent. Why
would a subshell just make the call stack go away? That makes no sense,
and a subshell isn't supposed to do that. Dash and mksh don't even agree
with one another on how that works:

    (cmd) ~ $ mksh /dev/fd/3 3<<\EOF
    function f { typeset x=f; g; }
    function g { ( unset x; echo "${x-unset}"; ) }
    x=global; f
    (ins) ~ $ dash /dev/fd/3 3<<\EOF
    alias typeset=local function=
    function f() { typeset x=f; g; }
    function g() { ( unset x; echo "${x-unset}"; ) }
    x=global; f

In fact, mksh prints "global" even without the subshell, despite it
using dynamic scope for either function definition syntax.

At least bash's output in this case (empty) can be fully explained as
a combination of quirks with unset and hidden locals (neither being
documented), plus dynamic scope being what it is.

We're pretty much arguing over which is the less counter-intuitive
inconsistency here. If mksh's subshells worked consistently as in bash,
you'd have written the same bug as bash in your example. And it would
be even easier to do so without the unset quirk since this could happen
within a single function call too:

    (cmd) ~ $ mksh /dev/fd/3 3<<\EOF
    function f {
      typeset x=f
      ( unset x; echo "${x-unset}"; )
    x=global; f

This could really bite if x and f are defined in separate files so the
initial state of x isn't necessarily known.

> So what should the documentation be? With my "eval" case in
> mind, it's hard to explain without getting down to how stacking
> variables work. Maybe something like:
> [...]

All that touches on several issues in addition to scope, such as
the various states that a variable can be in, and the exact nature
of references to variables like 'a[@]'. That's some of the least-well
documented stuff, but some of that should also probably be left subject
to change due to the great inconsistency across shells and other issues
just within bash.  (Ugh also have to mention the stupid 'a[0]' with
associative arrays - that's one where "consistency" is itself a bug).

> It might be worth pointing out that "unset -v", contrary to the
> default behaviour, won't unset functions so it's a good idea to
> use "unset -v" instead of "unset" if one can't guarantee that
> the variable was set beforehand (like the common case of using
> unset to remove a variable which was potentially imported from
> the environment).

Yeah. I believe POSIX mentions that as well.

Reply via email to