如何在 Bash 中对数组排序

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

array=(a c b f 3 5)

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

213455 次浏览

原始回复:

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

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

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

有了 ABc0,你无论如何都需要一个外排序程序。

使用 不需要外部程序,特殊的 shell 字符很容易处理:

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

Ksh 使用 set -sASCIIbetially进行排序。

我不认为你在巴斯需要一个外排序项目。

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

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

在从慕尼黑到法兰克福的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 语言。

试试这个:

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

产出将包括:

3
5
a
b
c
f

问题解决了。

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

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 具有 指出,如果元素包含通配符(如 *?) ,则需要注意:

已排序的 = ($(...))部分使用“ split and globb”操作符。您应该关闭 globb: 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的工作方式(每行排序)。通过 只有拆分一个新行并不重要,但是需要保留包含空格或制表符的元素。

IFS的默认值是 一个空间账单,其次是 一条新线路,不适合我们的操作。

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

<<<,称为 < em > here string ,如上所述,接受 "${array[*]}"的扩展,并将其提供给 sort的标准输入。

在我们的示例中,向 sort提供以下字符串:

a c
b
f
3 5

由于 sort 差不多,它产生:

3 5
a c
b
f

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

$(...)部分称为 指令替代,使其内容(sort <<<"${array[*]})作为普通命令运行,同时将生成的 标准输出作为字面值,放在 $(...)所在的位置。

在我们的例子中,这会产生一些类似于简单写作的东西:

sorted=(3 5
a c
b
f
)

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

最后是 unset IFS

这将 IFS的值重置为默认值,这是一个很好的实践。

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

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

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位唯一整数。

另一种解决方案使用外部 sort并处理 任何特殊字符(NULs: 除外)。应该与 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内置函数从重定向的 stdin 中读取输出。设置空的 IFS禁用分词,这在这里是不必要的ーー因此,read读取单个提供的变量的整个输入‘行’。-r选项禁用这里不需要的转义处理。最后,-d ''将行分隔符设置为 NUL ー即告诉 read读取以零结尾的字符串。

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

当然,这只是实现同一目标的众多方法之一。在我看来,它比在 bash 中实现完整的排序算法更简单,而且在某些情况下会更快。它可以处理包括换行符在内的所有特殊字符,应该可以在大多数公共系统上使用。最重要的是,它可以教会您一些关于 bash 的新的和令人敬畏的东西:)。

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

New _ array 的 echo 内容将是:

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[@]}" )
}

用作,例如:

$ 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 的!没有外部工具,没有子贝壳!他们是安全的写任何有趣的符号,你可能有(空格,换行符,全球字符,等)。

注2。测试 [[ $i < $pivot ]] 是正确的。它使用字典字符串比较。如果数组只包含整数,并且希望进行数字排序,则改为使用 ((i < pivot))请不要编辑这个答案来改变它。它已经被编辑(和回滚)了几次。我在这里给出的测试是正确的,并且与示例中给出的输出相对应: 示例同时使用了字符串和数字,目的是按照字典序对它进行排序。在本例中使用 ((i < pivot))

a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f

Tl; dr :

对数组 a_in进行排序,并将结果存储在 a_out中(元素不能有 嵌入了换行 < sup > [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 -fset +f以便稍后恢复它)。

  • 您不必担心用 unset IFS.[2]重置 IFS


可选阅读: 解释和示例代码

上面将 Bash 代码和外部实用程序 sort结合在一起,用于解决 使用任意 < em > single -line 元素词法或数字排序(可选按字段):

  • 性能 : 对于 大约20个或更多的元素来说,这将是 比纯 Bash 解决方案更快——一旦超过大约100个元素,就会变得越来越明显。
    (确切的阈值将取决于您的特定输入、机器和平台。)

    • 它之所以快是因为它是 避免 Bash 循环
  • printf '%s\n' "${a_in[@]}" | sort 执行排序(默认情况下,请参阅 sort的 POSIX 规格) :

    • "${a_in[@]}"安全地将数组 a_in的元素扩展为 个人论据,不管它们包含什么(包括空格)。

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

  • 注意,使用进程替换(<(...)) 将排序后的输出作为输入提供给 read/readarray(通过重定向到 stdin,<) ,因为 ABC1/readarray必须在 < em > current shell 中运行(不能在 子弹中运行)为了使输出变量 a_out对当前 shell 可见(变量保持在脚本的其余部分中定义)。

  • sort的输出读入 数组变量:

    • Bash v4 + : readarray -t a_outsort输出的各行读入数组变量 a_out的元素中,而不在每个元素(-t)中包含后面的 \n

    • Bash v3: readarray不存在,因此必须使用 read:
      IFS=$'\n' read -d '' -r -a a_out告诉 read读入数组(-a)变量 a_out,读入整个输入,跨行(-d '') ,但是通过换行(IFS=$'\n')将其拆分为数组元素。产生文字换行符(LF)的 $'\n'是所谓的 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而不仅仅是 sort,它会产生(非数字排序优先于数字排序,数字排序正确) :

*
a c
b
f
5
10

[1]要处理带有嵌入换行符的元素,使用以下变体(Bash v4 + ,带有 GNU sort) :
readarray -d '' -t a_out < <(printf '%s\0' "${a_in[@]}" | sort -z).
Micha Górny 的回答很有帮助,他有一个 Bash v3解决方案

[2]当 IFS 在 Bash v3变体中设置时,变化是 听命行事
相比之下,在 antak 的回答中,IFS=$'\n' 之后是 任务而不是命令,在这种情况下,IFS的变化是 全球性的

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

本着 bash/linux 的精神,我将为每个步骤提供最好的命令行工具。sort完成主要工作,但是需要用换行符而不是空格分隔输入,因此上面这个非常简单的流水线只需要:

回显数组内容—— > 用新行替换空间—— > 排序

$()是回显结果

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

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

已排序的 = ($(...))部分使用“ split and globb”操作符。您应该关闭 globb: set-f 或 set-o noglobb 或 shopt-op noglobb 或类似 * 的数组元素将扩展为一个文件列表。

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

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

分类:

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

这个问题 看起来密切相关。顺便说一下,这里有一个 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..a}{z..a})
declare -a output


mergesort input output


echo "${input[@]}"
echo "${output[@]}"
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[@]}"将把 nth 数组参数复制到 nth 位置参数。注意,引号保留了可能包含在数组元素中的空格)。
  • 打印每个位置参数(例如,printf '%s\n' "$@"将在自己的行上打印每个位置参数)。再次注意,引号保留了可能包含在每个位置参数中的空格)。
  • 然后 sort做它的事情。
  • 将流读入带有映射文件的数组(例如,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 专家撕成碎片! :)

非常感谢在我之前回答问题的人。使用他们出色的输入,bash 文档和来自其他思路的想法,这是什么工作非常适合我,没有 IFS 的变化

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

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

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

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

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

最后我们用

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

输出是

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

请告诉我这是否是嵌入式/n 的正确测试,因为两种解决方案产生相同的结果,但是第一种解决方案不能正确地使用嵌入式/n

保持简单;)

在下面的示例中,数组 b是数组 a的排序版本!

第二行 echo将数组 a中的每个项目发送给 pipe,然后 pipe将它们发送给 sort命令,输出用于启动数组 b

a=(2 3 1)
b=( $( for x in ${a[@]}; do echo $x; done | sort ) )
echo ${b[@]} # output: 1 2 3

答得好。学到了很多。看完之后,我决定参加比赛。我认为这是最短的方法(而且可能更快,因为它没有多少 shell 脚本解析,尽管有产生 printfsort的问题,但它们只被调用一次) ,并处理数据中的空白:

a=(3 "2 a" 1)                                                # Setup!
IFS=$'\n' b=( $(printf "%s\n" "${a[@]}" | sort) ); unset IFS # Sort!
printf "'%s' " "${b[@]}";                                    # Success!

产出:

'1' '2 a' '3'

注意,如果知道数组中没有空格,那么就不需要修改 IFS

灵感来自@yas 的 回答和@Alcamtar 的评论。

剪辑

哦,我不知怎么的错过了 真的接受了的答案,这个答案甚至比我的还短!

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

原来 unset是必需的,因为它是一个没有命令的变量赋值。

我建议你看看这个答案,因为它有一些关于 globbing 的有趣的东西,如果数组中有通配符的话,这些东西可能是相关的。它还对正在发生的事情有详细的描述。

编辑2

GNU 有一个扩展,其中排序使用 \0来分隔记录,如果数据中有 LF,这是很好的。然而,当它被返回到 shell 并被分配给一个数组时,我看不出有什么好的方法来转换它,以便 shell 在 \0上定界,因为即使设置 IFS=$'\0',shell 也不喜欢它,也不会正确地将它分解。