Как сохранить порядок ассоциативных массивов?

Я пытаюсь перебрать ассоциативный массив в Bash.

Это кажется простым, но цикл не соответствует начальному порядку массива.

Вот простой скрипт:

#!/bin/bash

echo -e "Workspace\n----------";
lsb_release -a

echo -e "\nBash version\n----------";
echo -e $BASH_VERSION."\n";

declare -A groups;
groups["group1"]="123";
groups["group2"]="456";
groups["group3"]="789";
groups["group4"]="abc";
groups["group5"]="def";

echo -e "Result\n----------";
for i in "${!groups[@]}"
do
    echo "$i => ${groups[$i]}";
done

Вывод:

Workspace
----------
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.2 LTS
Release:    14.04
Codename:   trusty

Bash version
----------
4.3.11(1)-release.

Result
----------
group3 => 789
group2 => 456
group1 => 123
group5 => def
group4 => abc

Почему у меня нет group1, group2 и т.д.?

Я не хочу иметь алфавитный порядок, я просто хочу, чтобы цикл следовал начальному порядку объявления массива...

Есть ли способ?

Ответ 1

Как уже указывалось, ошибки нет. Ассоциативные массивы хранятся в порядке "хэш". Если вы хотите заказать, вы не используете ассоциативные массивы. Или вы используете неассоциативный массив, а также ассоциативный массив.

Сохраните второй (неассоциативный) массив, который идентифицирует ключи в том порядке, в котором они созданы. Затем выполните второй массив, используя его содержимое для ввода первого (ассоциативного) массива при печати данных. Вот так:

declare -A groups;      declare -a orders;
groups["group1"]="123"; orders+=( "group1" )
groups["group2"]="456"; orders+=( "group2" )
groups["group3"]="789"; orders+=( "group3" )
groups["group4"]="abc"; orders+=( "group4" )
groups["group5"]="def"; orders+=( "group5" )

# Convoluted option 1
for i in "${!orders[@]}"
do
    echo "${orders[$i]}: ${groups[${orders[$i]}]}"
done
echo

# Convoluted option 1 - 'explained'
for i in "${!orders[@]}"
do
    echo "$i: ${orders[$i]}: ${groups[${orders[$i]}]}"
done
echo

# Simpler option 2 - thanks, PesaThe
for i in "${orders[@]}"
do
    echo "$i: ${groups[$i]}"
done

"Более простой вариант 2" был предложен PesaThe в comment и следует использовать в предпочтении "запутанной опции".

Пример вывода:

group1: 123
group2: 456
group3: 789
group4: abc
group5: def

0: group1: 123
1: group2: 456
2: group3: 789
3: group4: abc
4: group5: def

group1: 123
group2: 456
group3: 789
group4: abc
group5: def

Вероятно, вы не хотите иметь два оператора в строке, но он подчеркивает parallelism между обработкой двух массивов.

Точки с запятой после заданий в вопросе на самом деле не нужны (хотя они не оказывают никакого активного вреда, за исключением того, что читатель задается вопросом "почему?" ).

Ответ 2

Другим способом сортировки записей в вашем ассоциативном массиве является сохранение списка групп по мере добавления их в качестве записи в ассоциативном массиве. Вызовите этот ключ ввода "group_list". Когда вы добавляете каждую новую группу, добавьте ее в поле group_list, добавив пустое пространство для разделения последующих дополнений. Здесь я сделал для ассоциативного массива, который я назвал master_array:

master_array["group_list"]+="${new_group}";

Чтобы выполнить последовательность через группы в том порядке, в котором вы их добавили, выполните последовательность из поля group_list в цикле для, затем вы можете получить доступ к полям группы в ассоциативном массиве. Здесь фрагмент кода для одного, который я написал для master_array:

for group in ${master_array["group_list"]}; do
    echo "${group}";
    echo "${master_array[${group},destination_directory]}";
done

и здесь вывод из этого кода:

"linux"
"${HOME}/Backup/home4"
"data"
"${HOME}/Backup/home4/data"
"pictures"
"${HOME}/Backup/home4/pictures"
"pictures-archive"
"${HOME}/Backup/home4/pictures-archive"
"music"
"${HOME}/Backup/home4/music"

Это похоже на предложение Джонатана Леффлера, но хранит данные с ассоциативным массивом, а не требует сохранения двух отдельных непересекающихся массивов. Как вы можете видеть, это не в произвольном порядке, ни в алфавитном порядке, а в порядке, в котором я добавил их в массив.

Кроме того, если у вас есть подгруппы, вы можете создавать списки подгрупп для каждой группы, а также следовать через их. Это причина, по которой я сделал это таким образом, чтобы облегчить доступ к множеству массивов для доступа к ассоциативному массиву, а также разрешить расширение для новых подгрупп без необходимости изменять код.

EDIT: исправлено несколько опечаток

Ответ 3

Мой подход заключается в том, чтобы сначала создать отсортированный массив ключей:

keys=( $( echo ${!dict[@]} | tr ' ' $'\n' | sort ) )
for k in ${keys[@]}; do
    echo "$k=${dict[$k]}"
done