Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making a nameref a regular variable in bash

Tags:

EDIT: This has been confirmed to be a bug and will be fixed: https://lists.gnu.org/archive/html/bug-bash/2018-03/msg00055.html


So I'm messing around with bash's indirection feature, namerefs. I thought I had gotten the hang of it, but then I hit upon something that confused me when I was trying to figure out how to make namerefs into regular variables.

Here's a relevant passage from man bash:

declare -n Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references, assignments, and attribute modifications to name, except those using or changing the -n attribute itself, are performed on the variable referenced by name's value.


Part I understand

So say you wanted to unset a nameref--not the varaible that it points to, but the variable itself. You couldn't just say unset foo, because that would actually unset whatever foo points to; instead, you'd have to make it a regular variable, then unset it:

$ declare -p

$ foo=bar; bar='hello world'

$ declare -p
declare -- foo="bar"
declare -- bar="hello world"

$ declare -n foo; declare -p    # 'foo' is now a nameref
declare -n foo="bar"
declare -- bar="hello world"

$ declare +n foo; declare -p    # 'foo' is no longer a nameref
declare -- foo="bar"
declare -- bar="hello world"

$ unset foo; declare -p         # 'foo' is unset, not bar
declare -- bar="hello world"

Part I don't understand

That all makes sense to me and is consistent with my reading of the above manual passage. What confuses me is what happens upon a minor variation in the above--namely, we leave bar unset and undeclared:

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

$ echo "${foo}"                 # These two commands behave as expected--i.e., identically to how namerefs usually behave, just with an unset variable.
-bash: foo: unbound variable

$ echo "${!foo}"
bar

$ declare +n foo; declare -p    # Should make 'foo' a regular variable, right? Nope.
declare -n foo="bar"            # Still a nameref--wtf?
declare -- bar                  # And now bar's back--unset still, but declared. Wtf??

$ declare +n foo; declare -p    # THIS, however, works like I thought it would--but *why*? In both cases 'bar' is unset...
declare -- foo="bar"
declare -- bar

I apparently misunderstand how namerefs are supposed to work. Based on the passage from man, I would think that unsetting the nameref attribute of foo should work on foo, regardless of whether its target, bar, is undeclared.

Note that it works how I thought it would when bar is unset, but declared. This is the oddest part to me--I didn't realize there was any significance to a variable being undeclared! test -v, ${var-_}, ${var+_}, and set -u all only seem to care about whether the variable is set and make no distinction whatsoever between (A) an unset, undeclared variable and (B) an unset, declared variable.

Can someone explain what's going on here, maybe point to the portion of the manual that explains this? Are there any other special cases in the behavior of namerefs I'm going to be confused about? Thanks!


Potentially relevant information:

$ bash --version
GNU bash, version 4.4.19(1)-release (x86_64-unknown-linux-gnu)
...

$ echo "$-"
himuBCHs

Note that the behavior persists without set -u; I just did that to make bash's messages a little clearer.

like image 401
greatBigDot Avatar asked Mar 08 '18 17:03

greatBigDot


People also ask

What is $$ variable in bash?

$$ is a Bash internal variable that contains the Process ID (PID) of the shell running your script. Sometimes the $$ variable gets confused with the variable $BASHPID that contains the PID of the current Bash shell.

What does declare mean bash?

'declare' is a bash built-in command that allows you to update attributes applied to variables within the scope of your shell. In addition, it can be used to declare a variable in longhand. Lastly, it allows you to peek into variables.

Can a bash variable start with a number?

The only valid beginning character types for bash variables are letters and underscores.


1 Answers

There's a new argument to unset explicitly for the purpose of undefining a nameref (as opposed to the variable it points to):

unset -n namevar
like image 155
Charles Duffy Avatar answered Sep 19 '22 12:09

Charles Duffy