括号表达式(无范围)匹配 bash 中的意外字符

Tux*_*ife 20 bash locale test

我在 Linux 上使用 bash。我从以下 if 语句中获得了成功,但这不应该返回失败代码吗?

if [[ ? = [???] ]] ; then echo yes ; fi
Run Code Online (Sandbox Code Playgroud)

正方形不等于任何字符,所以我不明白为什么我会得到成功代码。

在我的案例中保留双括号对我来说很重要。

在这种情况下有没有其他方法可以做一个范围,或者有什么其他建议?

Sté*_*las 29

这是这些字符具有相同排序顺序的结果。

你还会注意到

sort -u << EOF
?
?
?
?
EOF
Run Code Online (Sandbox Code Playgroud)

只返回一行。

或者那个:

expr ? = ?
Run Code Online (Sandbox Code Playgroud)

返回 true(根据 POSIX 的要求)。

GNU 系统附带的大多数语言环境都具有许多具有相同排序顺序的字符(甚至是字符序列(整理序列))。在那些的情况下????这是因为没有定义顺序,而那些没有定义顺序的字符在 GNU 系统中最终具有相同的排序顺序。有些字符被明确定义为具有相同的排序顺序,例如 ? 和 ?(尽管(尽管对我来说)没有明显的(对我来说)如何完成的真正逻辑或一致性)。

这是令人惊讶和虚假行为的根源。我最近在 Austin 组(POSIX 和单一 UNIX 规范背后的主体)邮件列表上提出了这个问题,并且讨论仍在进行中,截至 2015 年 4 月 3 日。

在这种情况下,我不清楚是否[y]应该匹配xwherexysort 相同,但由于括号表达式旨在匹配整理元素,这表明该bash行为是预期的。

无论如何,我想[?-?]或至少[?-?]应该匹配?.

您会注意到不同的工具表现不同。ksh93 的行为类似于bashGNUgrepsed不。其他一些 shell 有不同的行为,有些yash甚至更多。

要获得一致的行为,您需要一个所有字符排序不同的语言环境。C语言环境是典型的语言环境。但是,大多数系统上 C 语言环境中的字符集是 ASCII。在 GNU 系统上,您通常可以访问C.UTF-8可用于处理 UTF-8 字符的语言环境。

所以:

(export LC_ALL=C.UTF-8; [[ ? = [???] ]])
Run Code Online (Sandbox Code Playgroud)

或标准等价物:

(export LC_ALL=C.UTF-8
 case ? in ([???]) true;; (*) false; esac)
Run Code Online (Sandbox Code Playgroud)

应该返回false。

另一种选择是仅设置LC_COLLATE为 C,它可以在 GNU 系统上工作,但不一定适用于其他可能无法指定多字节字符排序顺序的系统。


其中的一个教训是,在比较字符串时,相等并不像人们所期望的那样清晰。平等可能意味着,从最严格到最不严格。

  1. 相同数量的字节和所有字节成分具有相同的值。
  2. 相同数量的字符和所有字符都相同(例如,引用当前字符集中的相同代码点)。
  3. 根据语言环境的整理算法,这两个字符串具有相同的排序顺序(即,a < b 和 b > a 都不为真)。

现在,对于 2 或 3,假设两个字符串都包含有效字符。在 UTF-8 和其他一些编码中,某些字节序列不能形成有效字符。

因此,1 和 2 不一定等效,或者因为某些字符可能具有不止一种可能的编码。这通常是像 ISO-2022-JP 这样的有状态编码的情况,其中A可以表示为411b 28 42 411b 28 42作为切换到 ASCII 的序列,您可以根据需要插入任意数量的那些,这不会有什么区别),尽管我不会期望这些类型的编码仍在使用中,并且 GNU 工具至少通常无法正常使用它们。

还要注意,大多数非 GNU 实用程序无法处理 0 字节值(ASCII 中的 NUL 字符)。

使用哪些定义取决于实用程序和实用程序实现或版本。POSIX 对此并不是 100% 清楚。在 C 语言环境中,所有 3 个都是等效的。在那个 YMMV 之外。