Почему bash ассоциативные массивы не поддерживают порядок индексов?
Поскольку они предназначены для этого.
Почему порядок элементов меняется?
Bash Реализация ассоциативного массива использует библиотеку ha sh и хранит хэши индексов. Эти хэши хранятся в сегментах с 128 числом по умолчанию в сегментах . Га sh рассчитывается с помощью функции hash_string()
с использованием простого умножения и побитового XOR. Ключи ассоциативного массива перечислены в порядке следования появляются . Номер корзины вычисляется с помощью побитовой операции И между значением ключа ha sh и числом блоков, уменьшенным на 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
Порядок выводимых ключей сортируется с использованием порядка корзины в таблице ha sh, в которую они входят, поэтому они выводятся в таком порядке. Вот почему порядок элементов изменился.
Тем не менее, не следует полагаться на это поведение, поскольку порядок вывода ключей может измениться, если автор bash решит изменить функцию хеширования или внесите любое другое изменение.
А как обойти это поведение?
Нет способа обойти это. Массивы Bash используют таблицу ha sh для хранения хэшей. Порядок вставки ключей нигде не сохраняется.
Конечно, вы можете обойти это поведение, добавив 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
.