如何在Bash中对数组排序


139

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

array=(a c b f 3 5)

我需要对数组进行排序。不仅以排序的方式显示内容,还可以使用排序的元素来获取新数组。新排序的数组可以是全新的数组,也可以是旧的数组。

Answers:


208

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

IFS=$'\n' sorted=($(sort <<<"${array[*]}"))
unset IFS

在元素中支持空格(只要它不是换行符),在Bash 3.x中工作。

例如:

$ array=("a c" b f "3 5")
$ IFS=$'\n' sorted=($(sort <<<"${array[*]}")); unset IFS
$ printf "[%s]\n" "${sorted[@]}"
[3 5]
[a c]
[b]
[f]

注: @sorontar已经指出,如果元素包含通配符,如需要小心*?

sorted =($(...))部分正在使用“ split and glob”运算符。您应该关闭glob:set -fset -o noglobshopt -op noglob或数组中的一个元素,如*将会扩展到文件列表。

发生了什么:

结果是按顺序发生了六件事:

  1. IFS=$'\n'
  2. "${array[*]}"
  3. <<<
  4. sort
  5. sorted=($(...))
  6. unset IFS

首先, IFS=$'\n'

这是我们操作的重要组成部分,它通过以下方式影响2和5的结果:

鉴于:

  • "${array[*]}" 扩展到以的第一个​​字符分隔的每个元素 IFS
  • sorted=() 通过分割的每个字符来创建元素 IFS

IFS=$'\n' 进行设置,以便使用新行作为分隔符扩展元素,然后以每行成为元素的方式创建元素。(即在新行上拆分。)

用新行定界很重要,因为这就是sort操作方式(按行排序)。仅用换行分隔不是很重要,但是需要保留包含空格或制表符的元素。

的默认值IFSspacetab,后跟换行,并且不适合我们的操作。

接下来的sort <<<"${array[*]}"部分

<<<在这里称为字符串,接受的扩展"${array[*]}",如上所述,并将其输入的标准输入sort

在我们的示例中,sort以下字符串被馈入:

a c
b
f
3 5

由于sort sort,它产生:

3 5
a c
b
f

接下来的sorted=($(...))部分

$(...)部分称为命令替换,使它的内容(sort <<<"${array[*]})作为普通命令运行,同时将所得的 标准输出作为到达原处的文字$(...)

在我们的示例中,这类似于简单地编写:

sorted=(3 5
a c
b
f
)

sorted 然后变成通过在每个新行上拆分此文字而创建的数组。

最后, unset IFS

这会将的值重置为IFS默认值,这只是一个好习惯。

这是为了确保我们不会IFS对脚本中以后依赖的任何内容造成麻烦。(否则,我们需要记住,我们已经改变了一切,这对于复杂的脚本来说可能是不切实际的。)


2
@xx或不带的IFS,如果元素中包含空格,则会将您的元素拆分为小块。尝试IFS=$'\n' 省略吧!
antak

3
非常好。您能否为普通的bash用户解释该解决方案的工作原理?
u32004 2015年

2
现在,使用IFS,如果元素中只有一种特定类型的空白,它将把您的元素分成小块。好; 不完美:-)
有限赎罪

7
unset IFS必要吗?我认为IFS=在执行命令之前,仅将更改范围限定在该命令上,然后自动返回到其先前的值。
Mark

10
@MarkH这是必需的,因为sorted=()它不是命令,而是第二个变量分配。
antak

35

原始回复:

array=(a c b "f f" 3 5)
readarray -t sorted < <(for a in "${array[@]}"; do echo "$a"; done | sort)

输出:

$ for a in "${sorted[@]}"; do echo "$a"; done
3
5
a
b
c
f f

请注意,此版本处理包含特殊字符或空格(换行符除外)的值

注意 bash 4+支持readarray。


编辑根据建议通过@Dimitre我有它更新为:

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

这样做的好处是甚至可以理解带有正确嵌入换行符的排序元素。不幸的是,正如@ruakh正确表示的,这并不意味着的结果readarray将是正确的,因为readarray没有选择使用NUL常规换行符代替换行符


5
不错,还应该注意,从bash版本4开始,readarray就可用了。它可以缩短一点:readarray -t sorted < <(printf '%s\n' "${array[@]}" | sort)
Dimitre Radoulov 2011年

1
@Dimitre:我接受了您的建议,并修复了空白处理可用于任何东西的问题(内部使用nullchar-delimiters)。干杯
sehe 2011年

1
是的,这sort -z是有用的改进,我想该-z选项是GNU排序范围。
Dimitre Radoulov

2
如果要处理嵌入式换行符,则可以滚动自己的readarray。例如:sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)。这在使用bash v3而不是bash v4的情况下也适用,因为readarray在bash v3中不可用。
Bob Bell

1
@ user1527227是输入重定向(<)与进程替换 结合在一起<(...)。或直觉地说:因为(printf "bla")不是文件。
sehe

33

这是一个纯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
      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
}

在这两种情况下,您都可以更改使用的顺序:我使用了字符串比较,但是可以使用算术比较,比较wrt文件的修改时间,等等。您甚至可以使其更通用,并使其使用测试功能的第一个参数,例如,

#!/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

使当前文件夹中的文件按修改时间(最新的)排序。

注意。这些功能是纯Bash!没有外部实用程序,也没有子外壳!如果您有任何有趣的符号(空格,换行符,全局字符等),则它们是安全的。


1
令人赞叹的Bash荣誉,在输入元素和排序标准方面提供了极大的灵活性。如果使用提供的排序选项进行基于行的排序sort就足够了,那么sort+ read -a解决方案将从大约20个项目开始更快,并且随着您要处理的元素越来越多,而且越来越快。例如,在我2012年末运行OSX 10.11.1,带有Fusion Drive的iMac上:100个元素的阵列:ca. 0.03秒 (qsort())vs. 0.005秒 (sort+ read -a); 1000个元素的数组:0.375秒 (qsort())vs. 0.014秒(sort+ read -a)。
mklement0

真好 我记得大学时代的快速排序,但也会研究泡沫排序。对于我的排序需求,我有第一个和第二个元素构成键,然后是一个数据元素(稍后可以扩展)。您可以通过关键元素数量(parm1)和数据元素数量(parm2)来改进代码。对于OP,参数将为1和0。对我而言,参数将为2和1。无论如何,您的答案很有希望。
WinEunuuchs2Unix

1
对于未发现的字符串整数的数据集,我发现这if [ "$i" -lt "$pivot" ]; then是必需的,否则解析的“ 2” <“ 10”返回true。我相信这是POSIX与Lexicographical;或内联链接
Page2PagePro

27

如果您不需要处理数组元素中的特殊外壳字符:

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 -sASCIIbetchy排序。


非常好的背景信息。我几乎要问一个关于ksh将如何使用set -s标志的演示...但是,问题又在bash上了,所以这很
不合时宜

这应该与大多数工作KornShell实现(例如ksh88pdksh程序):set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" 而且,当然,该组命令将如有重置当前位置参数。
Dimitre Radoulov 2011年

您真是壳知识的源泉。我确定您必须具有摄影记忆之类的东西,因为这种微妙的差异
无法涵盖

10

tl; dr

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

Bash v4 +:

readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

Bash v3:

IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)

antak解决方案相比的优势:

  • 您不必担心意外的globbing(将数组元素误解释为文件名模式),因此不需要额外的命令来禁用globbing(set -f,并set +f在以后进行恢复)。

  • 你不必担心重新IFS使用unset IFS[2]


可选阅读:说明和示例代码

上面的代码结合了Bash代码和外部实用程序,sort以提供一种解决方案,该解决方案可以处理任意单行元素以及词法或数字排序(可选地按字段)

  • 性能:对于大约20个或更多元素,这将比纯Bash解决方案 -显着并且越来越多,因此一旦超过100个元素。
    (确切的阈值将取决于您的特定输入,机器和平台。)

    • 它之所以快是因为它避免了Bash循环
  • printf '%s\n' "${a_in[@]}" | sort 执行排序(默认为词法化-参见sortPOSIX spec):

    • "${a_in[@]}"安全地扩展为数组的元素a_in作为单独的参数,无论它们包含什么(包括空格)。

    • printf '%s\n' 然后按原样打印每个参数(即每个数组元素)。

  • 请注意,使用进程替代(<(...)将排序后的输出作为read/的输入readarray(通过重定向到stdin,<)提供,因为read/ readarray必须在当前 shell中运行(一定不能在subshel​​l中运行),以便输出变量a_out可见到当前外壳程序(使变量在脚本的其余部分中保持定义)。

  • sort输出读取到数组变量中

    • Bash v4 +:readarray -t a_out将by所输出的各行读sort入array variable的元素中a_out,而不\n在每个元素(-t)中包括尾部。

    • Bash v3:readarray不存在,因此read必须使用:
      IFS=$'\n' read -d '' -r -a a_out告诉read读入array(-a)变量a_out,跨行()读取整个输入-d '',但是用换行符(IFS=$'\n'。将其分成数组元素$'\n',这会产生文字换行符(LF ),就是所谓的ANSI C引用的字符串)。
      -r该选项实际上应始终与一起使用read,以禁止意外处理\字符。)

带注释的示例代码:

#!/usr/bin/env bash

# Define input array `a_in`:
# Note the element with embedded whitespace ('a c')and the element that looks like
# a glob ('*'), chosen to demonstrate that elements with line-internal whitespace
# and glob-like contents are correctly preserved.
a_in=( 'a c' b f 5 '*' 10 )

# Sort and store output in array `a_out`
# Saving back into `a_in` is also an option.
IFS=$'\n' read -d '' -r -a a_out < <(printf '%s\n' "${a_in[@]}" | sort)
# Bash 4.x: use the simpler `readarray -t`:
# readarray -t a_out < <(printf '%s\n' "${a_in[@]}" | sort)

# Print sorted output array, line by line:
printf '%s\n' "${a_out[@]}"

由于使用了sort不带选项的选项,因此会产生词法排序(数字在字母前排序,而数字序列在词法上视为数字,而不是数字):

*
10
5
a c
b
f

如果要按第一个字段进行数字排序,则可以使用sort -k1,1n而不是just sort,这样会产生结果(非数字先于数字排序,然后数字正确排序):

*
a c
b
f
5
10

[1]将手柄元件具有嵌入换行符,使用下列变型(击V4 +,与GNU sort):
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z)
MichałGórny的有用答案提供了Bash v3解决方案。

[2]当IFS 在Bash V3变体设置,所述变化是作用域到命令
相反,IFS=$'\n' antak的回答是分配而不是命令,在这种情况下,IFS更改是全局的


8

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

declare BSORT=()
function bubble_sort()
{   #
    # @param [ARGUMENTS]...
    #
    # Sort all positional arguments and store them in global array BSORT.
    # Without arguments sort this array. Return the number of iterations made.
    #
    # Bubble sorting lets the heaviest element sink to the bottom.
    #
    (($# > 0)) && BSORT=("$@")
    local j=0 ubound=$((${#BSORT[*]} - 1))
    while ((ubound > 0))
    do
        local i=0
        while ((i < ubound))
        do
            if [ "${BSORT[$i]}" \> "${BSORT[$((i + 1))]}" ]
            then
                local t="${BSORT[$i]}"
                BSORT[$i]="${BSORT[$((i + 1))]}"
                BSORT[$((i + 1))]="$t"
            fi
            ((++i))
        done
        ((++j))
        ((--ubound))
    done
    echo $j
}

bubble_sort a c b 'z y' 3 5
echo ${BSORT[@]}

打印:

3 5 a b c z y

从创建相同的输出

BSORT=(a c b 'z y' 3 5) 
bubble_sort
echo ${BSORT[@]}

请注意,Bash可能在内部使用智能指针,因此交换操作可能很便宜(尽管我对此表示怀疑)。但是,这bubble_sort表明merge_sortShell语言还可以实现更高级的功能。


5
气泡排序?哇。奥巴马说“冒泡排序是错误的方法”-> youtube.com/watch?v=k4RRi_ntQc8
Robottinosino

1
好吧,尽管O-guy想要变得聪明,但他没有意识到这不是一个50/50的机会问题。O-guy的前任,让我们告诉他B-guy曾经做的更好(雷诺斯堡,俄亥俄州,2000年10月):“我想,如果您知道自己的信念,就会很容易回答问题我无法回答你的问题。” 因此,这个B家伙确实了解布尔逻辑。O-Guy不会。
Andreas Spindler

通过使BSORT成为具有要引用的任何数组的名称引用的本地数组,可以使该函数更易于移植。即local -n BSORT="$1"在功能开始时。然后,您可以运行bubble_sort myarraymyarray进行排序。
johnraff

7

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

local e new_array=()
while IFS= read -r -d '' e; do
    new_array+=( "${e}" )
done < <(printf "%s\0" "${array[@]}" | LC_ALL=C sort -z)

首先查看最后的输入重定向。我们正在使用printf内置函数来写出零终止的数组元素。引用确保数组元素按原样传递,并且shell的特定性printf导致它为每个其余参数重用格式字符串的最后一部分。也就是说,它等效于:

for e in "${array[@]}"; do
    printf "%s\0" "${e}"
done

然后将以null终止的元素列表传递给sort。该-z选项使它读取以null终止的元素,对其进行排序并输出以null终止的元素。如果您只需要获取唯一元素,则可以通过,-u因为它比更具可移植性uniq -z。该LC_ALL=C独立保证稳定的排序顺序的语言环境-有时脚本很有用。如果要sort尊重语言环境,请删除该语言环境。

<()构造获取从生成的管道读取的描述符,并将循环<的标准输入重定向到该描述符while。如果您需要访问管道内部的标准输入,则可以使用另一个描述符-读者的练习:)。

现在,回到开始。该read内置读取从标准输入重定向输出。设置为空IFS将禁用单词拆分,这在这里是不必要的-结果,read将读取整个“行”输入到单个提供的变量。-r选项也会禁用此处不希望的转义处理。最后,-d ''将行定界符设置为NUL,即告诉read读取以零结尾的字符串。

结果,循环对于每个连续的零终止数组元素执行一次,其值存储在中e。该示例只是将项目放置在另一个数组中,但是您可能更喜欢直接处理它们:)。

当然,这只是实现同一目标的多种方式之一。如我所见,它比在bash中实现完整的排序算法更简单,并且在某些情况下会更快。它处理包括换行符在内的所有特殊字符,并且应可在大多数常见系统上使用。最重要的是,它可能会教给您有关bash的新知识和很棒的知识:)。


很好的解决方案和非常有用的解释,谢谢。一种扩展:在不将IFS设置为空的情况下,也将消除前导空格-即使不进行分词。
Dirk Herrmann

代替引入局部变量e并设置空的IFS,而使用REPLY变量。
罗宾·米德

2

试试这个:

echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort

输出将是:

3
5
一个
b
C
F

问题解决了。


3
应该对此进行编辑以将输出放入新数组中,以完全回答他的问题。
彼得·奥兰

2

如果您可以为数组中的每个元素计算唯一的整数,如下所示:

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 -> $?"

然后,您可以将这些整数用作数组索引,因为Bash始终使用稀疏数组,因此无需担心未使用的索引:

array=(a c b f 3 5)
for el in "${array[@]}"; do
    sexy_int "$el"
    sorted[$?]="$el"
done

echo "${sorted[@]}"
  • 优点 快速。
  • 缺点 重复的元素将合并,因此不可能将内容映射到32位唯一整数。

有趣的技术,我使用了一个变体来查找最大值/最小值,而没有明确的比较/排序。但是,不考虑长度而未加权的加法将不起作用:“ z”在“ aaaa”之前排序,因此您不能在上面显示的单词中使用它。
斯普里亚蒂先生

2

最小排序:

#!/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

1
array=(a c b f 3 5)
new_array=($(echo "${array[@]}" | sed 's/ /\n/g' | sort))    
echo ${new_array[@]}

echo new_array的内容将是:

3 5 a b c f

1

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

使用字符不是原始数组中(如$'\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>

这将抱怨源数组包含解决方法字符:

$ array=( a b 'c d' $'e\nf' $'g\4h')
$ sortarray "${array[@]}"
./script: error: array contains the workaround char

描述

  • 我们设置了两个局部变量wa(替代方法char)和一个空IFS
  • 然后(如果ifs为null),我们测试整个数组$*
  • 不包含任何可疑的char [[ $* =~ [$wa] ]]
  • 如果是这样,请发出一条消息并提示错误: exit 1
  • 避免扩展文件名: set -f
  • 设置IFS(IFS=$'\n')的新值,循环变量x和换行var(nl=$'\n')。
  • 我们打印接收到的参数的所有值(输入数组$@)。
  • 但是我们用替代方法char替换了任何新行"${@//$nl/$wa}"
  • 发送这些值进行排序sort -n
  • 并将所有排序的值放回位置参数中set --
  • 然后,我们一个个分配每个参数(以保留换行符)。
  • 循环 for x
  • 到一个新数组: sorted+=(…)
  • 内引号以保留任何现有的换行符。
  • 将解决方法恢复到换行符"${x//$wa/$nl}"
  • 做完了

1

这个问题看起来密切相关。顺便说一句,这是Bash中的一个mergesort(没有外部过程):

mergesort() {
  local -n -r input_reference="$1"
  local -n output_reference="$2"
  local -r -i size="${#input_reference[@]}"
  local merge previous
  local -a -i runs indices
  local -i index previous_idx merged_idx \
           run_a_idx run_a_stop \
           run_b_idx run_b_stop

  output_reference=("${input_reference[@]}")
  if ((size == 0)); then return; fi

  previous="${output_reference[0]}"
  runs=(0)
  for ((index = 0;;)) do
    for ((++index;; ++index)); do
      if ((index >= size)); then break 2; fi
      if [[ "${output_reference[index]}" < "$previous" ]]; then break; fi
      previous="${output_reference[index]}"
    done
    previous="${output_reference[index]}"
    runs+=(index)
  done
  runs+=(size)

  while (("${#runs[@]}" > 2)); do
    indices=("${!runs[@]}")
    merge=("${output_reference[@]}")
    for ((index = 0; index < "${#indices[@]}" - 2; index += 2)); do
      merged_idx=runs[indices[index]]
      run_a_idx=merged_idx
      previous_idx=indices[$((index + 1))]
      run_a_stop=runs[previous_idx]
      run_b_idx=runs[previous_idx]
      run_b_stop=runs[indices[$((index + 2))]]
      unset runs[previous_idx]
      while ((run_a_idx < run_a_stop && run_b_idx < run_b_stop)); do
        if [[ "${merge[run_a_idx]}" < "${merge[run_b_idx]}" ]]; then
          output_reference[merged_idx++]="${merge[run_a_idx++]}"
        else
          output_reference[merged_idx++]="${merge[run_b_idx++]}"
        fi
      done
      while ((run_a_idx < run_a_stop)); do
        output_reference[merged_idx++]="${merge[run_a_idx++]}"
      done
      while ((run_b_idx < run_b_stop)); do
        output_reference[merged_idx++]="${merge[run_b_idx++]}"
      done
    done
  done
}

declare -ar input=({z..a}{z..a})
declare -a output

mergesort input output

echo "${input[@]}"
echo "${output[@]}"

0

我不认为您需要在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[@]})"

这将打印:

 input: a c b f 3 5
output: 3 5 a b c f

气泡排序是O(n^2)。我似乎记得大多数排序算法都使用O(n lg(n))直到最后一打元素左右。对于最后的元素,使用选择排序。
jww '16


-1

sorted=($(echo ${array[@]} | tr " " "\n" | sort))

本着bash / linux的精神,我将为每个步骤提供最佳的命令行工具。sort可以完成主要工作,但需要用换行符代替空格而不是空格,因此上面非常简单的管道可以做到:

回声数组内容->用换行符替换空间->排序

$() 是回应结果

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

注意:@sorontar在对另一个问题的评论中提到:

sorted =($(...))部分正在使用“ split and glob”运算符。您应该关闭glob:set -f或set -o noglob或shopt -op noglob或类似*的数组元素将扩展为文件列表。


本着bash / linux的精神:我想您根本不了解这种精神。您的代码已完全损坏(路径名扩展和单词拆分)。最好(Bash≥4):mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort)否则sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)
gniourf_gniourf

您使用的反模式是::echo ${array[@]} | tr " " "\n"如果数组的字段包含空格和glob字符,则此模式将中断。此外,它产生一个子shell并使用一个无用的外部命令。而且由于echo是愚蠢的,这将打破,如果你的阵列开始-e-E-n。而是使用:printf '%s\n' "${array[@]}"。另一个反模式是:($())将“回显结果”放入数组中。当然不是!这是一个可怕的反模式,由于路径名扩展(globbing)和单词拆分而中断。切勿使用这种恐怖手段。
gniourf_gniourf

最高答案是“可怕的反模式”。以及拒绝别人回答您​​自己回答的问题的方法。
迈克尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.