【发布时间】:2011-11-18 12:52:15
【问题描述】:
我在 Bash 中有一个数组,例如:
array=(a c b f 3 5)
我需要对数组进行排序。不仅以排序的方式显示内容,还可以获得一个包含排序元素的新数组。新排序的数组可以是全新的也可以是旧的。
【问题讨论】:
我在 Bash 中有一个数组,例如:
array=(a c b f 3 5)
我需要对数组进行排序。不仅以排序的方式显示内容,还可以获得一个包含排序元素的新数组。新排序的数组可以是全新的也可以是旧的。
【问题讨论】:
你真的不需要那么多代码:
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 -f或set -o noglob或shopt -op noglob或像*这样的数组元素将扩展为文件列表。
结果是按此顺序发生的六件事的高潮:
IFS=$'\n'"${array[*]}"<<<sortsorted=($(...))unset IFSIFS=$'\n'
这是我们操作的一个重要部分,它通过以下方式影响 2 和 5 的结果:
给定:
"${array[*]}" 扩展到由IFS 的第一个字符分隔的每个元素
sorted=() 通过拆分 IFS 的每个字符来创建元素
IFS=$'\n' sets things up 以便使用 新行 作为分隔符扩展元素,然后以每行成为一个元素的方式创建。 (即在新行上拆分。)
用新行分隔很重要,因为这就是sort 的操作方式(按行排序)。 仅分割一个新行并不重要,但需要保留包含空格或制表符的元素。
IFS 的默认值是 一个空格,一个制表符,后跟 一个新行,不适合我们的操作。
sort <<<"${array[*]}" 部分<<<,称为here strings,采用上述"${array[*]}" 的扩展,并将其输入sort 的标准输入。
在我们的示例中,sort 被输入以下字符串:
a c
b
f
3 5
由于sort排序,它产生:
3 5
a c
b
f
sorted=($(...)) 部分$(...) 部分,称为command substitution,导致其内容 (sort <<<"${array[*]}) 作为正常命令运行,同时将生成的 标准输出 作为文字,随处可见 @ 987654358@是。
在我们的示例中,这会产生类似于简单编写的内容:
sorted=(3 5
a c
b
f
)
sorted 然后变成一个数组,通过在每一新行上拆分这个文字来创建。
unset IFS
这会将IFS 的值重置为默认值,这是一种很好的做法。
这是为了确保我们不会在脚本后面依赖于 IFS 的任何内容造成麻烦。 (否则我们需要记住我们已经改变了一些东西——这对于复杂的脚本可能是不切实际的。)
【讨论】:
IFS,如果元素中有空格,它会将元素分成小块。尝试省略IFS=$'\n' 的例如,看看!
IFS,如果元素中只有一种特定类型的空白,它会将您的元素分成小块。好的;不完美:-)
unset IFS 有必要吗?我认为将IFS= 添加到命令的范围内仅对该命令的更改,之后自动返回到其先前的值。
sorted=() 不是命令,而是第二个变量赋值。
原始回复:
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 -t sorted < <(printf '%s\n' "${array[@]}" | sort)
sort -z 是一个有用的改进,我想-z 选项是 GNU 排序扩展。
sorted=(); while read -d $'\0' elem; do sorted[${#sorted[@]}]=$elem; done < <(printf '%s\0' "${array[@]}" | sort -z)。这也适用于您使用 bash v3 而不是 bash v4,因为 readarray 在 bash v3 中不可用。
<) 结合 进程替换 <(...)。或者直观地说:因为(printf "bla") 不是文件。
这是一个纯 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 < $pivot ]] 是正确的。它使用字典字符串比较。如果您的数组只包含整数并且您想按数字排序,请改用((i < pivot))。
请不要编辑这个答案来改变它。它已经被编辑(并回滚)了几次。我这里给出的测试是正确的,并且与示例中给出的输出相对应:示例同时使用了字符串和数字,目的是按字典顺序对其进行排序。在这种情况下使用((i < pivot))是错误的。
【讨论】:
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)。
if [ "$i" -lt "$pivot" ]; then 是必需的,否则解析的“2”Inline Link.
如果不需要处理数组元素中的特殊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 以 ASCII 顺序排序。
【讨论】:
set -A array x 'a a' d; set -s -- "${array[@]}"; set -A sorted "$@" 并且,当然,set 命令会重置当前的位置参数,如果有的话。
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)
您不必担心意外的通配(将数组元素意外解释为文件名模式),因此不需要额外的命令来禁用通配(set -f 和set +f 稍后恢复它)。
您不必担心将IFS 重置为unset IFS。[2]
以上将 Bash 代码与外部实用程序 sort 结合在一起,以提供一种适用于任意单行元素和的解决方案>词汇或数字排序(可选按字段):
性能:对于大约 20 个或更多元素,这将比纯 Bash 解决方案更快 -一旦超过 100 个元素,就会显着增加。
(确切的阈值取决于您的具体输入、机器和平台。)
printf '%s\n' "${a_in[@]}" | sort 执行排序(在词汇上,默认情况下 - 请参阅sort's POSIX spec):
"${a_in[@]}" 安全地扩展为数组a_in 的元素作为单个参数,无论它们包含什么(包括空格)。
printf '%s\n' 然后按原样在其自己的行上打印每个参数 - 即每个数组元素。
read / readarray 必须在 current shell 中运行(不得在 subshell 中运行),才能使输出变量 a_out 成为对当前 shell 可见(以便变量在脚本的其余部分中保持定义)。
将sort的输出读入一个数组变量:
Bash v4+:readarray -t a_out 将 sort 输出的各个行读取到数组变量 a_out 的元素中,而不包括每个元素中的尾随 \n (-t)。
Bash v3:readarray 不存在,因此必须使用 read:IFS=$'\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 < <(printf '%s\0' "${a_in[@]}" | sort -z).
@987654325 @ 有一个 Bash v3 解决方案。
[2] 虽然在 Bash v3 变体中设置了 IFS,但更改仅限于命令。
相比之下,在 antak 的回答中,IFS=$'\n' 后面的内容是 assignment 而不是命令,在这种情况下,IFS 的更改是全局。
【讨论】:
在从慕尼黑到法兰克福的 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 语言的范围内。
【讨论】:
local -n BSORT="$1" 在函数的开头。然后你可以运行bubble_sort myarray 对myarray进行排序。
另一种使用外部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 尊重语言环境,请将其删除。
<() 构造获取要从生成的管道读取的描述符,< 将while 循环的标准输入重定向到它。如果你需要访问管道内的标准输入,你可以使用另一个描述符——读者练习:)。
现在,回到开头。 read 内置从重定向的标准输入读取输出。将IFS 设置为空会禁用分词,这在此处是不必要的——因此,read 会将输入的整个“行”读取到单个提供的变量。 -r 选项也会禁用此处不需要的转义处理。最后,-d '' 将行分隔符设置为 NUL——也就是说,告诉read 读取以零结尾的字符串。
因此,对于每个连续的以零结尾的数组元素执行一次循环,值存储在e 中。该示例只是将项目放在另一个数组中,但您可能更喜欢直接处理它们:)。
当然,这只是实现同一目标的众多方法之一。正如我所看到的,它比在 bash 中实现完整的排序算法更简单,并且在某些情况下它会更快。它处理所有特殊字符,包括换行符,并且应该适用于大多数常见系统。最重要的是,它可能会教你一些关于 bash 的新奇事物 :)。
【讨论】:
e 并设置空 IFS,而是使用 REPLY 变量。
试试这个:
echo ${array[@]} | awk 'BEGIN{RS=" ";} {print $1}' | sort
输出将是:
3 5 一种 b C 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 -> $?"
那么,你可以使用这些整数作为数组索引,因为 Bash 总是使用稀疏数组,所以不用担心未使用的索引:
array=(a c b f 3 5)
for el in "${array[@]}"; do
sexy_int "$el"
sorted[$?]="$el"
done
echo "${sorted[@]}"
【讨论】:
最小排序:
#!/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
【讨论】:
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
【讨论】:
对于常见的空格和换行问题有一个解决方法:
使用不在原始数组中的字符(如$'\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$*。[[ $* =~ [$wa] ]]。exit 1
set -f
IFS=$'\n')、循环变量 x 和换行符 (nl=$'\n')。$@)。"${@//$nl/$wa}"。sort -n。set --。for x
sorted+=(…)
"${x//$wa/$nl}"。【讨论】:
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..a}{z..a})
declare -a output
mergesort input output
echo "${input[@]}"
echo "${output[@]}"
【讨论】:
我不相信您需要在 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)) 直到最后十几个元素。对于最终元素,使用选择排序。
a=(e b 'c d')
shuf -e "${a[@]}" | sort >/tmp/f
mapfile -t g </tmp/f
【讨论】:
非常感谢在我之前回答的人。使用他们出色的输入、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 一起正常工作
【讨论】:
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 -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。
sorted=($(echo ${array[@]} | tr " " "\n" | sort))
本着 bash / linux 的精神,我会为每个步骤提供最好的命令行工具。 sort 完成主要工作,但需要用换行符而不是空格分隔输入,所以上面非常简单的管道就可以了:
回显数组内容 --> 用换行符替换空格 --> 排序
$() 是回显结果
($())是将“回显结果”放入数组中
注意:正如@sorontar 在comment 中提到的另一个问题:
sorted=($(...)) 部分使用“split and glob”运算符。您应该关闭 glob:set -f 或 set -o noglob 或 shopt -op noglob 或像 * 这样的数组元素将扩展为文件列表。
【讨论】:
mapfile -t sorted < <(printf '%s\n' "${array[@]}" | sort),否则sorted=(); while IFS= read -r line; do sorted+=( "$line" ); done < <(printf '%s\n' | sort)。
echo ${array[@]} | tr " " "\n":如果数组的字段包含空格和全局字符,这将中断。此外,它会生成一个子外壳并使用无用的外部命令。由于echo 很笨,如果您的数组以-e、-E 或-n 开头,它将中断。改为使用:printf '%s\n' "${array[@]}"。另一个反模式是:($()) 是将“回显结果”放入数组中。当然不是!这是一个可怕的反模式,由于路径名扩展(通配符)和分词而中断。永远不要使用这种恐怖。