在 bash 或 zsh 中对关联数组进行排序的正确方法是什么?

在 bash 或 zsh 中对关联数组进行排序的正确方法是什么?

我想知道如何在 bash 中对关联数组进行排序?我尝试了手册,但似乎与排序无关。

当前的解决方案是回显所有内容,并使用外部程序,即key value | sort -k2

这对我来说看起来效率很低。

数组的一个例子是:

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

我将查找最常用的 2 个 IP 地址,即 192.168.1.1 和 192.168.2.2,也就是说,我需要按该数组的值对该数组进行排序。

答案1

Zsh 有一种内置的方法来对列表进行排序。但是,我认为没有一种方法可以对值进行排序,同时保持与键的相关性参数扩展标志下标标志,这意味着需要显式循环。假设您的值不包含空字符,您可以构建一个包含值和键的数组,其中值和键之间用空字符连接,然后对其进行排序。

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]}")

@sch 编辑:前 4 行可以简化为

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

变量keys和包含以任意但一致的顺序values的键和值。如果没有空键,则A可以写入,值也类似。按字典顺序对键进行排序,添加标记以按数字排序(之前),如果要按升序排序则删除标记(在这种情况下,可以使用下标获得前两个值)。keys=(${(k)A})keys_sorted_by_decreasing_valuen910O[-2,-1]

Ksh93 有一种仅对位置参数进行排序的方法,使用set -s;这也存在于 zsh 中,但不存在于 bash 4.2 中。假设您的值不包含换行符或在换行符之前排序的控制字符:

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'}

这一切都非常复杂,因此您不妨采用外部排序,这更容易编写。假设 ksh 或 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.*//'
  ))

答案2

在 zsh 中,您可以获取关联数组 ( ${(kOn)A}) 或值 ( ${(On)A}) 的键的排序列表,但不能直接从已排序的值列表中获取键列表 (AFAIK),但您可以执行以下操作:

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

for v ("${(@nO)assoc}") ordered_keys+=("${(@k)assoc[(eR)$v]}")

也就是说,按数字 ( )和每个值对值 ( )O列表进行排序 ( ) ,添加匹配的ey(为了精确匹配,根据值而不是键获取反向列表)并将其添加到数组中。$assocnforvkeRordered_keys

答案3

按 KEY 对 bash 关联数组进行排序的最佳方法是不是把它分类。

相反,获取 KEYS 列表,将该列表作为变量排序,然后迭代该列表。示例:假设您有一个 IP 地址(键)和主机名(值)的数组:

替代方案:从 KEY 创建新列表,转换为行,对其进行排序,转换回列表,然后使用它迭代数组。

declare -A ADDR
ADDR[192.168.1.1]="host1"
ADDR[192.168.1.2]="host2"
etc...

KEYS=`echo ${!ADDR[@]} | tr ' ' '\012' | sort | tr '\012' ' '`
for KEY in $KEYS; do
  VAL=${ADDR[$KEY]}
  echo "KEY=[$KEY] VAL=[$VAL]"
done

答案4

“关联数组”通常意味着数组中的数据具有现实世界的含义,这就是您的情况。外部 unix 排序非常适合此任务,很少有 C 程序员能够胜过 unix 排序。特别是对于大数据,您可以定制、切片、分叉,发挥 UNIX 和 shell 的全部功能。这就是为什么这么多 shell 和 awk 平台不关心排序的原因。

相关内容