35

I get the following output:

Pushkin - 100500 
Gogol - 23 
Dostoyevsky - 9999

Which is the result of the following script:

for k in "${!authors[@]}"
do
    echo $k ' - ' ${authors["$k"]}
done   

All I want is to get the output like this:

Pushkin - 100500 
Dostoyevsky - 9999
Gogol - 23

which means that the keys in associative array should be sorted by value. Is there an easy method to do so?

0

6 Answers 6

38

You can easily sort your output, in descending numerical order of the 3rd field:

for k in "${!authors[@]}"
do
    echo $k ' - ' ${authors["$k"]}
done |
sort -rn -k3

See sort(1) for more about the sort command. This just sorts output lines; I don't know of any way to sort an array directly in bash.

I also can't see how the above can give you names ("Pushkin" et al.) as array keys. In bash, array keys are always integers.

Sign up to request clarification or add additional context in comments.

3 Comments

Bash 4.0 adds associative arrays where the keys can be strings.
Well I'll be... you're right. It just needs declare -A authors.
Your solution works. As far as I understand, it just sorts output strings, not the contents of the associative array, but anyway, the output produced is what I need.
9

Alternatively you can sort the indexes and use the sorted list of indexes to loop through the array:

authors_indexes=( ${!authors[@]} )
IFS=$'\n' authors_sorted=( $(echo -e "${authors_indexes[@]/%/\n}" | sed -r -e 's/^ *//' -e '/^$/d' | sort) )

for k in "${authors_sorted[@]}"; do
  echo $k ' - ' ${authors["$k"]}
done 

3 Comments

Boo, hiss re: echo -e. Consider printf '%s\n' "${authors_indexes[@]}" to expand an array into multiple lines in a way that doesn't depend on violations of POSIX sh (not just extensions, but actual violations: Any argument other than -n is required to print its text, unless it contains backslashes or -n is present, in which case behavior is entirely implementation-defined). Or printf '%s\0' "${array[@]}" to expand the array as NUL-delimit strings, thus in a manner that works even if you have literal newlines in your keys (which are legal).
...see pubs.opengroup.org/onlinepubs/009695399/utilities/echo.html, particularly the APPLICATION USAGE section.
IFS is changed as there is no command on the line, only assignments. also taking Charles comment and removing the need for a indexes assignment, then the corrected solution is... oIFS="$IFS" IFS=$'\n' authors_sorted=( $( printf '%s\n' "${!authors[@]}" | sort ) ) IFS="$oIFS"
6

Extending the answer from @AndrewSchulman, using -rn as a global sort option reverses all columns. In this example, authors with the same associative array value will be output by reverse order of name.

For example

declare -A authors
authors=( [Pushkin]=10050 [Gogol]=23 [Dostoyevsky]=9999 [Tolstoy]=23 )

for k in "${!authors[@]}"
do
  echo $k ' - ' ${authors["$k"]}
done | sort -rn -k3

will output

Pushkin  -  10050
Dostoyevsky  -  9999
Tolstoy  -  23
Gogol  -  23
Options for sorting specific columns can be provided after the column specifier. i.e. sort -k3rn

Note that keys can be specified as spans. Here -k3 happens to be fine because it is the final span, but to use only column 3 explicitly (in case further columns were added), it should be specified as -k3,3, Similarly to sort by column three in descending order, and then column one in ascending order (which is probably what is desired in this example):

declare -A authors
authors=( [Pushkin]=10050 [Gogol]=23 [Dostoyevsky]=9999 [Tolstoy]=23 )
for k in "${!authors[@]}"
do
  echo $k ' - ' ${authors["$k"]}
done | sort -k3,3rn -k1,1

will output

Pushkin  -  10050
Dostoyevsky  -  9999
Gogol  -  23
Tolstoy  -  23

Comments

6

The best way to sort a bash associative array by VALUE is to NOT sort it.

Instead, get the list of VALUE:::KEYS, sort that list into a new KEY LIST, and iterate through the list.

declare -A ADDR
ADDR[192.168.1.3]="host3"
ADDR[192.168.1.1]="host1"
ADDR[192.168.1.2]="host2"

KEYS=$(
for KEY in ${!ADDR[@]}; do
  echo "${ADDR[$KEY]}:::$KEY"
done | sort | awk -F::: '{print $2}'
)

for KEY in $KEYS; do
  VAL=${ADDR[$KEY]}
  echo "KEY=[$KEY] VAL=[$VAL]"
done

output:
KEY=[192.168.1.1] VAL=[host1]
KEY=[192.168.1.2] VAL=[host2]
KEY=[192.168.1.3] VAL=[host3]

1 Comment

Except you can not make any assignments in the loop, as it is then part of a pipeline!
6

Do something with unsorted keys:

for key in ${!Map[@]}; do
   echo $key
done

Do something with sorted keys:

for key in $(for x in ${!Map[@]}; do echo $x; done | sort); do
   echo $key
done

Store sorted keys as array:

Keys=($(for x in ${!Map[@]}; do echo $x; done | sort))

2 Comments

To sort on the value but also print out a concatenated key<sep>value, using echo with a space in an inline nested loop can be problematic; My solution was to use printf and not use a space: for kvp in $(for key in ${!Map[@]}; do printf '%s:%s\n' "$key" "${Map[$key]}"; done | sort -t: -rn -k2); do echo "$kvp"; done
Or keep it straightforward and use "${Map[@]}" and echo "$x".
0

If you can assume the value is always a number (no spaces), but want to allow for the possibility of spaces in the key:

for k in "${!authors[@]}"; do 
  echo "${authors["$k"]} ${k}"
done | sort -rn | while read number author; do 
  echo "${author} - ${number}"
done

Example:

$ declare -A authors
$ authors=(['Shakespeare']=1 ['Kant']=2 ['Von Neumann']=3 ['Von Auersperg']=4)
$ for k in "${!authors[@]}"; do echo "${authors["$k"]} ${k}"; done | sort -rn | while read number author; do echo "${author} - ${number}"; done
Von Auersperg - 4
Von Neumann - 3
Kant - 2
Shakespeare - 1
$ 

The chosen answer seems to work if there are no spaces in the keys, but fails if there are:

$ declare -A authors
$ authors=(['Shakespeare']=1 ['Kant']=2 ['Von Neumann']=3 ['Von Auersperg']=4)
$ for k in "${!authors[@]}"; do echo $k ' - ' ${authors["$k"]}; done | sort -rn -k 3
Kant  -  2
Shakespeare  -  1
Von Neumann  -  3
Von Auersperg  -  4
$ 

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.