为什么Bash关联数组不保持索引顺序?

问题描述 投票:1回答:3

我正在创建关联数组以在for循环中进行处理,但是我以索引顺序得到了一些奇怪的结果。请看一下这个示例脚本:

#!/bin/bash
declare -A test1=(
    [d]=1w45
    [e]=2dfg
    [m]=3df
    [o]=4df
)

declare -A test2=(
    [d1]=1w45
    [e2]=2dfg
    [m3]=3df
    [o4]=4df
)

declare -A test3=(
    [1d]=1w45
    [2e]=2dfg
    [3m]=3df
    [4o]=4df
)

echo ${!test1[@]}
echo ${!test2[@]}
echo ${!test3[@]}

输出将是

$ ./test 
d e m o
o4 m3 e2 d1
3m 4o 1d 2e

为什么项目顺序改变?以及如何绕过这种行为?预先感谢!

arrays bash associative-array
3个回答
1
投票

为什么不bash关联数组保持索引顺序?

因为它们被设计为不这样做。

为什么项目顺序改变?

Bash associative array implementation使用hash library并存储索引的哈希。哈希存储在buckets中,default number of buckets为128。使用简单的乘法和异或在hash_string()中计算哈希。键列在in the order buckets appear中。 Bucket number is calculated通过二进制&和-将带有桶数减少的键的哈希值减1。

我编译了bash commit 6c6454cb18d7cd30b3b26d5ba6479431e599f3ed,对我来说,您的脚本输出是:

$ ./test 
o m e d
d1 e2 m3 o4
1d 3m 2e 4o

所以我复制了`hash_string()函数并编写了一个小型C程序,该程序将输出键的存储区编号并进行编译和执行:

#include <stdio.h>

#define FNV_OFFSET 2166136261
#define FNV_PRIME 16777619

unsigned int
hash_string (s)
     const char *s;
{
  register unsigned int i;

  for (i = FNV_OFFSET; *s; s++)
    {
      i *= FNV_PRIME;
      i ^= *s;
    }

  return i;
}

int main() {
    const char *s[] = {
        "o", "m", "e", "d",
        "d1", "e2", "m3", "o4",
        "1d", "3m", "2e", "4",
    };
    for (int i = 0;  i < sizeof(s)/sizeof(*s); ++i) {
        printf("%3s %3d\n",
            s[i], 
            hash_string(s[i]) & (128 - 1));
    }
}

程序输出两列-键和键的存储区号(添加了多余的空行:]]

  o 112
  m 114
  e 122
  d 123

 d1  16
 e2  60
 m3  69
 o4 100

 1d  14
 3m  41
 2e  50
 4o  94

输出的键的顺序使用它们在哈希表中存储桶的顺序进行排序,因此它们按该顺序输出。这就是项目顺序更改的原因。

也就是说,您应该不是

依赖于此行为,因为如果bash的作者决定更改哈希函数或进行任何其他更改,则键的输出顺序可能会更改。

以及如何绕过此行为?

没有办法绕过这个。 Bash数组使用哈希表存储哈希。密钥的插入顺序未存储在任何地方。

当然,您可以通过修补bash来实现您要求的功能来绕过此行为。

也就是说,我只使用两个数组:

keys=(d1 e2 m3 o4)
elements=(1w45 2dfg 3df 4df)
declare -A test2
for ((i=0;i<${#keys[@]};++i)); do
    test2[${keys[$i]}]="${elements[$i]}"
done
# or maybe something along:
declare -A test2=($(paste -zd <(printf "[%s]=\0" "${keys[@]}") <(printf "%q \0" "${elements[@]}"))

这样,您就可以按照将键插入到单独的keys数组中的顺序来遍历键。


0
投票

根据评论,可以这样做来绕过此行为。


0
投票

为什么项目顺序改变?

© www.soinside.com 2019 - 2024. All rights reserved.