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"