统一码数字

统一码数字

维基百科关于正则表达式的文章,看来[[:digit:]]= [0-9]= \d

什么情况下它们不相等?有什么不同?

经过一些研究,我认为一个区别是括号表达式[:expr:]依赖于区域设置。

答案1

是的,它是[[:digit:]]~~ (其中〜表示近似)。 在大多数编程语言(支持的语言)中[0-9]\d

\d ≡ `[[:digit:]]`            # (is identical to, it is a short hand for).  

存在\d的实例少于[[:digit:]](在 POSIX 中可用grep -P,但在 POSIX 中不可用)。

统一码数字

UNICODE 中的许多数字, 例如:

123456789 # Hindu-Arabic 阿拉伯数字
٠١٢٣٤٥٦٧٨٩ # ARABIC-INDIC
۰۱۲۳۴۵۶۷۸۹ # EXTENDED ARABIC-INDIC/PERSIAN
߀߁߂߃߄߅߆߇߈߉ # NKO DIGIT
०१२३४५६७८९ # DEVANAGARI

所有这些可能包括在内[[:digit:]]或中\d,甚至在 的某些情况下[0-9]


POSIX

对于特定的 POSIX BRE 或 ERE:不
支持\d(不在 POSIX 中,但在 GNU 中grep -P)。 [[:digit:]]POSIX 要求对应于数字字符类,而 ISO C 要求数字字符类为字符 0 到 9,仅此而已。所以仅在 C 语言环境中所有[0-9][0123456789]\d[[:digit:]]的含义完全相同。没有[0123456789]可能的误解,[[:digit:]]在更多实用程序中可用,在某些情况下仅意味着[0123456789]\d很少有实用程序支持它。

至于[0-9],范围表达式的含义仅由 C 语言环境中的 POSIX 定义;在其他语言环境中,它可能会有所不同(可能是代码点顺序或排序规则或其他内容)。

[0123456789]

所有 ASCII 数字的最基本选项。
始终有效,(AFAICT)没有已知的失败实例。

它仅匹配英文数字:0123456789

[0-9]

一般认为[0-9]只是 ASCII 数字0123456789
在某些情况下,这是极其错误的:Linux 在某些非“C”(2020 年 6 月)系统的语言环境中,例如:

认为:

str='0123456789 ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९'

尝试grep发现它允许大多数:

$ echo "$str" | grep -o '[0-9]\+'
0123456789
٠١٢٣٤٥٦٧٨
۰۱۲۳۴۵۶۷۸
߀߁߂߃߄߅߆߇߈
०१२३४५६७८

那个 sed 有一些麻烦。应该仅删除0123456789但删除几乎所有数字。这意味着它接受大多数数字,但不接受一些九(???):

$ echo "$str" | sed 's/[0-9]\{1,\}//g'
 ٩ ۹ ߉ ९

即使 expr 也遇到了与 sed 相同的问题:

expr "$str" : '\([0-9 ]*\)'             # also matching spaces.
0123456789 ٠١٢٣٤٥٦٧٨

还有编辑

printf '%s\n' 's/[0-9]/x/g' '1,p' Q | ed -v <(echo "$str")
105
xxxxxxxxxx xxxxxxxxx٩ xxxxxxxxx۹ xxxxxxxxx߉ xxxxxxxxx९

[[:数字:]]

语言有很多种:Perl、Java、Python、C。其中[[:digit:]](和\d) 需要扩展含义。例如,此 perl 代码将匹配上面的所有数字:

$ str='0123456789 ٠١٢٣٤٥٦٧٨٩ ۰۱۲۳۴۵۶۷۸۹ ߀߁߂߃߄߅߆߇߈߉ ०१२३४५६७८९'

$ echo "$str" | perl -C -pe 's/[^\d]//g;' ; echo
0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८९

这相当于选择具有Numeric和的 Unicode 属性的所有字符digits

$ echo "$str" | perl -C -pe 's/[^\p{Nd}]//g;' ; echo
0123456789٠١٢٣٤٥٦٧٨٩۰۱۲۳۴۵۶۷۸۹߀߁߂߃߄߅߆߇߈߉०१२३४५६७८९

哪个 grep 可以重现(特定版本的 pcre 可能具有与 Perl 不同的内部数字代码点列表):

$ echo "$str" | grep -oP '\p{Nd}+'
0123456789
٠١٢٣٤٥٦٧٨٩
۰۱۲۳۴۵۶۷۸۹
߀߁߂߃߄߅߆߇߈߉
०१२३४५६७८९

贝壳

某些实现可能会将范围理解为与普通 ASCII 顺序不同的内容(例如 ksh93)(在 2018 年 5 月版本 (AT&T Research) 93u+ 2012-08-01 上进行测试时):

$ LC_ALL=en_US.utf8 ksh -c 'echo "${1//[0-9]}"' sh "$str"
  ۹ ߀߁߂߃߄߅߆߇߈߉ ९

现在(2020 年 6 月),来自 debian 的相同软件包 ksh93(相同版本 sh (AT&T Research) 93u+ 2012-08-01):

$ LC_ALL=en_US.utf8 ksh -c 'echo "${1//[0-9]}"' sh "$str"

 ٩ ۹ ߉ ९

在我看来,这肯定是即将发生的错误的来源。

答案2

这取决于您如何定义数字;[0-9]往往只是 ASCII 数字(或者可能是其他既不是 ASCII 也不是 ASCII 超集的数字,而是与 ASCII 中相同的 10 位数字,只是具有不同的位表示形式 (EBCDIC));\d另一方面,它可能只是简单的数字(旧版本的 Perl,或/a启用了正则表达式标志的现代版本的 Perl),也可能是 Unicode 匹配,其中的数字集比或匹配\p{Digit}更大。[0-9]/\d/a

$ perl -E 'say "match" if 42 =~ m/\d/'
match
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/\d/'
match
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/\d/a'
$ perl -E 'say "match" if "\N{U+09EA}" =~ m/[0-9]/'
$ 

perldoc perlrecharclass有关更多信息,或查阅相关语言的文档以了解其行为方式。

但是等等,还有更多!区域设置也可能会改变\d匹配的内容,因此\d可以匹配比完整的 Unicode 集更少的数字,并且(希望通常)还包括[0-9].这类似于 C 中isdigit(3)( [0-9]) 和isnumber(3)([0-9以及语言环境中的其他内容) 之间的差异。

可能可以进行调用来获取该数字的值,即使它不是[0-9]

$ perl -MUnicode::UCD=num -E 'say num(4)'
4
$ perl -MUnicode::UCD=num -E 'say num("\N{U+09EA}")'
4
$ 

答案3

其他答案中已经很好地解释了理论上的差异,因此仍然需要解释实际的差异。

以下是匹配数字的一些更常见的用例:


一次性数据提取

通常,当您想要处理一些数字时,数字本身位于格式笨拙的文本文件中。您想要提取它们以在您的程序中使用。您可能可以知道数字格式(通过查看文件)和您当前的区域设置,所以它是可以使用任何表格,只要能完成工作。\d需要最少的击键,因此非常常用。

输入清理

您有一些不受信任的用户输入(可能来自网络表单),并且您需要确保它不包含任何意外。也许您想将其存储在数据库的数字字段中,或者用作在服务器上运行的 shell 命令的参数。在这种情况下,你真的想要[0-9],因为它是最具限制性和可预测性的。

数据验证

您有一些数据,您不会将其用于任何“危险”的事情,但很高兴知道它是否是一个数字。例如,您的程序允许用户输入地址,并且如果输入不包含门牌号,您希望突出显示可能的拼写错误。在这种情况下,您可能希望尽可能广泛,所以[[:digit:]]是要走的路。


这些似乎是数字匹配的三个最常见的用例。如果您认为我错过了重要的内容,请发表评论。

答案4

[0-9]的不同含义在其他答案中有介绍。在这里我想添加正则表达式引擎的实现差异。[[:digit:]]\d

            [[:digit:]]    \d
grep -E               ✓     ×
grep -P               ✓     ✓
sed                   ✓     ×
sed -E                ✓     ×

所以[[:digit:]]总是有效\d依靠。在 grep 的手册中提到它[[:digit:]]只是0-9C语言环境中。

PS1:如果您了解更多,请扩展表格。

PS2:使用GNU grep 3.1和GNU 4.4进行测试。

相关内容