【问题标题】:Bash string lexicographical comparisons inconsistencyBash字符串字典比较不一致
【发布时间】:2021-06-27 06:00:56
【问题描述】:

Bash manual section 6.4 将 [[ string1

如果 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=

【问题讨论】:

标签: bash string-comparison lexicographic-ordering


【解决方案1】:

这与你的 shell 没有太大关系。要对.A1B 执行与语言环境相关的字典比较,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

上面的摘录还显示,为了在不改变语言环境设置的情况下强制使用byte-by-byte comparison,需要将 shell 兼容级别更改为 40(代表 4.0,最后一个版本的 bash默认情况下的行为方式符合您的预期)。

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

现在,至于你的问题(第一个和第二个比较与 ASCII 表一致,但为什么第三个是错误的?这个字典排序顺序到底是什么?),嗯,这是你的语言环境排序顺序显然。在What Collation is NOT 下,UCA 规范说:

一般来说,在串联或子字符串操作下不保留排序规则。

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

x 并不暗示 xz
x 并不暗示 zx
xz 并不暗示 x
zx 并不暗示 x

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

【讨论】:

  • 那我不明白strcoll(".A", "1B") 返回大于零。 “.A”的两个字节不构成 16 位汉字或任何其他语言。它们仍然是 UTF-8 中的 ASCII 值。
  • strcoll"大于零的值表示第一个不匹配的字符在str1中的值大于在str2中的值;"
  • @MichaelChen 我没说他们有。 Bash 是用 C 编写的,而不是 C++; C standard 没有提到逐个字符的比较。在您的语言环境中,序列.A1B 之后排序。
【解决方案2】:

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

为了解决语言敏感排序的复杂性,采用了多级比较算法。在比较两个单词时,最重要的特征是基本字母的同一性——例如,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

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

(当然,除此之外还有很多其他复杂情况。)

【讨论】:

    猜你喜欢
    • 2015-08-30
    • 2012-03-02
    • 2015-10-11
    • 2012-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-06
    相关资源
    最近更新 更多