UNIX排序命令忽略空格。

23

给定一个名为 txt 的文件:

ab
a c
a a

调用 sort txt 后,我获得:

a a
ab
a c
换句话说,这不是正确的排序,它会删除/忽略空格!我原本期望这是sort -i的行为,但它发生在有或没有-i标志的情况下。
我想要得到“正确”的排序。
a a
a c
ab

我该如何做到这一点?


我已经创建了您的输入文件,并使用排序功能提供所需的输出,没有任何问题。txt文件是在*nix系统上创建的吗?您确定它们是空格而不是其他类型的字符吗? - marto
是的,我实际上在我的命令行中输入了这个确切的例子...使用Ubuntu默认安装,几乎没有进行环境调整。 - dagnelies
请将正确的解决方案标记为已接受,而不是编辑问题以读取“已解决”。 - razlebe
实际上,这是正确的排序方式。它被称为库或字典排序,其中我们只查看字母的差异,而不是空格或标点符号。这是Unicode排序算法的默认模式,至少在达到第4级之前是这样的。然而,这并不是Unix sort命令应该采取的方式,因为Unix sort命令是基于字段而不是基于文本的。 - tchrist
可能是gnu排序产生意外结果的重复问题 - Cristian Ciupitu
7个回答

28

解决者:

export LC_ALL=C

sort()文档中获取的警告:

警告:环境指定的语言环境会影响排序顺序。设置 LC_ALL=C 以获得使用本机字节值的传统排序顺序。

(至少适用于ASCII,对于UTF8无法确定)


1
这是因为sort的帮助菜单上写着: *** 警告 *** 环境指定的语言环境会影响排序顺序。 设置LC_ALL=C以获取使用本地字节值的传统排序顺序。 - A. K.
2
@Aditya:是啊,你说的没错,什么是“locale”?为什么它会影响排序呢?默认排序为什么不是默认使用的呢?(我的LC_ALL为空)在这种情况下使用哪种排序方式?如果这些对我来说都不明显,那我很抱歉。 - dagnelies
2
...是啊,我刚刚注意到 LC_ALL=C 破坏了我的 UTF8 字符显示...所以要么我无法正确排序它们,要么无法正确显示它们。太好了! - dagnelies
2
你不必导出LC_ALL,只需在单个命令中运行它 - 就像LC_ALL=C sort ...一样。 - CmdrMoozy
2
“到底什么是'locale'?为什么它会影响排序?为什么默认情况下不使用默认排序?”-- 没有一种正确的排序顺序。不同的人对事物应该如何排序有不同的看法。其中一些取决于“locale”,例如美国或德国。因此,计算机“locale”是一种环境设置,影响排序顺序、大小写转换、数字格式等等,以便这些函数执行该地区认为的“默认”操作。LC_ALL=C是最小公共分母;您实际上是在告诉计算机“装傻”。 - DevSolar
显示剩余2条评论

13

如前所述,LC_ALL=C sort 就可以解决问题。这是因为不同的语言对于排序字符有不同的规则,这些规则通常由高级语言学家而非计算机科学专家制定。而你所在的区域设置中,这些规则似乎认为在排序中应该忽略空格。

通过在 LC_ALL 前加上 C(或者当 LC_ALL 未设置时,LC_COLLATE=C 也可以),你明确地声明了无关语言的排序方式(以及数字格式和其他东西)。这正是你在这种情况下所需要的。如果你想将这个设置作为默认值,在你的环境中导出 LC_COLLATE 即可。

选择这种默认方式是为了保持与“正常”的现实世界排序方案(例如电话簿)的一致性,这些方案通常会忽略空格。


3
您可以使用“env”程序在排序期间暂时更改LC_COLLATE。例如:
/usr/bin/env LC_COLLATE=POSIX /bin/sort file1 file2
虽然在命令行上有点繁琐,但如果您在脚本中使用它,应该是透明的。

在脚本中,您可以定义一个函数:sort_posix() { env LC_COLLATE=POSIX sort "$@"; } - myrdd

3

在一些语言中,有些字母超出了[A-Za-z]的范围,因此仅使用C语言环境即按照字节值排序并不是一个好的解决方案。这些字母在UTF-8中由多个字节表示,因此字节值的排序顺序并不是我们想要的。(有些字符可能有两种等效的表示形式(预组合和分解))。

尽管如此,空格的处理仍然是一个问题。 我尝试了以下方法:

$ cat stest  
a b  
a c  
ab  
a d  

$ sort stest  
ab  
a b  
a c  
a d  

$ sort -k 1,1 stest  
a b  
a c  
a d  
ab  

对于我的需求,使用“-k 1,1”就可以解决问题了。我尝试过另一种但比较麻烦的解决方案,即将空格替换为某个辅助字符,然后进行排序,最后再将辅助字符改回空格。


1

我一直在研究如何优化我维护的一个shell脚本,因为它有很多国际用户(重点是百分比,而不是数量)。

我在网上和SO上看到的大多数选项似乎都建议像这里看到的那样,在全局范围内设置区域设置(过度)。

export LC_ALL=C

或者像从gnu.org这样将其输送到每个单独的命令中(繁琐)

$ echo abcdefghijklmnopqrstuvwxyz | LC_ALL=C /usr/xpg4/bin/tr 'a-z' 'A-Z' ABCDEFGHIJKLMNOPQRSTUVWXYZ

我希望避免在运行我的程序时无意中破坏用户的区域设置。这很容易实现,只需省略全局化即可,就像您所期望的那样。不需要将此变量导出到程序之外。
由于某些原因,我必须设置LANG而不是LC_ALL,但所有单独的区域设置都已设置,对我来说足够了。
这是测试,非常简单。
#!/bin/bash
# locale_checker.sh

#Check and set locale to LC_ALL to optimize character sort and search.
echo "locale was $LANG"
LANG=C
locale

并输出证明它是临时的,并且可以限制为我的脚本进程的内容。

mateor@:~/snippets$ ./locale_checker.sh
locale was en_US.UTF-8
LANG=C
LANGUAGE=en_US:en
LC_CTYPE="C"
LC_NUMERIC="C"
LC_TIME="C"
LC_COLLATE="C"
LC_MONETARY="C"
LC_MESSAGES="C"
LC_PAPER="C"
LC_NAME="C"
LC_ADDRESS="C"
LC_TELEPHONE="C"
LC_MEASUREMENT="C"
LC_IDENTIFICATION="C"
LC_ALL=
mateor@:~/snippets$ locale
LANG=en_US.UTF-8
LANGUAGE=en_US:en
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

这样做可以获得优化的区域设置,而不会破坏其他人的环境,并避免在你认为可能有帮助的地方到处传输它的乏味。


1
一个名为locale_checker.sh的实用程序不应该修改语言环境。 - PointedEars

0

很奇怪,在这里可以运行(cygwin)。

尝试使用sort -d txt


0

对我来说

$ cat txt
ab
a c
a a
$ sort txt
a a
a c
ab

我敢打赌,在你的ac之间,你有一个不间断空格、半个空格或其他高代码点空格!

编辑

刚在Linux上运行了一下。我应该看看标签。是的,我得到了与你相同的输出!我的第一次运行是在Mac上。看起来是GNU和BSD之间的差异。我会进一步调查。

编辑2:

Linux使用基于字段的排序...仍在寻找如何抑制它。尝试过了

sort -t, txt

希望欺骗GNU以为整行是一个字段,但它仍然使用当前语言环境进行排序。

编辑3:

原帖作者通过设置语言环境为C来解决了这个问题。

export LC_ALL=C

似乎没有其他方法。 sort 命令将使用当前语言环境,尽管它经常说 C(或其别名 POSIX)是默认语言环境,但如果您使用的是 Linux,则可能已为您设置。 输入 locale -a 查看可用的语言环境。 在我的系统上:

$ locale -a
C
POSIX
en_AG
en_AU.utf8
en_BW.utf8
en_CA.utf8
en_DK.utf8
en_GB.utf8
en_HK.utf8
en_IE.utf8
en_IN
en_NG
en_NZ.utf8
en_PH.utf8
en_SG.utf8
en_US.utf8
en_ZA.utf8
en_ZW.utf8

看起来将区域设置为 C(或其别名 POSIX)是打破基于字段的 sort 行为并将整行作为一个字段处理的唯一方法。在我看来,这相当奇怪。我认为-t-k 选项,或者可能是一些新选项会是更明智的方法来实现这一点。

顺便说一句,看起来这个问题之前已经在 SO 上问过了:从 GNU sort 中得到意外结果


嗯...奇怪...我肯定有一个普通的空格...实际上,我在我的命令行中输入了相同的示例,结果与你不同...多么奇怪。顺便说一下,我正在使用Ubuntu默认安装,几乎没有进行环境调整。 - dagnelies
我得到了与@arnaud相同的输出。 - A. K.
请查看我的答案,以避免覆盖用户的语言环境。 - mateor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接