Arrays 如何在Bash中对数组进行排序
我在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
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或BSDsort
(遗憾的是,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+,带有GNUsort
):
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")