对作为参数传递给函数的数组进行排序

时间:2014-06-23 13:55:24

标签: arrays bash function sorting

我希望有一些帮助函数进行数组排序,我可以 随时随地使用。所以我做了这样的事情。它有效。

#!/usr/bin/bash

# sorts an array given as param
function array_sort {

    declare -a source_array=("${!1}")
    sorted_array=($(for elmnt in "${source_array[@]}"; do echo $elmnt; done | sort))
    echo "${sorted_array[@]}" # wont echo to stdout, because of assignment to a variable
}

# TEST CASE

# orginal array
arr=(c b a 3 2 1)

#assign function call to a variable
sorted=$(array_sort arr[@])

# echo-out the results
echo ${sorted[@]}

我的问题是,在从函数返回数组元素(没有实现更好的排序算法)方面,是否有一些更好(更干净)的方法呢?

3 个答案:

答案 0 :(得分:1)

如果你想要一个强大的排序功能(即能完美处理空格和换行的那个),你可以考虑在Bash中实现一个排序算法:这里是一个快速排序。

quicksort() {
    # sorts the positional elements wrt alphanumerical sort
    # return is in array quicksort_ret
    if (($#==0)); then
        quicksort_ret=()
        return
    fi
    local pivot=$1 greater=() lower=() i
    shift
    for i; do
        if [[ "$i" < "$pivot" ]]; then
            lower+=( "$i" )
        else
            greater+=( "$i" )
        fi
    done
    quicksort "${greater[@]}"
    greater=( "${quicksort_ret[@]}" )
    quicksort "${lower[@]}"
    quicksort_ret+=( "$pivot" "${greater[@]}" )
}


$ quicksort c b a 3 2 1
$ printf '%s\n' "${quicksort_ret[@]}"
1
2
3
a
b
c

您可以在行

中更改订购测试
if [[ "$i" < "$pivot" ]]; then
无论你喜欢什么,都可以。例如,对于仅数字排序,您可以使用

if ((i<pivot)); then

您甚至可以使用将扩展为排序函数的变量(例如,quicksort_order)。在这种情况下,请用

替换前一行
if $quicksort_order "$i" "$pivot"; then

并使用,例如,如果你想要字母数字排序:

order_alnum() { [[ $1 < $2 ]]; }
quicksort_order=order_alnum

quicksort函数使用输入的位置参数和输出的变量quicksort_ret。现在很容易围绕这个函数创建一个包装器来处理数组名称作为输入。


对于一种方法,与您的方法一样,使用sort但修复了通配符和空格的问题(但没有解决换行问题)。使用内置mapfile,所以这只是Bash≥4。对于Bash&lt; 4,还有其他解决方法(但是你不应该再使用Bash&lt; 4了)。

#!/usr/bin/bash

# sorts an array given as param
# return is in array sorted_array
array_sort() {
    mapfile -t sorted_array < <( printf '%s\n' "${!1}" | sort )
}

# TEST CASE 1
echo "Test 1"
# original array
arr=(c b a 3 2 1)
# sort array
array_sort "arr[@]"
# display array
declare -p "sorted_array"

# TEST CASE 2
echo "Test 2"
# original array
arr=( '*' 'a space in this field' )
# sort array
array_sort "arr[@]"
# display array
declare -p "sorted_array"

# TEST CASE 3 (fails)
echo "Test 3"
# original array
arr=( $'there is\na newline\nin this array' )
# sort array
array_sort "arr[@]"
# display array
declare -p "sorted_array"

将输出:

Test 1
declare -a sorted_array='([0]="1" [1]="2" [2]="3" [3]="a" [4]="b" [5]="c")'
Test 2
declare -a sorted_array='([0]="*" [1]="a space in this field")'
Test 3
declare -a sorted_array='([0]="a newline" [1]="in this array" [2]="there is")'

在评论中回答您的问题:

  

这样我就必须知道sorted_array变量的名称,才能在我的脚本中使用它。这可以避免吗?

如果要提供已排序数组的名称,请将array_sort修改为:

array_sort() {
    # $1 is the name of array to sort (with the trailing [@])
    # $2 is the name of the returned array (without [@])
    # Note: name of output array can be name of input array
    mapfile -t "$2" < <( printf '%s\n' "${!1}" | sort )
}

并用作:

$ a=( a g e z j r )
$ array_sort "a[@]" a_sorted
$ declare -p a_sorted
declare -a a_sorted='([0]="a" [1]="e" [2]="g" [3]="j" [4]="r" [5]="z")'

如果你想在我的第一个答案中使用quicksort函数,你可以使用包装函数(抱歉名称)(*)

quicksort_gniourf() {
    # $1 is the name of array to sort (with the trailing [@])
    # $2 is the name of the returned array (without [@])
    # Note: name of output array can be name of input array
    # This is a wrapper function around the quicksort function
    quicksort "${!1}"
    local k=0 v
    declare -g "$2=()"
    for v in "${quicksort_ret[@]}"; do
        printf -v "$2[$k]" '%s' "$v"
        ((++k))
    done
}

并用作(这里我使用相同的数组名称输入和输出):

$ a=( a g e z j r )
$ quicksort_gniourf "a[@]" a
$ declare -p a
declare -a a='([0]="a" [1]="e" [2]="g" [3]="j" [4]="r" [5]="z")'
  

另外,你如何回应结果数组,阻止*的扩展,declare -p可以,但printf或echo会扩展文件名?

使用array打印数组echo而不扩展通配符(观察引号):

echo "${array[@]}"

并使用printf,每行一个字段(观察引号):

printf '%s\n' "${array[@]}"

(*)正如@konsolebox在评论中提到的那样,declare -g出现在bash 4.2中。如果您愿意,可以将此行替换为eval "$2=()"(此时它相当安全,因为$2应该是变量名称。)

答案 1 :(得分:0)

您可以按如下方式对数组进行排序:

arr=(c b a 3 2 1)
sarr=( $(sort < <(printf "%s\n" "${arr[@]}")) )
printf "%s\n" "${sarr[@]}"
1
2
3
a
b
c

编辑:将其变为函数:

array_sort() {
    declare -a source_array=("${!1}");
    sarr=( $(sort < <(printf "%s\n" "${arr[@]}")) );
    echo "${sarr[@]}";
}

答案 2 :(得分:0)

PlayShell&#39; array/sort.sh转发,这是一个使用Quicksort算法的纯Bash解决方案。

# ---- array.sh ----

# array_copy (avar <src>, avar <dest>) :: boolean
#
# Copies a whole array including index (key) structure.
#
# For a faster method that does not copy key structure, see
# array_get_all().
#
# This function will return true status code even if the source array
# is empty.  It may only return false if other problem occurs like for
# example if source or destination array is not an indexed array
# variable or if the two array variables are not compatible.
# On the other hand, array_get_all() returns false if source array is
# empty.
#
function array_copy {
    local -i __I
    eval "$2=() && for __I in \${!$1[@]}; do $2[__I]=\${$1[__I]}; done"

    # I hope AVAR=() does not reset variable's attributes.  I've been
    # wondering if I should use 'unset AVAR\[*\]' instead.  The latter
    # version probably is a bit slower though since it's a builtin call.
}

# array_reset (avar <array>, [mixed <element_value>, ...])
#
# Clears an array or resets it to optional elements.
#
function array_reset {
    eval "$1=(\"\${@:2}\")"
}

# ---- array/sort.sh ----

# ----------------------------------------------------------------------

# array/sort.sh
#
# A portable utility that provides a function that sorts an array of a
# specific type.  The sorted output can be in the form of values or
# indices.
#
# This methods were based from QuickSort (the one described in
# "A Book on C 4th Ed.").
#
# Credits have got to be given to the authors of the book
# "A Book on C 4th Ed." for this great algorithm.  The algorithm was
# originally described by someone and was completely explained in the
# book with an implementation that's written in C.
#
# I knew C from many sources but most of what I learned came from this
# book and I therefore recommend it to starters for a good start and
# also to experienced programmers for a good reference and new methods
# that they may discover from it.
#
# I hope you enjoy using these functions and/or algorithms.
#
# Author: konsolebox
# Copyright free, 2008-2013

# ----------------------------------------------------------------------


# array_sort
# (
#   ["from=<array>"],
#   ["type=<string|integer>"],
#   ["to=<array>"],
#   ["as=<values|indices>"],
#   ["--" [ SOURCEVALUES[@] ]]
# )
#
function array_sort {
    [[ $# -eq 0 ]] && return

    local __FROM __TYPE __TO __AS
    local -a __ARRAY
    local -a -i __INDICES

    while [[ $# -gt 0 ]]; do
        case "$1" in
        from=*)
            __FROM=${1#from=}
            ;;
        type=*)
            __TYPE=${1#type=}
            ;;
        to=*)
            __TO=${1#to=}
            ;;
        as=*)
            __AS=${1#as=}
            ;;
        --)
            shift
            break
            ;;
        #beginsyntaxcheckblock
        *)
            array_sort_error "unknown parameter: $1"
            ;;
        #endsyntaxcheckblock
        esac
        shift
    done

    #beginsyntaxcheckblock
    [[ -n $__FROM && $__FROM != [[:alpha:]_]*([[:alpha:][:digit:]_]) ]] && \
        array_sort_error "variable name not valid for the source array: $__FROM"
    [[ -n $__TYPE && $__TYPE != @(string|integer) ]] && \
        array_sort_error "argument is not valid for type: $__TYPE"
    [[ -n $__TO && $__TO != [[:alpha:]_]*([[:alpha:][:digit:]_]) ]] && \
        array_sort_error "variable name not valid for the target array: $__TO"
    [[ -n $__AS && $__AS != @(values|indices) ]] && \
        array_sort_error "argument is not valid for as: $__AS"
    [[ -z $__FROM && $# -eq 0 ]] && \
        array_sort_error "a source should be specified either by 'from=<array>' or '-- CONTENTS[@]'"
    #endsyntaxcheckblock

    if [[ $# -gt 0 ]]; then
        __ARRAY=("$@")
    elif [[ -n $__FROM ]]; then
        array_copy "$__FROM" __ARRAY || \
            array_sort_error "failed to make a temporary working copy of $__FROM."
    fi

    [[ -z $__TYPE ]] && __TYPE=string
    [[ -z $__TO ]] && __TO=__
    [[ -z $__AS ]] && __AS=values

    __INDICES=("${!__ARRAY[@]}")

    if [[ ${#__INDICES[@]} -gt 1 ]]; then
        case "$__TYPE" in
        string)
            array_sort_strings 0 "$(( ${#__INDICES[@]} - 1 ))"
            ;;
        integer)
            array_sort_integers 0 "$(( ${#__INDICES[@]} - 1 ))"
            ;;
        esac
    fi

    case "$__AS" in
    values)
        local -i I J=0
        array_reset "$__TO"
        eval "for I in \"\${__INDICES[@]}\"; do ${__TO}[J++]=\${__ARRAY[I]}; done"
        ;;
    indices)
        eval "$__TO=(\"\${__INDICES[@]}\")"
        ;;
    esac
}


# array_sort_strings (uint LEFT, uint RIGHT)
#
function array_sort_strings {
    [[ $1 -lt $2 ]] || return

    local -i LEFT=$1 RIGHT=$2 PIVOT PARTITION

    if array_sort_strings_findpivot; then
        array_sort_strings_partition
        array_sort_strings "$LEFT" "$(( PARTITION - 1 ))"
        array_sort_strings "$PARTITION" "$RIGHT"
    fi
}


# array_sort_strings_findpivot () :: boolean
#
function array_sort_strings_findpivot {
    local -i A B C P MIDDLE

    (( MIDDLE = LEFT + (RIGHT - LEFT) / 2 ))

    (( A = __INDICES[LEFT] ))
    (( B = __INDICES[MIDDLE] ))
    (( C = __INDICES[RIGHT] ))

    [[ ${__ARRAY[A]} > "${__ARRAY[B]}" ]] && (( A = $B, B = $A ))
    [[ ${__ARRAY[A]} > "${__ARRAY[C]}" ]] && (( A = $C, C = $A ))
    [[ ${__ARRAY[B]} > "${__ARRAY[C]}" ]] && (( B = $C, C = $B ))

    if [[ ${__ARRAY[A]} < "${__ARRAY[B]}" ]]; then
        PIVOT=$B
        return 0
    fi

    if [[ ${__ARRAY[B]} < "${__ARRAY[C]}" ]]; then
        PIVOT=$C
        return 0
    fi

    for (( P = LEFT + 1; P < MIDDLE; ++P )); do
        if [[ ${__ARRAY[P]} > "${__ARRAY[A]}" ]]; then
            PIVOT=$P
            return 0
        fi

        if [[ ${__ARRAY[P]} < "${__ARRAY[A]}" ]]; then
            PIVOT=$A
            return 0
        fi
    done

    for (( P = MIDDLE + 1; P < RIGHT; ++P )); do
        if [[ ${__ARRAY[P]} > "${__ARRAY[A]}" ]]; then
            PIVOT=$P
            return 0
        fi

        if [[ ${__ARRAY[P]} < "${__ARRAY[A]}" ]]; then
            PIVOT=$A
            return 0
        fi
    done

    return 1
}


# array_sort_strings_partition ()
#
function array_sort_strings_partition {
    local -i L R T
    local P=${__ARRAY[PIVOT]}

    for (( L = LEFT, R = RIGHT; L <= R; )); do
        while [[ ${__ARRAY[__INDICES[L]]} < "$P" ]]; do
            (( ++L ))
        done

        until [[ ${__ARRAY[__INDICES[R]]} < "$P" ]]; do
            (( --R ))
        done

        [[ L -lt R ]] && (( T = __INDICES[L], __INDICES[L] = __INDICES[R], __INDICES[R] = T, ++L, --R ))
    done

    (( PARTITION = L ))
}


# array_sort_integers (uint LEFT, uint RIGHT)
#
function array_sort_integers {
    [[ $1 -lt $2 ]] || return

    local -i LEFT=$1 RIGHT=$2 PIVOT PARTITION

    if array_sort_integers_findpivot; then
        array_sort_integers_partition
        array_sort_integers "$LEFT" "$(( PARTITION - 1 ))"
        array_sort_integers "$PARTITION" "$RIGHT"
    fi
}


# array_sort_integers_findpivot () :: boolean
#
function array_sort_integers_findpivot {
    local -i A B C P MIDDLE

    (( MIDDLE = LEFT + (RIGHT - LEFT) / 2 ))

    (( A = __INDICES[LEFT] ))
    (( B = __INDICES[MIDDLE] ))
    (( C = __INDICES[RIGHT] ))

    [[ __ARRAY[A] -gt __ARRAY[B] ]] && (( A = $B, B = $A ))
    [[ __ARRAY[A] -gt __ARRAY[C] ]] && (( A = $C, C = $A ))
    [[ __ARRAY[B] -gt __ARRAY[C] ]] && (( B = $C, C = $B ))

    if [[ __ARRAY[A] -lt __ARRAY[B] ]]; then
        PIVOT=$B
        return 0
    fi

    if [[ __ARRAY[B] -lt __ARRAY[C] ]]; then
        PIVOT=$C
        return 0
    fi

    for (( P = LEFT + 1; P < MIDDLE; ++P )); do
        if [[ __ARRAY[P] -gt __ARRAY[A] ]]; then
            PIVOT=$P
            return 0
        fi

        if [[ __ARRAY[P] -lt __ARRAY[A] ]]; then
            PIVOT=$A
            return 0
        fi
    done

    for (( P = MIDDLE + 1; P < RIGHT; ++P )); do
        if [[ __ARRAY[P] -gt __ARRAY[A] ]]; then
            PIVOT=$P
            return 0
        fi

        if [[ __ARRAY[P] -lt __ARRAY[A] ]]; then
            PIVOT=$A
            return 0
        fi
    done

    return 1
}


# array_sort_integers_partition ()
#
function array_sort_integers_partition {
    local -i L R T P

    for (( L = LEFT, R = RIGHT, P = __ARRAY[PIVOT]; L <= R; )); do
        for (( ; __ARRAY[__INDICES[L]] < P; ++L )); do
            continue
        done

        for (( ; __ARRAY[__INDICES[R]] >= P; --R )); do
            continue
        done

        [[ L -lt R ]] && (( T = __INDICES[L], __INDICES[L] = __INDICES[R], __INDICES[R] = T, ++L, --R ))
    done

    (( PARTITION = L ))
}


# array_sort_error (string <message>)
#
function array_sort_error {
    echo "array_sort: $1"
    exit 1
}


# ----------------------------------------------------------------------

# Footnotes:
#
# * In some versions of bash, conditional statements does not properly
#   parse the second string operand so sometimes this form doesn't work:
#
#   [[ $STRINGVAR1 < $STRINGVAR2 ]]
#
#   So to make it work, we have no choice but put it around quotes:
#
#   [[ $STRINGVAR1 < "$STRINGVAR2" ]]
#
# * In some versions of bash, a segmentation fault occurs when
#   assignment statements where sources are arrays are compounded.
#
#   (( A = __A0[INDEX1], B = __A0[INDEX2] ))

# ----------------------------------------------------------------------