7

Consider an associative array in bash (versions 5.2.15(1)-release and 5.2.21(1)-release):

declare -A ufs=(); ufs["one"]=1; ufs["two"]=2; ufs["three"]=3
printf '> %s\n' "${!ufs[@]}"
> two
> three
> one

unset ufs["one"]
printf '> %s\n' "${!ufs[@]}"
> two
> three

All is as you'd expect.

Now let's try again, this time with nullglob enabled:

shopt -s nullglob
declare -A ufs=(); ufs["one"]=1; ufs["two"]=2; ufs["three"]=3
printf '> %s\n' "${!ufs[@]}"
> two
> three
> one

unset ufs["one"]
printf '> %s\n' "${!ufs[@]}"
> two
> three
> one

Why does the unset fail to delete the element in this case?

15
  • 3
    Is it as simple as ufs[one] being globbed into nothing? Have you tried quoting it "ufs[one]"? Commented Oct 8 at 11:09
  • @muru I've quoted in the example per my real code. Would that make a difference to your suggestion? Commented Oct 8 at 11:12
  • 2
    @MarcusMüller I'm not saying it's consistent with design - for that there has to be a design. :P But I thought the fancier parsing was only applied to declare, typeset and co., and not to set and unset Commented Oct 8 at 13:35
  • 1
    @muru, @MarcusMüller I think that this was caught me out. Considering the man page says, "unset name[subscript] destroys the array element at index subscript", I wasn't expecting the unset to take a value that could be globbed. Commented Oct 8 at 13:47
  • 2
    yeah I honestly think this is a defect. Under no circumstances would I want nullglob to lead to something that is actually there and actually an element of an array to not be unset because of nullglob Commented Oct 8 at 14:03

1 Answer 1

11

ufs["one"] is globbed first. If nullglob isn’t set, and no file matches in the current directory, it is left as-is, and unset clears it as expected. However with nullglob set, if no file matches, it is cleared:

$ set -x
$ unset ufs["one"]
+ unset

unset called with no arguments doesn’t complain, so the change isn’t obvious, unless failglob is set.

If you prevent globbing, everything works in all situations:

$ unset 'ufs["one"]'
$ printf '> %s\n' "${!ufs[@]}"
> two
> three

or, more robustly:

$ unset 'ufs[one]'

(this works even if assoc_expand_once is enabled).

8
  • setting failglob would expose it Commented Oct 8 at 12:12
  • 2
    That both unset 'ufs["one"]' and unset 'ufs[one]' work at removing the element with key one could be seen as a bug though. Commented Oct 8 at 12:14
  • 3
    After shopt -s assoc_expand_once in newer versions of bash, only unset 'ufs[one]' works so that's the one I'd recommend using Commented Oct 8 at 12:16
  • 4
    @ChrisDavies, yes, that type declare and type unset both return is a shell builtin in bash even though arguments of the former are interpreted specially adds to the confusion. It's clearer in zsh where declare is reported as a reserved word (which it is in both bash and zsh, though is also a simple builtin when not entered litterally like when quoted or the result of an expansion; compare declare a[1]=1 and 'declare' a[1]=1 in bash -O failglob) and unset as a mere shell builtin. Commented Oct 8 at 12:27
  • 2
    @mr.spuratic, "setting failglob would expose it" - maybe, but my real code requires nullglob so failglob would be counterproductive Commented Oct 8 at 17:26

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.