Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

I'm wondering how should I sort the associated array in bash? I tried the manual, but seems nothing related to sort.

The current solution is echo everything out, and use external program i.e key value | sort -k2

That looks inefficient to me.

An example of array was:

A['192.168.2.2']=5
A['192.168.3.2']=1
A['192.168.1.1']=9

And I'll be looking for the top 2 used IP address, which is 192.168.1.1 and 192.168.2.2, that is, I need to sort this array by it's value.

share|improve this question
    
It sounds like what you are trying to accomplish is too complex for bash to do easily. What are you trying to do? –  jw013 Oct 16 '12 at 15:10
    
And asking for the "right" way is just asking for trouble. ;) –  lynxlynxlynx Oct 16 '12 at 17:09
    
mywiki.wooledge.org/BashWeaknesses –  jordanm Oct 16 '12 at 20:04
    
Is switching to zsh an option? Otherwise, relying on external tools is common in shell programming. –  Gilles Oct 16 '12 at 21:47
1  
If you getting this data from parsing and have gawk available, I tend to just do it all in awk. –  jordanm Oct 17 '12 at 2:40

3 Answers 3

Zsh has a built-in way to sort lists. However, I don't think there's a way to sort the values while keeping the correlation with the keys using parameter expansion flags and subscript flags, which means that an explicit loop is necessary. Assuming that your values don't contain a null character, you can build an array containing the values and keys concatenated with a null character in between, and sort that.

keys=("${(@k)A}")
values=("${(@v)A}")
combined=()
for ((i=1; i <= $#values; i++)) { combined[i]=($values[i]$'\0'$keys[i]); }
keys_sorted_by_decreasing_value=("${${(@On)combined}#*$'\0'}")
keys_of_the_top_two_values=("${(@)keys_sorted_by_decreasing_value[1,2]}")

EDIT by @sch: the first 4 lines can be simplified to

combined=()
for k v ("${(@kv)A}") combined+=($k$'\0'$v)

The variables keys and values contain the keys and values of A in an arbitrary but consistent order. You can write keys=(${(k)A}) if there are no empty keys, and similarly for values. keys_sorted_by_decreasing_value sorts keys lexicographically, add the n flag to sort numerically (9 before 10) and remove O if you want to sort in increasing order (in which case the top two values can be obtained with the subscript [-2,-1]).

Ksh93 has a way to sort the positional parameters only, with set -s; this also exists in zsh but not in bash 4.2. Assuming your values don't contain newlines or control characters that sort before newlines:

keys=("${!A[@]}")
combined=()
for ((i=0; i <= ${#keys}; i++)); do combined[i]=(${A[${keys[$i]}]}$'\n'${keys[$i]}); done
set -A sorted -s "${combined[@]}"
top_combined=${sorted[${#sorted[@]}-1]}  # -2 for the next-to-largest, etc.
top_key=${top_combined#*$'\n'}

This is all pretty complex, so you might as well go for the external sort, which is a lot easier to write. Assuming that neither keys nor values contain control characters, in ksh or bash:

IFS=$'\n'; set -f
keys_sorted_by_decreasing_value=($(
    for k in "${!A[@]}"; do printf '%s\t%s\n' "${A[$k]}" "$k"; done |
    sort | sed $'s/\t.*//'
  ))
share|improve this answer

A shell is before all a tool to run other tools. It sounds to me you're after a programming language like perl, ruby, python...

Having said that, here is some possible solution for zsh.

In zsh, you can get a sorted list of the keys of an associated array (${(kOn)A}) or of the values (${(On)A}) but not directly a list of keys from the sorted list of values (AFAIK), but you could do things like:

typeset -A A B
A=(
  192.168.2.2 5
  192.168.3.2 1
  192.168.1.1 9
  192.168.8.1 9
)

for v ("${(@nO)A}") B+=("${(@kv)A[(eR)$v]}")

That is, order (O) the list of values ($A) numerically (n) and for each value, add the key/value pairs matching the value $v (e for exact match, R to get the reverse list based on value, not key) and add that to the B associative array.

Then, you'd get the sorted list in B:

$ printf '%s => %s\n' "${(@kv)B}"
192.168.8.1 => 9
192.168.1.1 => 9
192.168.2.2 => 5
192.168.3.2 => 1

And you can select the first 2 keys with

$ print -rl -- ${${(k)B}[1,2]}
192.168.8.1
192.168.1.1
share|improve this answer

"Associative Array" often means that the data in array have real-world meaning, which is your case. External unix sort is ideal for this task, and few C programmer can out-perform unix sort. Especially for big data you can tailor, slice, fork, bring full power of unix and shell. This is why so many shell and awk platform there don't bother with a sort.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.