4

Bash 手册第 6.4 节将 [[ string1 < string2 ]] 描述为

如果 string1 在当前语言环境中按字典顺序在 string2 之后排序,则为真。

我使用的是普通的英语 Linux,并希望我当前的语言环境是 ASCII,其中句点 [.] 在字典上小于 [0-9A-Za-z]。但是,看看这些:

$ echo $BASH_VERSION
4.3.11(1)-release
$ [[ "." < "1" ]] && echo "yes"
yes
$ [[ "A" < "B" ]] && echo "yes"
yes
$ [[ ".A" < "1B" ]] && echo "yes"
$

第 1 次和第 2 次比较与 ASCII 表一致,但为什么第 3 次是错误的?这个词典排序顺序到底是什么?

这是语言环境的输出:

$ locale
LANG=en_US.UTF-8
LANGUAGE=
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=
4

2 回答 2

3

这与您的外壳没有太大关系。.A要对and执行依赖于语言环境的字典比较1B,bash 只需调用strcoll(".A", "1B")并解释返回值,仅此而已。

    {
#if defined (HAVE_STRCOLL)
      if (shell_compatibility_level > 40 && flags & TEST_LOCALE)
    return ((op[0] == '>') ? (strcoll (arg1, arg2) > 0) : (strcoll (arg1, arg2) < 0));
      else
#endif
    return ((op[0] == '>') ? (strcmp (arg1, arg2) > 0) : (strcmp (arg1, arg2) < 0));
    }

(从test.c复制)

上面的摘录还显示,为了在不更改语言环境设置的情况下强制进行逐字节比较,需要将 shell 兼容级别更改为40(代表 4.0,这是 bash 的最后一个版本,默认情况下的行为方式符合您的预期) )。

$ shopt -s compat40
$ [[ .A < 1B ]] && echo yes
yes
$ 

现在,关于您的问题(第一个和第二个比较与 ASCII 表一致,但为什么第三个是错误的?这个词典排序顺序到底是什么?),好吧,显然这是您的语言环境的排序规则。在What Collat​​ion is NOT下,UCA 规范说:

通常,在连接或子字符串操作下不保留排序顺序。

例如, x 小于 y 的事实并不意味着x + z小于y + z,因为字符可能会形成跨子字符串或连接边界的收缩。总之:

x < y并不意味着xz < yz
x < y并不意味着zx < zy
xz < yz并不意味着x < y
zx < zy并不意味着x < y

我认为,这证实了这不是一个错误,而是一个特性。

于 2021-06-27T13:23:27.460 回答
1

UTF-8 排序规则不会像传统的 ASCIIbetical 排序规则那样逐个字符地进行。它使用多级比较,其中某些类型的差异优先于其他类型,即使它们出现在字符串的后面。在这种情况下,您看到的结果是“基本字符”顺序(“A”<“1B”)优先于标点差异。这是标准的引用:

为了解决语言敏感排序的复杂性,采用了多级比较算法。在比较两个单词时,最重要的特征是基本字母的同一性——例如,A 和 B 之间的差异。如果基本字母不同,通常会忽略重音差异。如果基本字母或其重音不同,则通常会忽略大小写差异(大写与小写)。标点符号的处理因人而异。在某些情况下,标点符号被视为基本字母。在其他情况下,如果有任何基数、重音或大小写差异,则应忽略它。[...]

这是一个显示标点符号与“基本字符”优先级的示例:

$ printf '%s\n' {,.,-}{,1,A,AB,B,BA} | LANG=en_US.UTF-8 sort
-
.
-1
.1
1
-A
.A
A
-AB
.AB
AB
-B
.B
B
-BA
.BA
BA

请注意,标点符号仅对打破包含相同基本字符的行之间的联系很重要。您还可以看到涉及大写和重音的类似效果:

printf '%s\n' {a,A,B}{A,Å,B} | LANG=en_US.UTF-8 sort
aA
AA
aÅ
AÅ
aB
AB
BA
BÅ
BB

请注意,第二个字符的重音优先级高于第一个字符的大写(并且字符串中任何位置的标点符号的优先级都低于任何一个)。

(当然,除此之外还有很多其他并发症。)

于 2021-06-27T21:14:43.120 回答