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

Posted

技术标签:

【中文标题】如何在 Bash 中对数组进行排序【英文标题】:How to sort an array in Bash 【发布时间】:2011-11-18 12:52:15 【问题描述】:

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

array=(a c b f 3 5)

我需要对数组进行排序。不仅以排序的方式显示内容,还可以获得一个包含排序元素的新数组。新排序的数组可以是全新的也可以是旧的。

【问题讨论】:

【参考方案1】:

你真的不需要那么多代码:

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 有 pointed out,如果元素包含诸如 *? 之类的通配符,则需要注意:

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

发生了什么:

结果是按此顺序发生的六件事的高潮:

    IFS=$'\n' "$array[*]" &lt;&lt;&lt; sort sorted=($(...)) unset IFS

首先,IFS=$'\n'

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

给定:

"$array[*]" 扩展到由IFS 的第一个字符分隔的每个元素 sorted=() 通过拆分 IFS 的每个字符来创建元素

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

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

IFS 的默认值是 一个空格一个制表符,后跟 一个新行,不适合我们的操作。

接下来,sort &lt;&lt;&lt;"$array[*]" 部分

&lt;&lt;&lt;,称为here strings,采用上述"$array[*]" 的扩展,并将其输入sort 的标准输入。

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

a c
b
f
3 5

由于sort排序,它产生:

3 5
a c
b
f

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

$(...) 部分,称为command substitution,导致其内容 (sort &lt;&lt;&lt;"$array[*]) 作为正常命令运行,同时将生成的 标准输出 作为文字,随处可见 @ 987654358@是。

在我们的示例中,这会产生类似于简单编写的内容:

sorted=(3 5
a c
b
f
)

sorted 然后变成一个数组,通过在每一新行上拆分这个文字来创建。

最后,unset IFS

这会将IFS 的值重置为默认值,这是一种很好的做法。

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

【讨论】:

@xx 或没有IFS,如果元素中有空格,它会将元素分成小块。尝试省略IFS=$'\n' 例如,看看! 非常好。您能为普通的 bash 用户解释一下这个解决方案是如何工作的吗? 现在,使用IFS,如果元素中只有一种特定类型的空白,它会将您的元素分成小块。好的;不完美:-) unset IFS 有必要吗?我认为将IFS= 添加到命令的范围内仅对该命令的更改,之后自动返回到其先前的值。 @MarkH 这是必要的,因为sorted=() 不是命令,而是第二个变量赋值。【参考方案2】:

原始回复:

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 而不是常规的换行符 作为行分隔符。

【讨论】:

很好,还应该注意 readarray 从 bash 版本 4 开始可用。可以缩短一点:readarray -t sorted &lt; &lt;(printf '%s\n' "$array[@]" | sort) @Dimitre:我采纳了您的建议并修复了空白处理以适用于任何内容(在内部使用 nullchar 分隔符)。干杯 是的,sort -z 是一个有用的改进,我想-z 选项是 GNU 排序扩展。 如果你想处理嵌入的换行符,你可以滚动你自己的 readarray。例如:sorted=(); while read -d $'\0' elem; do sorted[$#sorted[@]]=$elem; done &lt; &lt;(printf '%s\0' "$array[@]" | sort -z)。这也适用于您使用 bash v3 而不是 bash v4,因为 readarray 在 bash v3 中不可用。 @user1527227 它是输入重定向 (&lt;) 结合 进程替换 &lt;(...)。或者直观地说:因为(printf "bla") 不是文件。【参考方案3】:

这是一个纯 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[@]" )

用作,例如,

$ 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!没有外部实用程序,也没有子shell!对于您可能拥有的任何有趣符号(空格、换行符、全局字符等),它们都是安全的。

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

【讨论】:

感谢令人印象深刻的 Bashing,它在输入元素和排序标准方面提供了极大的灵活性。如果使用sort 提供的排序选项进行基于行的排序就足够了,那么sort + read -a 解决方案将从大约20 个项目开始更快,并且随着您处理的元素越多,速度会越来越快和。例如,在我 2012 年末运行 OSX 10.11.1 和 Fusion Drive 的 iMac 上:100 元素数组:ca。 0.03s 秒。 (qsort()) 与 ca。 0.005 秒。 (sort + read -a); 1000 元素数组:大约0.375 秒。 (qsort()) 与 ca。 0.014 秒 (sort + read -a)。 不错。我记得大学时代的快速排序,但也会研究冒泡排序。对于我的排序需要,我有第一个和第二个元素形成键,然后是一个数据元素(我稍后可能会扩展)。您的代码可以通过关键元素的数量 (parm1) 和数据元素的数量 (parm2) 来改进。对于 OP,参数是 1 和 0。对我来说,参数是 2 和 1。无论如何,你的答案最有希望。 使用我发现的未转换字符串整数数据集if [ "$i" -lt "$pivot" ]; then 是必需的,否则解析的“2”Inline Link.【参考方案4】:

如果不需要处理数组元素中的特殊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

kshset -sASCII 顺序排序

【讨论】:

非常好的背景信息。我几乎会要求一个关于 ksh 如何使用 set -s 标志的演示......但话说回来,问题是关于 bash,所以这将是相当离题的 这应该适用于大多数 KornShell 实现(例如 ksh88pdksh):set -A array x 'a a' d; set -s -- "$array[@]"; set -A sorted "$@" 并且,当然,set 命令会重置当前的位置参数,如果有的话。 你是贝壳知识的源泉。我敢肯定你一定有过照片的记忆或其他东西,因为这种细微的差异是人类物种的大多数其他成员所无法做到的 :),+1 以获得完整的信息包【参考方案5】:

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's solution:

您不必担心意外的通配(将数组元素意外解释为文件名模式),因此不需要额外的命令来禁用通配(set -fset +f 稍后恢复它)。

您不必担心将IFS 重置为unset IFS[2]


选读:解释和示例代码

以上将 Bash 代码与外部实用程序 sort 结合在一起,以提供一种适用于任意行元素的解决方案>词汇或数字排序(可选按字段)

性能:对于大约 20 个或更多元素,这将比纯 Bash 解决方案更快 -一旦超过 100 个元素,就会显着增加。(确切的阈值取决于您的具体输入、机器和平台。)

它快速的原因是它避免了 Bash 循环

printf '%s\n' "$a_in[@]" | sort 执行排序(在词汇上,默认情况下 - 请参阅sort's POSIX spec):

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

printf '%s\n' 然后按原样在其自己的行上打印每个参数 - 即每个数组元素。

1234563 >read / readarray 必须在 current shell 中运行(不得在 subshel​​l 中运行),才能使输出变量 a_out 成为对当前 shell 可见(以便变量在脚本的其余部分中保持定义)。

sort的输出读入一个数组变量

Bash v4+:readarray -t a_outsort 输出的各个行读取到数组变量 a_out 的元素中,而不包括每个元素中的尾随 \n (-t)。

Bash v3:readarray 不存在,因此必须使用 readIFS=$'\n' read -d '' -r -a a_out 告诉 read 读入数组 (-a) 变量 a_out,读取整个输入,跨行 (-d ''),但通过换行符将其拆分为数组元素(IFS=$'\n'$'\n',产生文字换行符 (LF),即所谓的ANSI C-quoted string)。 (-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

如果您希望 numerical 按第一个字段排序,您可以使用 sort -k1,1n 而不是仅使用 sort,这会产生(非数字在数字之前排序,并且数字正确排序):

*
a c
b
f
5
10

[1] 要处理带有嵌入换行符的元素,请使用以下变体(Bash v4+,带有 GNU sort):readarray -d '' -t a_out &lt; &lt;(printf '%s\0' "$a_in[@]" | sort -z).@987654325 @ 有一个 Bash v3 解决方案。

[2] 虽然在 Bash v3 变体中设置了 IFS,但更改仅限于命令。 相比之下,在 antak 的回答中,IFS=$'\n'  后面的内容是 assignment 而不是命令,在这种情况下,IFS 的更改是全局

【讨论】:

【参考方案6】:

在从慕尼黑到法兰克福的 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_sort 这样的更高级的功能也在 shell 语言的范围内。

【讨论】:

冒泡排序?哇.. 奥巴马说“冒泡排序是错误的方法”-> youtube.com/watch?v=k4RRi_ntQc8 好吧,看起来 O-guy 想要聪明一点,但他并没有感觉到这不是一个 50/50 的机会问题。作为 O-guy 的前任,让我们告诉他 B-guy,曾经做得更好(俄亥俄州雷诺兹堡,2000 年 10 月):“我认为,如果您知道自己的信仰,那么回答问题会容易得多. 我无法回答你的问题。”所以这个B-guy真的对布尔逻辑有所了解。 O-guy 没有。 通过使 BSORT 成为一个带有 nameref 的本地数组到要排序的任何数组,可以使函数更容易移植。即local -n BSORT="$1" 在函数的开头。然后你可以运行bubble_sort myarraymyarray进行排序。【参考方案7】:

另一种使用外部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

然后将以空结尾的元素列表传递给sort-z 选项使其读取以空字符结尾的元素,对它们进行排序并输出以空字符结尾的元素。如果您只需要获取唯一元素,则可以传递-u,因为它比uniq -z 更便携。 LC_ALL=C 确保独立于语言环境的稳定排序顺序——有时对脚本很有用。如果您希望 sort 尊重语言环境,请将其删除。

&lt;() 构造获取要从生成的管道读取的描述符,&lt;while 循环的标准输入重定向到它。如果你需要访问管道内的标准输入,你可以使用另一个描述符——读者练习:)。

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

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

当然,这只是实现同一目标的众多方法之一。正如我所看到的,它比在 bash 中实现完整的排序算法更简单,并且在某些情况下它会更快。它处理所有特殊字符,包括换行符,并且应该适用于大多数常见系统。最重要的是,它可能会教你一些关于 bash 的新奇事物 :)。

【讨论】:

很好的解决方案和非常有用的解释,谢谢。一个扩展:如果不将 IFS 设置为空,则前导空格也将被消除 - 即使没有进行分词。 不要引入局部变量 e 并设置空 IFS,而是使用 REPLY 变量。【参考方案8】:

试试这个:

echo $array[@] | awk 'BEGINRS=" "; print $1' | sort

输出将是:

3 5 一种 b C F

问题解决了。

【讨论】:

应该编辑这个以将输出放入一个新数组中以完全回答他的问题。【参考方案9】:

如果你可以为数组中的每个元素计算一个唯一的整数,像这样:

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”之前,所以你不能将它用于上面显示的单词。【参考方案10】:

最小排序:

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

【讨论】:

【参考方案11】:
array=(a c b f 3 5)
new_array=($(echo "$array[@]" | sed 's/ /\n/g' | sort))    
echo $new_array[@]

new_array 的回显内容为:

3 5 a b c f

【讨论】:

【参考方案12】:

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

使用不在原始数组中的字符(如$'\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(workaround char)和一个空 IFS 然后(ifs 为 null)我们测试整个数组 $*。 不包含任何 woraround char [[ $* =~ [$wa] ]]。 如果是,请发出消息并发出错误信号:exit 1 避免文件名扩展:set -f 设置 IFS 的新值 (IFS=$'\n')、循环变量 x 和换行符 (nl=$'\n')。 我们打印接收到的参数的所有值(输入数组$@)。 但我们将任何新行替换为解决方法 char "$@//$nl/$wa"。 发送这些值以进行排序sort -n。 并将所有排序值放回位置参数set --。 然后我们一一分配每个参数(以保留换行符)。 在循环中for x 到一个新数组:sorted+=(…) 在引号内保留任何现有的换行符。 将解决方法恢复为换行符"$x//$wa/$nl"。 完成

【讨论】:

【参考方案13】:

This question 看起来密切相关。顺便说一句,这是 Bash 中的合并排序(没有外部进程):

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..az..a)
declare -a output

mergesort input output

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

【讨论】:

【参考方案14】:

我不相信您需要在 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)) 直到最后十几个元素。对于最终元素,使用选择排序。【参考方案15】:
a=(e b 'c d')
shuf -e "$a[@]" | sort >/tmp/f
mapfile -t g </tmp/f

【讨论】:

【参考方案16】:

非常感谢在我之前回答的人。使用他们出色的输入、bash 文档和其他方面的想法,这对我来说是完美的,无需 IFS 更改

array=("a \n c" b f "3 5")

在 bash 中使用进程替换和读取数组 > v4.4 WITH EOL 字符

readarray -t sorted < <(sort < <(printf '%s\n' "$array[@]"))

在 bash 中使用进程替换和读取数组 > v4.4 WITH NULL 字符

readarray -td '' sorted < <(sort -z < <(printf '%s\0' "$array[@]"))

最后我们验证

printf "[%s]\n" "$sorted[@]"

输出是

[3 5]
[a \n c]
[b]
[f]

请让我知道这是否是对嵌入式 /n 的正确测试,因为两种解决方案都会产生相同的结果,但第一个解决方案不应该与嵌入式 /n 一起正常工作

【讨论】:

【参考方案17】:
array=(z 'b c');  set "$array[@]"; printf '%s\n' "$@";  \
    | sort \
    | mapfile -t array; declare -p array
declare -a array=([0]="b c" [1]="z")
打开一个内联函数 ... 以获取一组新的位置参数(例如 $1$2 等)。 将数组复制到位置参数。 (例如,set "$array[@]" 会将第 n 个数组参数复制到第 n 个位置参数。注意引号保留了数组元素中可能包含的空格)。 打印每个位置参数(例如,printf '%s\n' "$@" 将在自己的行上打印每个位置参数。同样,请注意引号保留每个位置参数中可能包含的空白)。 然后sort 做它的事。 使用 mapfile 将流读入数组(例如,mapfile -t array 将每一行读入变量 array-t 忽略每行中的 \n)。 转储数组以显示其已排序。

作为一个函数:

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

我期待着被所有 UNIX 大师撕碎! :)

【讨论】:

你测试过代码吗?这是行不通的。 $ref 是运行mapfile 的子shell 的本地,它不会影响父shell。可能是,您在 zsh 或 fish 下测试了代码 - 问题是关于 bash。【参考方案18】:

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

本着 bash / linux 的精神,我会为每个步骤提供最好的命令行工具。 sort 完成主要工作,但需要用换行符而不是空格分隔输入,所以上面非常简单的管道就可以了:

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

$() 是回显结果

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

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

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

【讨论】:

本着 bash / linux 的精神:我猜你根本没有理解这种精神。您的代码已完全损坏(路径名扩展和分词)。这会更好(Bash≥4):mapfile -t sorted &lt; &lt;(printf '%s\n' "$array[@]" | sort),否则sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done &lt; &lt;(printf '%s\n' | sort) 您正在使用的反模式是:echo $array[@] | tr " " "\n":如果数组的字段包含空格和全局字符,这将中断。此外,它会生成一个子外壳并使用无用的外部命令。由于echo 很笨,如果您的数组以-e-E-n 开头,它将中断。改为使用:printf '%s\n' "$array[@]"。另一个反模式是:($()) 是将“回显结果”放入数组中。当然不是!这是一个可怕的反模式,由于路径名扩展(通配符)和分词而中断。永远不要使用这种恐怖。 最佳答案是“可怕的反模式”。以及对别人对您自己回答的问题的回答投反对票的方式。 关于陷阱的精彩讨论!

以上是关于如何在 Bash 中对数组进行排序的主要内容,如果未能解决你的问题,请参考以下文章

如何在bash中对语义版本进行排序?

如何在 Bash 中对数组进行切片

如何在Vue中对对象数组进行排序和过滤

如何在javascript中对多维数组进行排序

如何在 Java 中对对象数组进行排序?

如何在 ColdFusion 中对结构数组进行排序