bashreferenceunset

BASH unset -v problem - referenced vars are unset too


Blindly relying on some statement claiming 'use unset-v or strange things will happen', I've been using unset -v since.
Now I've encountered following problem in conjunction with name references, where a referenced var is unset, too:

gitStatus=$(git status --porcelain=v1)
declare -n gitStatusResult=gitStatus
unset -v gitStatusResult 
echo -e "GIT_STATUS: ${gitStatus[@]}"

gitStatus will be empty, too after that, not just gitStatusResult.
When using unset -n gitStatusResult or simply gitStatusResult= instead, the content of gitStatus will be preserved.

unset --help states, that:

-v treat each NAME as a shell variable
-n treat each NAME as a name reference and unset the variable itself rather than the variable it references

This was unexpected - in my understanding things should be just the other way round, where -n should unset gitStatus directly, and -v should only unset the named reference gitStatusResult.

EDIT: In addition, when using unset -n gitStatus, neither gitStatus nor gitStatusResult appear empty.

Could somebody shed some light on this issue?

I'm using Cygwin GNU bash, version 5.2.21(1)-release.


Solution

  • This is, I believe, the intended behavior of a nameref. A nameref is just that: a reference to a name, not a "real" variable itself. Anything you do to or with a nameref is equivalent to doing the same to or with the underlying variable.

    foo=1
    declare -n fooRef=foo
    
    echo $fooRef  # same as echo $foo
    fooRef=2      # same as foo=2
    [[ -v fooRef ]]  # same as [[ -v foo ]]
    unset -v fooRef  # same as unset -v foo
    

    Note that the reference in the last case outlives the original variable; you can still use fooRef to revivify foo:

    fooRef=5  # still the same as foo=5
    

    As such, if you want to remove the nameref, you need some way to address it directly; that's what unset -n is for, bypassing the ordinary behavior of a nameref.

    bash-5.2$ foo=1
    bash-5.2$ declare -n fooRef=foo
    bash-5.2$ declare -p foo fooRef
    declare -- foo="1"
    declare -n fooRef="foo"
    bash-5.2$ unset -n fooRef
    bash-5.2$ declare -p foo fooRef
    declare -- foo="1"
    bash: declare: fooRef: not found