I try to iterate over an associative array in Bash.
It seems to be simple, but the loop doesn't follow the initial order of the array.
Here is a simple script to try:
#!/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
The output:
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
Why I don't have group1
, group2
, etc.?
I don't want to have an alphanum order, I just want that the loop follow the initial declaration's order of the array...
Is there a way?
So yes, they are always ordered. Arrays are implemented as a hash table.
PHP array is an ordered map, so, it's a map that keeps the order. array elements just keep the order since they were added that's all.
The best way to sort a bash associative array by KEY is to NOT sort it. Instead, get the list of KEYS, sort that list as a variable, and iterate through the list. Example: Suppose you have an array of IP addresses (keys) and host names (values):
Associative arrays, also called maps or dictionaries, are an abstract data type that can hold data in (key, value) pairs. Associative arrays have two important properties. Every key can only appear once, just like every phone number can only appear once in a directory.
As already pointed out, there is no mistake. Associative arrays are stored in a 'hash' order. If you want ordering, you don't use associative arrays. Or, you use a non-associative array as well as an associative array.
Keep a second (non-associative) array that identifies the keys in the order that they're created. Then step through the second array, using its contents to key the first (associative) array when printing the data. Like this:
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
The 'simpler option 2' was suggested by PesaThe in a comment, and should be used in preference to the 'convoluted option'.
Sample output:
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
You probably don't want to have two statements per line like that, but it emphasizes the parallelism between the handling of the two arrays.
The semicolons after the assignments in the question are not really necessary (though they do no active harm, beyond leaving the reader wondering 'why?').
My approach is to create a sorted array of keys first:
keys=( $( echo ${!dict[@]} | tr ' ' $'\n' | sort ) ) for k in ${keys[@]}; do echo "$k=${dict[$k]}" done
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With