与ksh或zsh相反,bash不支持对数组或任意字符串列表进行排序。它可以排序水珠或输出alias
或set
或typeset
(虽然这最后3不会在用户的区域设置排序顺序),但实际上不能在这里使用。
POSIX工具箱中没有任何东西可以很容易地对任意字符串列表进行排序(sort
排序行,因此只有NUL和换行符以外的短字符(LINE_MAX通常比PATH_MAX短)序列,而文件路径是其他非空字节序列大于0)。
因此,尽管您可以awk
使用(使用<
字符串比较运算符)甚至bash
使用(使用[[ < ]]
)实现自己的排序算法,但对于中的任意路径bash
,可移植的最简单的方法可能是perl
:
使用bash4.4+
,您可以执行以下操作:
readarray -td '' sorted_filearray < <(perl -MFile::Basename -l0 -e '
print for sort {basename($a) cmp basename($b)} @ARGV' -- "${filearray[@]}")
这给出了类似strcmp()
的顺序。对于基于语言环境的排序规则(例如,在glob中或在其输出中)的订单ls
,请向中添加一个-Mlocale
参数perl
。对于数字排序(更像GNU,sort -g
因为它支持的数字为+3
,1.2e-5
而不是千位分隔符,尽管不是十六进制),请使用<=>
代替cmp
(和再次使用命令-Mlocale
来纪念用户的小数点sort
)。
您将受到命令参数最大大小的限制。为了避免这种情况,您可以将文件列表传递到perl
其标准输入上,而不是通过参数传递:
readarray -td '' sorted_filearray < <(
printf '%s\0' "${filearray[@]}" | perl -MFile::Basename -0le '
chomp(@files = <STDIN>);
print for sort {basename($a) cmp basename($b)} @files')
对于较旧的版本bash
,您可以使用while IFS= read -rd ''
循环代替,readarray -d ''
也可以perl
输出正确引用的路径列表,以便将其传递给eval "array=($(perl...))"
。
使用zsh
,您可以伪造全局扩展,可以为其定义排序顺序:
sorted_filearray=(/(e{'reply=($filearray)'}oe{'REPLY=$REPLY:t'}))
通过reply=($filearray)
我们实际上迫使全局扩展(最初只是/
)成为数组的元素。然后,我们根据文件名的尾部定义排序顺序。
对于strcmp()
样顺序,固定区域设置为C.对于数值排序(类似于GNU sort -V
,不sort -n
进行比较时,这使得一个显著差1.4
和1.23
(语言区域.
是十进制标记)例如),添加n
水珠限定符。
除了oe{expression}
,您还可以使用函数来定义排序顺序,例如:
by_tail() REPLY=$REPLY:t
或更高级的类似:
by_numbers_in_tail() REPLY=${(j:,:)${(s:,:)${REPLY:t}//[^0-9]/,}}
(因此a/foo2bar3.pdf
(2,3数字)在b/bar1foo3.pdf
(1,3)之后但在c/baz2zzz10.pdf
(2,10)之前排序)并用作:
sorted_filearray=(/(e{'reply=($filearray)'}no+by_numbers_in_tail))
当然,这些可以应用于真正的glob,因为这是它们的主要目的。例如,对于pdf
任何目录中的文件列表,按基名称/尾排序:
pdfs=(**/*.pdf(N.oe+by_tail))
¹如果strcmp()
可以接受基于-的排序,并且对于短字符串,则可以awk
在传递给字符串之前将其转换为十六进制编码,然后sort
在排序之后进行转换。