Search code examples
zsh

Looping through associative array not as expected


According to the recipe described in Associative arrays in zsh, I tried the following script to test a loop through an associative Array in Zsh 5.5.1:

typeset -A emap
emap=( X y U v )
for key val in ${(kv)emap}
do
  echo key=$key val=$val
done

I get the expected output

key=U val=v
key=X val=y

But when I write the example like this:

env_code ()
{
  typeset -A emap
  emap=( MX1 Mme VOX Vbo MS1 Msc JRUBY_VERSION '()' FOO '')
  local ename
  local abbr
  for ename abbr in ${(kv)emap}
  do
    echo ename=$ename abbr=$abbr
  done
}
env_code # Invoking the function

I get the output

ename=FOO abbr=JRUBY_VERSION
ename=() abbr=MX1
ename=Mme abbr=MS1
ename=Msc abbr=VOX
ename=Vbo abbr=

We see that keys and values are exchanged. I would have expected the output

ename=MX1 abbr=Mme
ename=VOX abbr=Vbo

Solution

  • The array itself is intact:

    % typeset -A emap
    % emap=( MX1 Mme VOX Vbo MS1 Msc JRUBY_VERSION '()' FOO '')
    % declare -p emap
    typeset -A emap=( [FOO]='' [JRUBY_VERSION]='()' [MS1]=Msc [MX1]=Mme [VOX]=Vbo)
    

    Note, in particular, the order of the key-value pairs.

    However, when you expand ${(kv)emap} without quotes, the empty string value of the key FOO is lost.

    % print -l ${(kv)emap}
    FOO
    JRUBY_VERSION
    ()
    MX1
    Mme
    MS1
    Msc
    VOX
    Vbo
    

    As a result, FOO is "mapped" to the value JRUBY_VERSION, not ''.

    Quoting the expansion restores the expected behavior. With quotes, you need the explicit indexing operation to produce individual arguments to print.

    % print -l "${(kv)emap[@]}"
    FOO
    
    JRUBY_VERSION
    ()
    MX1
    Mme
    MS1
    Msc
    VOX
    Vbo
    

    Without [@], the empty string is still preserved, just harder to notice.

    % print -l "${(kv)emap}"
    FOO  JRUBY_VERSION () MX1 Mme MS1 Msc VOX Vbo
    
    # Note the two spaces between FOO and JRUBY_VERSION.
    

    Unquoted parameter expansions are not subject to word splitting, but they are subject to quote removal, and with no characters remaining after the quotes are removed from the empty string, there is no argument left after the expansion.