如何根据关联数组中的值对关联数组的索引数组进行重新排序?

How to re-sort an indexed array of associative arrays, based on the values in the associative arrays?

在我的 shell 代码中,我有一个索引数组,其中包含关联数组的名称:

declare -A assoc1=([name]=aaa [age]=20)
declare -A assoc2=([name]=bbb [age]=40)
declare -A assoc3=([name]=ccc [age]=25)
indexed_array=(assoc1 assoc2 assoc3)

因此,使用上面的方法,${indexed_array[@]} 等于 assoc1 assoc2 assoc3

我想要一个 sort_array 函数,它可以对 indexed_array 中的值进行重新排序,以便将年龄最大的关联数组 (assoc2) 列在最前面或最后,如下所示:

new_indexed_array=( $(echo ${indexed_array[@]} | sort_by 'age' 'desc') )

之后我应该在新数组中获得重新排序的内容:

declare -p new_indexed_array
# gives "assoc2 assoc3 assoc1"

我有一些样板代码可以进入数组值,但无法进一步对数组进行排序..

function sort_by {
    # for each hash in the given array
    get_stdin # (custom func, sets $STDIN)
    for hash in ${STDIN[@]}
    do
      # get the hash keys
      hash_keys="$(eval "echo ${!$hash[@]}")"
      # for each key
      for hashkey in $hash_keys
      do
        # reset
        return_the_array=false
        # if $hashkey matches the key given
        if [ "$hashkey" = "" ];then
          # check the value of this one if highest/lowest
          # (compared to previous ones)
          # and then return if yes/mo (asc/desc)
        fi
        # if $return_the_array = true, then we found the right key and
        # it's higher/lower
        if [ "$return_the_array" = true ];then
          # do stuff
        fi
      done
    done
}

如果您有 Bash 4.3 或更新版本,您可以为此使用 namerefs,如下所示:

sort_by() {
    local arr field sort_params elem
    declare -n arr=
    field=

    # Build array with sort parameters
    [[  == 'desc' ]] && sort_params+=('-r')
    [[ $field == 'age' ]] && sort_params+=('-n')

    # Schwartzian transform
    for elem in "${arr[@]}"; do
        declare -n ref=$elem
        printf '%s\t%s\n' "${ref["$field"]}" "$elem"
    done | sort "${sort_params[@]}" | cut -f2
}

declare -A assoc1=([name]=aaa [age]=20)
declare -A assoc2=([name]=bbb [age]=40)
declare -A assoc3=([name]=ccc [age]=25)
indexed_array=(assoc1 assoc2 assoc3)

readarray -t byage < <(sort_by indexed_array age desc)
declare -p byage

readarray -t byname < <(sort_by indexed_array name asc)
declare -p byname

调用语法有点不同:

sort_by ARRAYNAME FIELD SORTORDER

并且输出是每行一个元素,因此要将其读回数组,我们必须使用类似 readarray 的方法(请参阅最后的示例)。

首先,我们使用 nameref 将数组名分配给 arr:

declare -n arr=

arr 现在的行为就好像它是实际数组一样。

然后,我们用sort的参数建一个数组:如果第三个参数是desc,我们就用-r,如果字段是age ,我们使用 -n。这可以做得更聪明一些,并检查该字段是否包含数值,并相应地设置 -n

然后我们遍历 arr 的元素,其中的元素是关联数组的名称。在循环中,我们将名称分配给 ref:

declare -n ref=$elem

ref 现在表现得像实际的关联数组。

为了排序,我们使用 Schwartzian transform(修饰 - 排序 - 取消修饰)打印带有所选字段名称的行,然后是数组名称;例如,对于 age,我们会得到

20      assoc1
40      assoc2
25      assoc3

使用适当的参数将其通过管道传输到 sort,然后使用 cut -f2 我们再次删除排序字段。

示例的输出如下所示:

declare -a byage=([0]="assoc2" [1]="assoc3" [2]="assoc1")
declare -a byname=([0]="assoc1" [1]="assoc2" [2]="assoc3")

注意 declare -n 在函数中声明局部参数,因此它们不会污染全局命名空间。

仅供参考,我使用的是已接受答案的修改版本:

function sort_by {
    local field sort_params elem
    field=
    # Build array with sort parameters
    [[  == 'desc' ]] && sort_params+=('-r')
    [[ $field == 'age' ]] && sort_params+=('-n')
    # Schwartzian transform, 
    # get piped array contents from $(cat)
    while read -r elem; do
        declare -n ref=$elem
        printf '%s\t%s\n' "${ref["$field"]}" "$elem"
    done | sort "${sort_params[@]}" | cut -f2 | tr '\n' ' '
}

这个函数和接受的答案之间的区别在于,上面的这个函数期望接收索引数组的内容作为管道输入,而不是将数组名作为第一个参数..

因此,我的函数可以这样调用:

echo ${someIndexedArray[@]} | sort_by 'age' 'desc'

(这个函数在同一行输出所有内容,而不是换行)

这个(对我来说)的好处是它现在可以在我使用的 CMS 中工作,而且 sort_by 函数不需要知道数组的名称 - 这不会是通过 CMS。