9

I'm writing a set of shell functions that I want to have working in both Bash and KornShell93, but with Bash I'm running into a "circular name reference" warning.

This is the essence of the problem:

function set_it {
    typeset -n var="$1"

    var="hello:$var"
}

function call_it {
    typeset -n var="$1"

    set_it var
}

something="boff"
call_it something
echo "$something"

Running it:

$ ksh script.sh
hello:boff

$ bash script.sh
script.sh: line 4: warning: var: circular name reference
hello:

KornShell93 does exactly what I want, but Bash fails, and also warns about the same thing on line 2 if the something variable in the script is named var instead.

I'd like to have the var variable be local to each function, which is why I use typeset, but Bash doesn't seem to like "dereferencing" a nameref to a variable with the same name as the nameref itself. I can't use local -n or declare -n since it would break in ksh which lacks these, and even if I did, it doesn't solve the issue.

The only solution I've found is to use unique variable names in each function, which seems rather silly since they are local.

The Bash manual says the following about typeset:

typeset [...]

-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 and assignments to name, except for changing the -n attribute itself, are performed on the variable referenced by name's value.

[...]

When used in a function, declare and typeset make each name local, as with the local command, unless the -g option is supplied. If a variable name is followed by =value, the value of the variable is set to value.

It is obvious that there is something I don't understand about Bash's name references and function-local variables.

So, the question is: In this case, am I missing something about Bash's handling of name reference variables, or is this a bug/mis-feature in Bash?

Update: I'm currently working with GNU bash, version 4.3.39(1)-release (x86_64-apple-darwin15) as well as with GNU bash, version 4.3.46(1)-release (x86_64-unknown-openbsd6.0). The Bash shipped with macOS is too old to know about name references at all.

Update: Even shorter:

function bug {
    typeset -n var="$1"
    printf "%s\n" "$var"
}

var="hello"
bug var

Results in bash: warning: var: circular name reference. The var in the function should have different scope from the var in the global scope. This imposes an unnecessary restriction on the caller. The restriction being "you're not allowed to name your variables whatever you want, because there may be a name clash with a (local) nameref in this function".

11
  • Same behavior in 4.4.0(5)-rc2; I'd report this as a bug.
    – chepner
    Commented Aug 11, 2016 at 0:31
  • Actually, it's a known problem: mywiki.wooledge.org/BashFAQ/…
    – chepner
    Commented Aug 11, 2016 at 0:35
  • 1
    As a practical solution, consider deploying ksh93 everywhere. It's been open source for years. Commented Aug 11, 2016 at 0:56
  • @chepner I reported it to bug-bash last night. Thanks for the wiki reference, I hadn't seen it. The suggestion therein is the same as my workaround (use unique variable names).
    – Kusalananda
    Commented Aug 11, 2016 at 4:22
  • @Gilles I wish I could! :-) This came up when rewriting a small library of ksh93 shell utils so that they would be usable in bash too...
    – Kusalananda
    Commented Aug 11, 2016 at 4:59

2 Answers 2

5

Chet Ramey (Bash maintainer) says

There was extensive discussion about namerefs on bug-bash earlier this year. I have a reasonable suggestion about how to change this behavior, and I will be looking at it after bash-4.4 is released.

In the meanwhile, I'm resorting to slightly obfuscate the names of my local nameref variables, so that they don't clash within the library nor (hopefully) with global shell variable names.


In bash 5.0, this is ever so slightly remedied (but not really fixed). The following is the observed behaviour:

$ foo () { typeset -n var="$1"; echo "$var"; }
$ var=hello
$ foo var
bash: typeset: warning: var: circular name reference
bash: warning: var: circular name reference
bash: warning: var: circular name reference
hello

This shows that it works, but that there also are are a few warnings.

The relevant NEWS entry says

i. A nameref name resolution loop in a function now resolves to a variable by
that name in the global scope.
3

Bash's namerefs are a bit stupid, and they pretty much do what it says on the tin: they reference a name. They don't capture the "identity" of the referenced variable, like pointers and references in most general-purpose programming languages do.

So if you run declare -n ref=var, then using ref does a name lookup for the variable var, and uses its value.

And if you run declare -n foo=foo, then using foo does a name lookup for the variable foo, and... well, realizes it's a loop, and emits a warning.

Also the variable the reference finds might not be the same variable that existed with the name when the nameref was set. E.g. consider the script below:

$ cat nameref.sh 
#!/bin/bash

var="from main"
declare -n ref=var               # a

echo "main:  \$ref='$ref'"

foo() {
    local var="from foo"
    bar
}

bar() {
    echo "bar(): \$ref='$ref'"   # b
}

foo

In line "a", the top level var is visible (and you might think you're saving a reference to it), but when $ref is used on line "b", it's the var from foo() that's visible, and that's what the reference finds.

$ bash nameref3.sh 
main:  $ref='from main'
bar(): $ref='from foo'

You can get the same within one function too:

$ cat nameref4.sh

var=outer
foo() {
    declare -n ref=var
    echo "ref=$ref"
    local var=inner
    echo "ref=$ref"
}
        
foo

The first echo gets the value from the outer context, while the second gets that just assigned in foo().

As mentioned in Kusalananda's answer, Bash 5.0 fetches the top level instance of the named variable when facing a loop. But that doesn't help that much, since, well, it still emits the warning, and it doesn't let you reference a variable from the middle of the call stack if you have multiple nested functions.


Ksh is different in this regard. I'm not exactly what it does, but it seems that there, typeset -n ref=var resolves the variable from the immediately outer context, and stores its actual identity. Then again, local variables in general are different in Ksh than in Bash, which may be an issue with complex programs using them, even without namerefs in the mix.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .