我应该在Perl正则表达式中使用\d还是[0-9]来匹配数字?

71

最近几周我看了很多问题/答案,发现在perl正则表达式中使用\d被评论为不正确。因为在perl的后续版本中,\d[0-9]不同,\d将表示任何具有数字属性的Unicode字符,而[0-9]则表示字符'0','1','2',...,'9'。

我知道在某些情况下,[0-9]将是正确的用法,而在其他情况下,则是\d。我想知道人们认为哪个是正确的默认用法?

个人觉得\d符号非常简洁且表达力强,而相比之下,[0-9]则有些冗长。但我对编写多语言代码或者说针对不符合ASCII字符范围的语言代码的经验很少,因此可能有点天真。

我注意到...

$find /System/Library/Perl/5.8.8/ -name \*pm | xargs grep '\\d' | wc -l
  298
$find /System/Library/Perl/5.8.8/ -name \*pm | xargs grep '\[0-9\]' | wc -l
  26
9个回答

86

在我看来使用\d非常危险,这是语言中一个糟糕的设计决策,因为大多数情况下你想要使用[0-9]。如果使用 ASCII 数字,则哈夫曼编码应该使用\d

之前的大多数帖子已经强调了为什么应该使用[0-9],所以让我再给你提供一些数据:

  • 如果我正确地阅读了 Unicode 图表,则'۷۰'是一个数字(印度数字70,不要相信我的话)。

  • 试一试:

    $ perl -le '$one = chr 0xFF11; print "$one + 1 = ", $one+1;'
    1 + 1 = 1
    
    这里是一份有效数字的部分列表(这取决于您使用的字体,可能不会在浏览器中正确显示)。每个数字只有第一个被解释为数字,如上所示,在使用Perl进行算术计算时:
  •  ZERO:  0٠۰߀०০੦૦୦௦౦೦൦๐໐0
     ONE:   1١۱߁१১੧૧୧௧౧೧൧๑໑1
     TWO:   2٢۲߂२২੨૨୨௨౨೨൨๒໒2
     THREE: 3٣۳߃३৩੩૩୩௩౩೩൩๓໓3
     FOUR:  4٤۴߄४৪੪૪୪௪౪೪൪๔໔4
     FIVE:  5٥۵߅५৫੫૫୫௫౫೫൫๕໕5
     SIX:   6٦۶߆६৬੬૬୬௬౬೬൬๖໖6
     SEVEN: 7٧۷߇७৭੭૭୭௭౭೭൭๗໗7
     EIGHT: 8٨۸߈८৮੮૮୮௮౮೮൮๘໘8
     NINE:  9٩۹߉९৯੯૯୯௯౯೯൯๙໙9��
    

你还没有被说服吗?


14
谢谢您的夸奖!+1 代表赞同这个列表,我开始想知道还有哪些数字字符。 - nickf
2
如果Perl迄今已经接受了UNICODE,那么它似乎应该走到底并处理所有数字。当然,这样做会导致疯狂的局面,但是疯狂不是所有Perl程序员的命运吗;-)? - RBerteig
还有更多的字符,但我只包括了我系统上可以显示的字符。我使用了http://www.unicode.org/Public/UNIDATA/UnicodeData.txt中的Unicode数据,并从那里提取了字符信息。 - mirod
我理解并欣赏如果你正在处理UNICODE,你需要处理perl无法进行算术运算的数字。如果我使用\d,我可能会得到一些数字,无法进行算术运算,但如果我使用[0-9],我可能会错过我想要捕获的数字...所以哪个是正确的 - 这完全取决于输入的上下文。我想我觉得perl决定让简写\d表示任何数字字符,而不是我可以进行算术运算的任何数字字符,或者至少没有提供另一个合适的简写,这让人感到不直观。 - Beano
1
@nickf,根据我的统计,目前有61组数字,请查看我回答中的模块链接以获取列表。 - Chas. Owens
2
@Beano 我并不是说不要使用 \d;我是说当你想要匹配 [0-9] 时不要使用 \d。这就像当你想要匹配 [ ] 时不要使用 \s 一样。问题在于,你是否介意同时匹配 ⑤ 和 5? - Chas. Owens

50
为了最大程度的安全性,建议在任何不特别想匹配所有Unicode定义数字的情况下使用[0-9]
根据perluniintro,Perl不支持使用除[0-9]以外的数字作为数字,所以如果以下条件都成立,我会强烈建议使用[0-9]:
1. 您想将结果用作数字(例如,在其中执行数学运算或将其存储在仅接受正确数字的位置(例如数据库中的INT列))。 2. 在数据中可能存在非数字字符[^0-9],从而使正则表达式能够匹配它们。(请注意,对于不受信任/有敌意的输入,这一点应始终被考虑为真。)
如果有任何一个条件不成立,则很少有理由特别地不使用\d(您可能会发现这种情况),如果您想匹配所有Unicode定义数字,则肯定需要使用\d

3
如果应用于Unicode字符串,\d确实可以匹配超过10个不同的字符。 - pts
\d 匹配任何具有数字属性的内容。如果您只想匹配0、1、2、3、4、5、6、7、8和9,则可以使用[0-9]进行匹配,或者将/a添加到字符类快捷方式中以获取ASCII语义。https://www.effectiveperlprogramming.com/2011/01/know-your-character-classes/ - brian d foy

11
根据 perlreref\d 是区域和 Unicode 感知的。但是,如果您使用的编码不是 Unicode,则无需担心 Unicode 数字;如果您使用的编码类似于 Latin-1(ISO 8859-1 或 8859-15),则区域感知也不会对您造成影响,因为该编码不包含任何其他数字字符。因此,对于许多人来说,在大多数情况下,您可以放心使用 \d。但是,如果 Unicode 数据是您工作的一部分,则需要更仔细地考虑您要寻找的内容。

6
就像从轨道上摧毁站点一样,[0-9] 是确保的唯一方法。是的,它很丑陋。是的,使\d 成为UNICODE和区域设置感知的选择是愚蠢的。但这是我们的问题,我们必须面对它。
至于那些掩耳盗铃地说它不影响他们今天使用的字符集的人,你可能今天正在使用该字符集,但全世界现在都在使用UTF-8,你也很快就会使用它。记住编写代码时要像维护您的代码的人是一个认识您住址的杀人狂一样。
哦,至于Perl模块使用\d[0-9],即使核心仍然存在UNICODE问题
如果您确实意味着任何数字,但想要能够对结果进行数学运算,则可以使用Text::Unidecode
#!/usr/bin/perl

use strict;
use warnings;

use Text::Unidecode;

my $number = "\x{1811}\x{1812}\x{1813}\x{1814}\x{1815}";
print "$number is ", unidecode($number), "\n";

经过更多测试,看起来Text::Unidecode不能正确处理所有数字字符。我正在编写一个模块来解决这个问题。


4

我认为两者都有其应用场景。然而,在我的封闭的大型美国企业世界中,99.999%的时间(特别是在处理数据时)它们是可以互换使用的。我每天都使用perl来操作数据,在我处理的数据集中没有任何不符合[0-9]规则的数字。然而,我也意识到\d[0-9]之间存在重要区别,了解这种区别是很好的。我使用\d是因为它更加简洁(正如你所说),在我处理数据的小世界中从来不会出错。


你需要\d而不是/d——如果你真的需要它的话。 - Telemachus
\d 匹配任何具有数字属性的内容。如果您只想匹配0、1、2、3、4、5、6、7、8和9,则可以使用[0-9]进行匹配,或者将/a添加到字符类快捷方式中以获取ASCII语义。https://www.effectiveperlprogramming.com/2011/01/know-your-character-classes/ - brian d foy

2
如果您将\d应用于Unicode字符串(例如在"\X{660}" =~ /\d/中),它将匹配Unicode数字。如果您将\d应用于二进制字符串(例如上述UTF-8的等效形式:"\xd9\xa0" =~ /\d/),它只会匹配10个ASCII数字。Perl 5.8默认不创建Unicode字符串(除非您明确要求,例如在"\X{...}"use utf8;等)。因此,我的建议是:仅在您的应用程序使用Unicode字符串时关注\d[0-9]之间的区别。

2
为什么要允许区分,如果有一种方法可以每次都得到你想要的呢?\d匹配任何具有数字属性的内容。如果您只想匹配0、1、2、3、4、5、6、7、8和9,请使用[0-9]进行匹配,或者添加/a以获取字符类快捷方式的ASCII语义。https://www.effectiveperlprogramming.com/2011/01/know-your-character-classes/ - brian d foy

2

使用\d的主要反对意见似乎是非ASCII数字。

可以通过使用/a选项来避免这种情况。例如:

m/\d/a

这将限制数字匹配仅限于ASCII。

https://perldoc.perl.org/perlre#/a-(and-/aa):

在 /a 模式下,\d 总是精确匹配数字 "0" 到 "9"

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

1
如果使用 \d 感觉不太顺手,也许你可以定义一个正则表达式: $d=qr/[0-9]/; 然后用它来代替 \d

-2
随着数据格式控制的增加,模式特异性的需求降低...
例如,如果你匹配了一段机器生成的数据,并且它总是遵循相同的输出格式规则,那么你就不需要太精确了。 以IPv4地址为例。如果你要从路由器接口配置行中提取IP地址,你只需要像这样:
 'ip\haddress\h(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\D'

然而,如果你试图在某个地方(比如电子邮件X-Header)深处找到嵌入的IP地址,或者你试图验证一个IP地址,那就是另一回事了!


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