bug-bash
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Unexpected behavior of 'declare +n var' when var's target is unset and u


From: GreatBigDot
Subject: Unexpected behavior of 'declare +n var' when var's target is unset and undeclared
Date: Thu, 08 Mar 2018 14:51:44 -0500

Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS:  -DPROGRAM='bash' -DCONF_HOSTTYPE='x86_64'
-DCONF_OSTYPE='linux-gnu' -DCONF_MACHTYPE='x86_64-unknown-linux-gnu'
-DCONF_VENDOR='unknown' -DLOCALEDIR='/usr/share/locale'
-DPACKAGE='bash' -DSHELL -DHAVE_CONFIG_H   -I.  -I. -I./include
-I./lib  -D_FORTIFY_SOURCE=2 -march=x86-64 -mtune=generic -O2 -pipe
-fstack-protector-strong -fno-plt
-DDEFAULT_PATH_VALUE='/usr/local/sbin:/usr/local/bin:/usr/bin'
-DSTANDARD_UTILS_PATH='/usr/bin' -DSYS_BASHRC='/etc/bash.bashrc'
-DSYS_BASH_LOGOUT='/etc/bash.bash_logout'
-DNON_INTERACTIVE_LOGIN_SHELLS -Wno-parentheses -Wno-format-security
uname output: Linux ArchBox0 4.15.7-1-ARCH #1 SMP PREEMPT Wed Feb 28
19:01:57 UTC 2018 x86_64 GNU/Linux
Machine Type: x86_64-unknown-linux-gnu

Bash Version: 4.4
Patch Level: 19
Release Status: release

Description:
        If you try to turn a nameref back a regular variable with
'declare -n' but the nameref is pointing to an undeclared variable, the
nameref remains a nameref, and the variable it points to is declared
(but remians unset). This seems to contradict the bash manual, which
says that "all references, assignments, and attribute modifications to
[a nameref], **except those using or changing the -n attribute
itself,** are performed on the variable referenced by name's value."
[Emphasis mine.] Yet, if the nameref's target doesn't exist, then it is
still treated as a reference when dealing with the '-n' attribute.

Repeat-By:
        Create a nameref 'foo' pointing to 'bar', where bar is unset
and undeclared:

        $ unset bar
    $ declare -n foo="bar"
        $ declare -p
        declare -n foo="bar"

Now attempt to turn 'foo' back into a regular variable:

        $ declare +n foo
        $ declare -p
        declare -n foo="bar"
        declare -- bar

This is not what happens when `bar` is set (or even, bizarrely enough,
when `bar` is unset but declared--see below). Observe:

         $ declare -n foo=bar
         $ bar='Hello World!'
         $ declare -p
         declare -n foo="bar"
         declare -- bar="Hello World!"

         $ declare +n foo
         $ declare -p
         declare -- foo="bar"
         declare -- bar="Hello World!"

The oddest part is that the normal behavior works when `bar` is unset,
but declared:

        $ declare -n foo=bar
        $ declare bar
        $ declare -p
        declare -n foo="bar"
        declare -- bar

<sidenote> `bar` truly is unset at this point, though it /isn't/
undeclared:

         $ declare -p bar
         declare -- bar

         $ set -u; printf '%s\n' "$bar"; set +u
         -bash: bar: unbound variable

         $ test -v bar && printf '%s\n' 'set' || printf '%s\n' 'unset'
         unset

         $ printf '%s\n' "${bar-0}" "${bar-1}"
         0
         1

         $ printf '<%s>\n' "${bar+0}"
         <>

</sidenote> Anyway, with this setup, `declare +n` works like it usually
does:

        $ declare -p
        declare -n foo="bar"
        declare -n bar

        $ declare +n foo; declare -p
        declare foo="bar"
        declare bar

I find this truly bizarre, as I didn't realize there was any
significance to a variable being /declared/; that is, I thought bash
thought there to be no significant difference between an unset variable
that was nevertheless declared with `declare`, and an unset, undeclared
variable. Yet the former behaves normally with respect to namerefs,
while the latter does not.

Is this expected behavior? If so, would anyone mind pointing me to
where this is documented and if there are any other edge cases related
to this? Thanks!

Fix:
        When I posted this to StackOverflow [https://stackoverflow.com/
questions/49179596/making-a-nameref-a-regular-variable-in-bash], a user
answered that one can simply use a new option to unset:

        [...]
        $ declare -p
        declare -n foo="bar"

        $ unset -n foo; declare -p

This works regardless of the status of bar.

Trying to figure out how to unset a nameref (not the variable
it points to, but the variable itself) was what led me to this behavior
in the first place, and I can't think of any other common use case for
`declare +n` off the top of my head. Yet if that really was one's end
goal, it's easy enough to use the above fix:

        [...]
        $ declare -p
        declare -n foo="bar"

        $ _tmp="${!foo}"; declare -p
        declare -n foo="bar"
        declare -- _tmp="bar"

        $ unset -n foo; declare -p
        declare -- _tmp="bar"

        $ foo="${_tmp}"; unset _tmp; declare -p
        declare -- foo="bar"



reply via email to

[Prev in Thread] Current Thread [Next in Thread]