在探索正则表达式(也称为正则表达式)时,有许多人似乎将正则表达式视为圣杯。看起来如此复杂的东西——一定是任何问题的答案。他们倾向于认为每个问题都可以用正则表达式解决。

另一方面,也有很多人不惜一切代价试图避免正则表达式。他们试图找到一种绕过正则表达式的方法,并仅仅为了它而接受额外的编码,即使正则表达式将是一个更紧凑的解决方案。

为什么正则表达式被认为如此有争议?关于它们的工作原理是否存在广泛的误解?或者正则表达式通常比较慢是一种普遍的看法?


当前回答

正则表达式对包括我自己在内的很多人来说都是一个严重的谜。这很有效,但就像看数学方程一样。我很高兴地告诉大家,有人终于在http://regexlib.com/上创建了各种正则表达式函数的统一位置。现在,如果微软只创建一个正则表达式类,它将自动完成许多常见的事情,如消除字母或过滤日期。

其他回答

在某些情况下,我认为你必须使用它们。例如构建lexer。

在我看来,这是会写regexp的人和不会(或几乎不会)写regexp的人的观点。 我个人认为这是一个很好的想法,例如,有效的表单输入,它在javascript警告用户,或在服务器端语言。

你可能会问为什么goto会有争议。

基本上,当你拥有这么多“显而易见”的权力时,人们倾向于在它们不是最佳选择的情况下滥用它们。例如,要求用正则表达式解析csv或XML或HTML的人的数量让我感到震惊。这不是做这项工作的合适工具。但是一些用户还是坚持使用正则表达式。

就我个人而言,我试图找到一个中庸之道——在正则表达式擅长的地方使用它们,在它们不是最优的时候避免使用它们。

请注意,正则表达式仍然可以用于解析csv、XML、HTML等。但通常不是在一个正则表达式中。

这是一个有趣的话题。 许多regexp爱好者似乎混淆了公式的简洁性和效率。最重要的是,一个需要大量思考的regexp会给它的作者带来巨大的满足感,使它立即变得合法。

但是…当性能不是问题,并且需要快速处理文本输出时(例如在Perl中),regexp非常方便。此外,虽然性能是一个问题,但人们可能不愿意尝试使用可能有bug或效率较低的自制算法来击败regexp库。

此外,还有许多原因导致regexp受到不公平的批评

regexp效率不高,因为构建顶部的regexp并不明显 有些程序员“忘记”只编译一次要多次使用的regexp(就像Java中的静态模式) 有些程序员采用试错策略——regexp的效果更差!

我不认为他们有那么大的争议。

我也认为你已经回答了你自己的问题,因为你指出到处使用它们(不是所有的语言都是常规语言2)或根本避免使用它们是多么愚蠢。作为程序员,您必须做出一个明智的决定,即正则表达式何时有助于代码,何时有害于代码。当面临这样的决定时,要记住两件重要的事情:可维护性(这意味着可读性)和可扩展性。

对于那些特别讨厌它们的人,我猜他们从来没有学会正确使用它们。我认为大多数人只要花上几个小时学习一些不错的教程,就能很快掌握并熟练使用这些语言。以下是我的建议:

http://docs.python.org/howto/regex

尽管该页讨论的是Python上下文中的正则表达式,但我发现这些信息在其他地方也非常适用。有一些东西是python特有的,但我相信它们被清楚地指出来了,而且很容易记住。

使正则表达式可维护

让以前被称为“正则表达式”的模式变得神秘的一个主要进展是Perl的/x regex标志——有时在嵌入时写入(?x)——它允许空格(换行、缩进)和注释。这大大提高了可读性和可维护性。留白区域允许认知分块,所以你可以看到哪些组和哪些组。

现代模式现在也同时支持相对编号和命名的反向引用。这意味着您不再需要计算捕获组来计算您需要$4或\7。这有助于创建可以包含在其他模式中的模式。

下面是一个相对编号的捕获组:

$dupword = qr{ \b (?: ( \w+ ) (?: \s+ \g{-1} )+ ) \b }xi;
$quoted  = qr{ ( ["'] ) $dupword  \1 }x;

下面是命名捕获的高级方法的示例:

$dupword = qr{ \b (?: (?<word> \w+ ) (?: \s+ \k<word> )+ ) \b }xi;
$quoted  = qr{ (?<quote> ["'] ) $dupword  \g{quote} }x;

语法regex

最棒的是,这些命名捕获可以放置在(?(DEFINE)…)块中,这样您就可以将声明与模式中单个命名元素的执行分离开来。这使得它们更像模式中的子例程。 这种“语法规则”的一个很好的例子可以在这个答案和这个答案中找到。这些看起来更像是语法上的声明。

后者提醒你:

确保永远不要写行噪声模式。你不需要,也不应该。没有一种编程语言可以禁止使用空白、注释、子例程或字母数字标识符。所以在你的模式中使用所有这些东西。

这一点怎么强调都不过分。当然,如果您不在您的模式中使用这些东西,您经常会创建一个噩梦。但如果你真的要使用它们,就不需要了。

下面是另一个现代语法模式的例子,用于解析RFC 5322: 使用5.10.0;

$rfc5322 = qr{

   (?(DEFINE)

     (?<address>         (?&mailbox) | (?&group))
     (?<mailbox>         (?&name_addr) | (?&addr_spec))
     (?<name_addr>       (?&display_name)? (?&angle_addr))
     (?<angle_addr>      (?&CFWS)? < (?&addr_spec) > (?&CFWS)?)
     (?<group>           (?&display_name) : (?:(?&mailbox_list) | (?&CFWS))? ; (?&CFWS)?)
     (?<display_name>    (?&phrase))
     (?<mailbox_list>    (?&mailbox) (?: , (?&mailbox))*)

     (?<addr_spec>       (?&local_part) \@ (?&domain))
     (?<local_part>      (?&dot_atom) | (?&quoted_string))
     (?<domain>          (?&dot_atom) | (?&domain_literal))
     (?<domain_literal>  (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?
                                   \] (?&CFWS)?)
     (?<dcontent>        (?&dtext) | (?&quoted_pair))
     (?<dtext>           (?&NO_WS_CTL) | [\x21-\x5a\x5e-\x7e])

     (?<atext>           (?&ALPHA) | (?&DIGIT) | [!#\$%&'*+-/=?^_`{|}~])
     (?<atom>            (?&CFWS)? (?&atext)+ (?&CFWS)?)
     (?<dot_atom>        (?&CFWS)? (?&dot_atom_text) (?&CFWS)?)
     (?<dot_atom_text>   (?&atext)+ (?: \. (?&atext)+)*)

     (?<text>            [\x01-\x09\x0b\x0c\x0e-\x7f])
     (?<quoted_pair>     \\ (?&text))

     (?<qtext>           (?&NO_WS_CTL) | [\x21\x23-\x5b\x5d-\x7e])
     (?<qcontent>        (?&qtext) | (?&quoted_pair))
     (?<quoted_string>   (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*
                          (?&FWS)? (?&DQUOTE) (?&CFWS)?)

     (?<word>            (?&atom) | (?&quoted_string))
     (?<phrase>          (?&word)+)

     # Folding white space
     (?<FWS>             (?: (?&WSP)* (?&CRLF))? (?&WSP)+)
     (?<ctext>           (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e])
     (?<ccontent>        (?&ctext) | (?&quoted_pair) | (?&comment))
     (?<comment>         \( (?: (?&FWS)? (?&ccontent))* (?&FWS)? \) )
     (?<CFWS>            (?: (?&FWS)? (?&comment))*
                         (?: (?:(?&FWS)? (?&comment)) | (?&FWS)))

     # No whitespace control
     (?<NO_WS_CTL>       [\x01-\x08\x0b\x0c\x0e-\x1f\x7f])

     (?<ALPHA>           [A-Za-z])
     (?<DIGIT>           [0-9])
     (?<CRLF>            \x0d \x0a)
     (?<DQUOTE>          ")
     (?<WSP>             [\x20\x09])
   )

   (?&address)

}x;

这不是很了不起吗?您可以采用bnf风格的语法,并将其直接转换为代码,而不会丢失其基本结构!

如果现代语法模式对您来说还不够,那么Damian Conway出色的Regexp::Grammars模块提供了更简洁的语法,还提供了高级的调试。下面是解析RFC 5322重铸为该模块模式的相同代码:

#!/usr/bin/perl

use strict;
use warnings;
use 5.010;
use Data::Dumper "Dumper";

my $rfc5322 = do {
    use Regexp::Grammars;    # ...the magic is lexically scoped
    qr{

    # Keep the big stick handy, just in case...
    # <debug:on>

    # Match this...
    <address>

    # As defined by these...
    <token: address>         <mailbox> | <group>
    <token: mailbox>         <name_addr> | <addr_spec>
    <token: name_addr>       <display_name>? <angle_addr>
    <token: angle_addr>      <CFWS>? \< <addr_spec> \> <CFWS>?
    <token: group>           <display_name> : (?:<mailbox_list> | <CFWS>)? ; <CFWS>?
    <token: display_name>    <phrase>
    <token: mailbox_list>    <[mailbox]> ** (,)

    <token: addr_spec>       <local_part> \@ <domain>
    <token: local_part>      <dot_atom> | <quoted_string>
    <token: domain>          <dot_atom> | <domain_literal>
    <token: domain_literal>  <CFWS>? \[ (?: <FWS>? <[dcontent]>)* <FWS>?

    <token: dcontent>        <dtext> | <quoted_pair>
    <token: dtext>           <.NO_WS_CTL> | [\x21-\x5a\x5e-\x7e]

    <token: atext>           <.ALPHA> | <.DIGIT> | [!#\$%&'*+-/=?^_`{|}~]
    <token: atom>            <.CFWS>? <.atext>+ <.CFWS>?
    <token: dot_atom>        <.CFWS>? <.dot_atom_text> <.CFWS>?
    <token: dot_atom>        <.CFWS>? <.dot_atom_text> <.CFWS>?
    <token: dot_atom_text>   <.atext>+ (?: \. <.atext>+)*

    <token: text>            [\x01-\x09\x0b\x0c\x0e-\x7f]
    <token: quoted_pair>     \\ <.text>

    <token: qtext>           <.NO_WS_CTL> | [\x21\x23-\x5b\x5d-\x7e]
    <token: qcontent>        <.qtext> | <.quoted_pair>
    <token: quoted_string>   <.CFWS>? <.DQUOTE> (?:<.FWS>? <.qcontent>)*
                             <.FWS>? <.DQUOTE> <.CFWS>?

    <token: word>            <.atom> | <.quoted_string>
    <token: phrase>          <.word>+

    # Folding white space
    <token: FWS>             (?: <.WSP>* <.CRLF>)? <.WSP>+
    <token: ctext>           <.NO_WS_CTL> | [\x21-\x27\x2a-\x5b\x5d-\x7e]
    <token: ccontent>        <.ctext> | <.quoted_pair> | <.comment>
    <token: comment>         \( (?: <.FWS>? <.ccontent>)* <.FWS>? \)
    <token: CFWS>            (?: <.FWS>? <.comment>)*
                             (?: (?:<.FWS>? <.comment>) | <.FWS>)

    # No whitespace control
    <token: NO_WS_CTL>       [\x01-\x08\x0b\x0c\x0e-\x1f\x7f]

    <token: ALPHA>           [A-Za-z]
    <token: DIGIT>           [0-9]
    <token: CRLF>            \x0d \x0a
    <token: DQUOTE>          "
    <token: WSP>             [\x20\x09]

    }x;

};


while (my $input = <>) {
    if ($input =~ $rfc5322) {
        say Dumper \%/;       # ...the parse tree of any successful match
                              # appears in this punctuation variable
    }
}

perlre手册中有很多好东西,但是这些对基本正则表达式设计特性的显著改进绝不仅仅局限于Perl。事实上,precrepattern手册页可能更容易阅读,而且涵盖了相同的领域。

现代模式与你在有限自动机课上所学的基本知识几乎没有任何共同之处。