Arrays 如何在Bash中对数组进行排序

Arrays 如何在Bash中对数组进行排序,arrays,bash,shell,sorting,Arrays,Bash,Shell,Sorting,我在Bash中有一个数组,例如: array=(a c b f 3 5) 我需要对数组进行排序。不仅以排序方式显示内容,还可以使用排序后的元素获取新数组。新排序的数组可以是全新的或旧的。原始响应: array=(a c b "f f" 3 5) readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort) 注意此版本处理包含特殊字符或空格的值(新行除外) 注意bash 4+支持readarra

我在Bash中有一个数组,例如:

array=(a c b f 3 5)
我需要对数组进行排序。不仅以排序方式显示内容,还可以使用排序后的元素获取新数组。新排序的数组可以是全新的或旧的。

原始响应:

array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)
注意此版本处理包含特殊字符或空格的值(新行除外)

注意bash 4+支持readarray


编辑根据@Dimitre的建议,我已将其更新为:

readarray -t sorted < <(printf '%s\0' "${array[@]}" | sort -z | xargs -0n1)

readarray-t排序<如果不需要处理数组元素中的特殊shell字符:

array=(a c b f 3 5)
sorted=($(printf '%s\n' "${array[@]}"|sort))
使用bash,您无论如何都需要一个外部排序程序

使用zsh不需要外部程序,并且可以轻松处理特殊的shell字符:

% array=('a a' c b f 3 5); printf '%s\n' "${(o)array[@]}" 
3
5
a a
b
c
f

ksh有
set-s
来对ASCI进行逻辑排序。

我不确定您是否需要Bash中的外部排序程序

下面是我对简单冒泡排序算法的实现

function bubble_sort()
{   #
    # Sorts all positional arguments and echoes them back.
    #
    # Bubble sorting lets the heaviest (longest) element sink to the bottom.
    #
    local array=($@) max=$(($# - 1))
    while ((max > 0))
    do
        local i=0
        while ((i < max))
        do
            if [ ${array[$i]} \> ${array[$((i + 1))]} ]
            then
                local t=${array[$i]}
                array[$i]=${array[$((i + 1))]}
                array[$((i + 1))]=$t
            fi
            ((i += 1))
        done
        ((max -= 1))
    done
    echo ${array[@]}
}

array=(a c b f 3 5)
echo " input: ${array[@]}"
echo "output: $(bubble_sort ${array[@]})"

在从慕尼黑到法兰克福的3个小时的火车旅行中(由于啤酒节明天开始,我很难到达法兰克福),我在想我的第一篇帖子。对于一般的排序函数,使用全局数组是一个更好的主意。以下函数处理任意字符串(换行符、空格等):

从中创建相同的输出

BSORT=(a c b 'z y' 3 5) 
bubble_sort
echo ${BSORT[@]}
请注意,Bash可能在内部使用智能指针,因此交换操作可能很便宜(尽管我对此表示怀疑)。然而,
bubble\u sort
证明了更高级的函数,如
merge\u sort
也在shell语言的范围内

试试这个:

echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
输出将是:

3 5 a b c f 3. 5. A. B C F
问题已解决。

您实际上不需要那么多代码:

IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS
由于
sort
排序,它会生成:

3 5
a c
b
f
接下来,排序=($(…)
部分
如果可以为数组中的每个元素计算一个唯一的整数,则调用的
$(…)
部分将导致其内容(
排序),如下所示:

tab='0123456789abcdefghijklmnopqrstuvwxyz'

# build the reversed ordinal map
for ((i = 0; i < ${#tab}; i++)); do
    declare -g ord_${tab:i:1}=$i
done

function sexy_int() {
    local sum=0
    local i ch ref
    for ((i = 0; i < ${#1}; i++)); do
        ch="${1:i:1}"
        ref="ord_$ch"
        (( sum += ${!ref} ))
    done
    return $sum
}

sexy_int hello
echo "hello -> $?"
sexy_int world
echo "world -> $?"
  • 专业,快
  • 缺点。重复的元素被合并,并且不可能将内容映射到32位唯一整数

另一种解决方案,使用外部
排序
并处理任何特殊字符(NULs除外:)。应该与bash-3.2和GNU或BSD
sort
(遗憾的是,POSIX不包括
-z

然后将以null结尾的元素列表传递给
排序
-z
选项使它读取以null结尾的元素,对它们进行排序,并输出以null结尾的元素。如果只需要获取唯一元素,可以传递
-u
,因为它比
uniq-z
更便于携带。LC_ALL=C
确保了独立于区域设置的稳定排序顺序-有时对脚本很有用。如果希望排序符合区域设置,请将其删除


新_数组的回显内容将为:

3 5 a b c f

下面是一个纯Bash快速排序实现:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
qsort() {
   local pivot i smaller=() larger=()
   qsort_ret=()
   (($#==0)) && return 0
   pivot=$1
   shift
   for i; do
      # This sorts strings lexicographically.
      if [[ $i < $pivot ]]; then
         smaller+=( "$i" )
      else
         larger+=( "$i" )
      fi
   done
   qsort "${smaller[@]}"
   smaller=( "${qsort_ret[@]}" )
   qsort "${larger[@]}"
   larger=( "${qsort_ret[@]}" )
   qsort_ret=( "${smaller[@]}" "$pivot" "${larger[@]}" )
}
此实现是递归的…因此这里有一个迭代快速排序:

#!/bin/bash

# quicksorts positional arguments
# return is in array qsort_ret
# Note: iterative, NOT recursive! :)
qsort() {
   (($#==0)) && return 0
   local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
   qsort_ret=("$@")
   while ((${#stack[@]})); do
      beg=${stack[0]}
      end=${stack[1]}
      stack=( "${stack[@]:2}" )
      smaller=() larger=()
      pivot=${qsort_ret[beg]}
      for ((i=beg+1;i<=end;++i)); do
         if [[ "${qsort_ret[i]}" < "$pivot" ]]; then
            smaller+=( "${qsort_ret[i]}" )
         else
            larger+=( "${qsort_ret[i]}" )
         fi
      done
      qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
      if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
      if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
   done
}
和使用:

$ qsort compare_mtime *
$ declare -p qsort_ret
将当前文件夹中的文件按修改时间排序(最新优先)

注意。这些函数都是纯Bash!没有外部实用程序,也没有子shell!它们可以安全地写入任何有趣的符号(空格、换行符、全局字符等)

注2。测试
[[[$i<$pivot]]
是正确的。它使用字典式字符串比较。如果您的数组只包含整数,并且您希望按数字排序,请改用
((i
。 请不要编辑此答案以更改此答案。它已经被编辑(并回滚)了好几次。我在这里给出的测试是正确的,并且与示例中给出的输出相对应:示例使用字符串和数字,目的是按照字典顺序对其进行排序。在这种情况下使用
((i
是错误的。

a=(eb'cd')
shuf-e“${a[@]}”排序>/tmp/f

mapfile-tgtl;博士

对数组
a_in
进行排序,并将结果存储在
a_out
(元素不得嵌入换行符[1] ):

Bash v4+:

readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
由于使用了不带选项的
sort
,这将产生词汇排序(数字在字母之前排序,数字序列按词汇处理,而不是按数字处理):

如果您希望按第1个字段进行数字排序,则应使用
排序-k1,1n
而不是仅使用
排序
,这将产生(非数字排序优先于数字,数字排序正确):


[1] 要使用嵌入的换行符处理元素,请使用以下变量(bashv4+,带有GNU
sort
):
readarray-d'-tau out<
sorted=($(echo${array[@]}tr”“”\n“| sort))

本着bash/linux的精神,我将为每一步提供最好的命令行工具<代码>排序
执行主要任务,但需要用换行符而不是空格分隔输入,因此上面非常简单的管道只需执行以下操作:

回显数组内容-->用换行符替换空格-->排序

$()
是用来回显结果的

($())
是将“回显结果”放入数组中

注意:正如@sorontar在另一个问题的回答中提到的:

排序=($(…)部分正在使用“拆分和全局”运算符。您应该关闭glob:set-f或set-o noglob或shopt-op noglob或类似*的数组元素将展开为文件列表


对于常见的空格和换行符问题,有一个解决方法:

使用不在原始数组中的字符(如
$'\1'
$'\4'
或类似字符)

此函数用于完成以下任务:

# Sort an Array may have spaces or newlines with a workaround (wa=$'\4')
sortarray(){ local wa=$'\4' IFS=''
             if [[ $* =~ [$wa] ]]; then
                 echo "$0: error: array contains the workaround char" >&2
                 exit 1
             fi

             set -f; local IFS=$'\n' x nl=$'\n'
             set -- $(printf '%s\n' "${@//$nl/$wa}" | sort -n)
             for    x
             do     sorted+=("${x//$wa/$nl}")
             done
       }
这将对数组进行排序:

$ array=( a b 'c d' $'e\nf' $'g\1h')
$ sortarray "${array[@]}"
$ printf '<%s>\n' "${sorted[@]}"
<a>
<b>
<c d>
<e
f>
<gh>
描述
  • 我们设置了两个局部变量
    wa
    (变通字符)和一个空IFS
  • 然后(使用ifs null)进行测试
    local e new_array=()
    while IFS= read -r -d '' e; do
        new_array+=( "${e}" )
    done < <(printf "%s\0" "${array[@]}" | LC_ALL=C sort -z)
    
    for e in "${array[@]}"; do
        printf "%s\0" "${e}"
    done
    
    array=(a c b f 3 5)
    new_array=($(echo "${array[@]}" | sed 's/ /\n/g' | sort))    
    echo ${new_array[@]}
    
    3 5 a b c f
    
    #!/bin/bash
    
    # quicksorts positional arguments
    # return is in array qsort_ret
    qsort() {
       local pivot i smaller=() larger=()
       qsort_ret=()
       (($#==0)) && return 0
       pivot=$1
       shift
       for i; do
          # This sorts strings lexicographically.
          if [[ $i < $pivot ]]; then
             smaller+=( "$i" )
          else
             larger+=( "$i" )
          fi
       done
       qsort "${smaller[@]}"
       smaller=( "${qsort_ret[@]}" )
       qsort "${larger[@]}"
       larger=( "${qsort_ret[@]}" )
       qsort_ret=( "${smaller[@]}" "$pivot" "${larger[@]}" )
    }
    
    $ array=(a c b f 3 5)
    $ qsort "${array[@]}"
    $ declare -p qsort_ret
    declare -a qsort_ret='([0]="3" [1]="5" [2]="a" [3]="b" [4]="c" [5]="f")'
    
    #!/bin/bash
    
    # quicksorts positional arguments
    # return is in array qsort_ret
    # Note: iterative, NOT recursive! :)
    qsort() {
       (($#==0)) && return 0
       local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
       qsort_ret=("$@")
       while ((${#stack[@]})); do
          beg=${stack[0]}
          end=${stack[1]}
          stack=( "${stack[@]:2}" )
          smaller=() larger=()
          pivot=${qsort_ret[beg]}
          for ((i=beg+1;i<=end;++i)); do
             if [[ "${qsort_ret[i]}" < "$pivot" ]]; then
                smaller+=( "${qsort_ret[i]}" )
             else
                larger+=( "${qsort_ret[i]}" )
             fi
          done
          qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
          if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
          if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
       done
    }
    
    #!/bin/bash
    
    # quicksorts positional arguments
    # return is in array qsort_ret
    # Note: iterative, NOT recursive! :)
    # First argument is a function name that takes two arguments and compares them
    qsort() {
       (($#<=1)) && return 0
       local compare_fun=$1
       shift
       local stack=( 0 $(($#-1)) ) beg end i pivot smaller larger
       qsort_ret=("$@")
       while ((${#stack[@]})); do
          beg=${stack[0]}
          end=${stack[1]}
          stack=( "${stack[@]:2}" )
          smaller=() larger=()
          pivot=${qsort_ret[beg]}
          for ((i=beg+1;i<=end;++i)); do
             if "$compare_fun" "${qsort_ret[i]}" "$pivot"; then
                smaller+=( "${qsort_ret[i]}" )
             else
                larger+=( "${qsort_ret[i]}" )
             fi
          done
          qsort_ret=( "${qsort_ret[@]:0:beg}" "${smaller[@]}" "$pivot" "${larger[@]}" "${qsort_ret[@]:end+1}" )
          if ((${#smaller[@]}>=2)); then stack+=( "$beg" "$((beg+${#smaller[@]}-1))" ); fi
          if ((${#larger[@]}>=2)); then stack+=( "$((end-${#larger[@]}+1))" "$end" ); fi
       done
    }
    
    compare_mtime() { [[ $1 -nt $2 ]]; }
    
    $ qsort compare_mtime *
    $ declare -p qsort_ret
    
    a=(e b 'c d')
    shuf -e "${a[@]}" | sort >/tmp/f
    mapfile -t g </tmp/f
    
    readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)
    
    IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
    
    *
    10
    5
    a c
    b
    f
    
    *
    a c
    b
    f
    5
    10
    
    # Sort an Array may have spaces or newlines with a workaround (wa=$'\4')
    sortarray(){ local wa=$'\4' IFS=''
                 if [[ $* =~ [$wa] ]]; then
                     echo "$0: error: array contains the workaround char" >&2
                     exit 1
                 fi
    
                 set -f; local IFS=$'\n' x nl=$'\n'
                 set -- $(printf '%s\n' "${@//$nl/$wa}" | sort -n)
                 for    x
                 do     sorted+=("${x//$wa/$nl}")
                 done
           }
    
    $ array=( a b 'c d' $'e\nf' $'g\1h')
    $ sortarray "${array[@]}"
    $ printf '<%s>\n' "${sorted[@]}"
    <a>
    <b>
    <c d>
    <e
    f>
    <gh>
    
    $ array=( a b 'c d' $'e\nf' $'g\4h')
    $ sortarray "${array[@]}"
    ./script: error: array contains the workaround char
    
    #!/bin/bash
    array=(.....)
    index_of_element1=0
    
    while (( ${index_of_element1} < ${#array[@]} )); do
    
        element_1="${array[${index_of_element1}]}"
    
        index_of_element2=$((index_of_element1 + 1))
        index_of_min=${index_of_element1}
    
        min_element="${element_1}"
    
            for element_2 in "${array[@]:$((index_of_element1 + 1))}"; do
                min_element="`printf "%s\n%s" "${min_element}" "${element_2}" | sort | head -n+1`"      
                if [[ "${min_element}" == "${element_2}" ]]; then
                    index_of_min=${index_of_element2}
                fi
                let index_of_element2++
            done
    
            array[${index_of_element1}]="${min_element}"
            array[${index_of_min}]="${element_1}"
    
        let index_of_element1++
    done
    
    set +m
    shopt -s lastpipe
    
    sort_array() { 
        declare -n ref=$1
        set "${ref[@]}"
        printf '%s\n' "$@"
        | sort \
        | mapfile -t $ref
    }
    
    array=(z y x); sort_array array; declare -p array
    declare -a array=([0]="x" [1]="y" [2]="z")