为什么正则表达式如此具有争议性?

Gum*_*mbo 209 regex

在探索正则表达式(也称为RegEx-es)时,有许多人似乎将正则表达式视为圣杯.看起来如此复杂的东西 - 只是必须回答任何问题.他们倾向于认为使用正则表达式可以解决所有问题.

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

为什么正则表达式被认为是如此有争议?是否存在关于它们如何工作的普遍误解?或者可能是一个普遍的信念,正则表达式通常很慢?

Kyl*_*nin 133

我不认为人们会反对正则表达式因为它们很慢,而是因为它们难以读写,而且很难做到正确.虽然在某些情况下正则表达式为问题提供了有效,紧凑的解决方案,但有时它们会被用于更好地使用易于阅读,可维护的代码段的情况.

  • @pace,它不是_slow patterns_,它是_slow engines_.大多数(现代)正则表达式_engines_不适合复杂的模式(例如很多`|`或`.*`),因为它们使用堆栈机器和回溯.这就是为什么你必须仔细调整Perl,Java,Python,Ruby中的正则表达式...旧式正则表达式引擎(例如`grep`)首先将模式编译为DFA.之后,模式的复杂性在很大程度上是无关紧要的.我只是使用Java和grep来获得相同的文本和模式:22分钟vs 2s.这是科学:http://swtch.com/~rsc/regexp/regexp1.html (7认同)
  • 是的,与使用简单的函数相比,正则表达式极其缓慢.而且不仅速度慢,而且当面对任意(用户提供的)输入时,正则表达式引擎的性能可能完全不可预测**. (2认同)

Joe*_*ger 121

使正则表达式可维护

揭开以前被称为"正则表达式"的模式的一个重大进展是Perl的/x正则表达式标志 - 有时(?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;
Run Code Online (Sandbox Code Playgroud)

语法正则表达式

最重要的是,这些命名的捕获可以放在一个(?(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;
Run Code Online (Sandbox Code Playgroud)

这不是那么了不起 - 而且很精彩?您可以采用BNF风格的语法并将其直接转换为代码而不会失去其基本结构!

如果说现代语法结构仍然是不够的你,那么达米安康威的辉煌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
    }
}
Run Code Online (Sandbox Code Playgroud)

有一个在很多好东西perlre手册页,但在基本的正则表达式的设计特点,这些显着改善绝不单单局限于Perl的手段.实际上,pcrepattern手册可能更容易阅读,并涵盖相同的领域.

现代模式与你在有限自动机类中教授的原始事物几乎没有任何共同之处.

  • 是!是!最后,有人展示了一个很好的例子,说明x修饰符的正则表达式是多么可读.我无法相信很少有人知道它存在,更不用说实际使用它了. (9认同)
  • Python类似地有一个`re.VERBOSE`标志. (5认同)
  • 只是枪手继续说我仍然对人们为了使正则表达式可用的长度感到惊讶. (2认同)

Cha*_*ens 68

正则表达式是一个很好的工具,但人们认为"嘿,这是一个很棒的工具,我会用它做X!" 其中X是不同工具更适合的东西(通常是解析器).这是使用锤子的标准,你需要一个螺丝刀问题.

  • 说解析器使用正则表达式就像说解析器使用赋值语句.在您查看它们的使用方式之前,它没有任何意义. (62认同)
  • 解析器更好时使用RegEx很烦人.当语言的标准字符串查找或替换函数可用时(通常以线性时间)使用RegEx是不可原谅的. (24认同)
  • 请记住,大多数解析器 - 太阳能分析仪 - 仍然使用正则表达式来解析它们的东西:-) (4认同)
  • [词法分析器](http://en.wikipedia.org/wiki/Lexical_analysis)可能确实使用正则表达式.它们也称为标记化器,但它们不是[语法分析器](http://en.wikipedia.org/wiki/Parsing)(或解析器).要读取足够复杂的字符串,应使用标记生成器将字符串作为标记读取(可能使用正则表达式,可能不是,取决于标记生成器).然后应将这些标记传递给解析器,解析器将使用语法规则处理它们,而语法规则绝对不是正则表达式. (4认同)

Bar*_*own 52

几乎每个我认识的人经常使用正则表达式(双关语)来自Unix-ish背景,他们使用将RE作为一流编程结构的工具,如grep,sed,awk和Perl.由于使用正则表达式几乎没有语法上的开销,因此当他们这样做时,他们的工作效率会提高.

相比之下,使用RE是外部库的语言的程序员往往不会考虑正则表达式可以带到表中的内容.程序员的"时间成本"是如此之高,以至于a)REs从未作为他们培训的一部分出现,或者b)他们没有在RE方面"思考"并且更愿意依赖于更熟悉的模式.

  • 是的,我从来没有原谅Python使用库使正则表达式语法冗长.我认为纯度高于理智. (11认同)
  • 我来自unix背景,使用sed,awk和perl加载,当然还有大量的grepping,但是知道当我使用正则表达式时,它是一个只写的hack,我讨厌维护.这对于s​​hell脚本/一次性来说是好事,但对于实际工作,对于任何不仅仅是抓取一些数据来保存的东西,我现在使用一个具有清晰语法的正确的tokenizer/lexer/parser.我喜欢的所有/任何,干净+可以自我优化.我已经学到了很多困难,而且多年来,一开始就有一点自律意味着以后会减少努力.正则表达式是键盘上的一个时刻,皱眉的一生. (7认同)

Bil*_*win 44

正则表达式允许您以紧凑的方式编写自定义有限状态机(FSM),以处理输入字符串.使用正则表达式很难有至少两个原因:

  • 老派软件开发涉及很多规划,纸模型和仔细思考.正则表达式非常适合这个模型,因为正确地编写一个有效的表达式需要大量的盯着它,可视化FSM的路径.

    现代软件开发人员更愿意敲定代码,并使用调试器逐步执行,以查看代码是否正确.正则表达式不能很好地支持这种工作方式.正则表达式的一个"运行"实际上是原子操作.很难在调试器中观察逐步执行.

  • 编写一个偶然接受比你想要的更多输入的正则表达式太容易了.正则表达式的值实际上并不匹配有效输入,而是无法匹配无效输入.对正则表达式进行"否定测试"的技术不是很先进,或者至少没有广泛使用.

    这使得正则表达式难以阅读.仅仅通过查看正则表达式,需要大量注意力来可视化应该被拒绝的所有可能的输入,但是被错误地接受.曾经尝试调试其他人的正则表达式代码?

如果今天软件开发人员对使用正则表达式有抵触,我认为这主要是由于这两个因素.

  • perl -Mre = debug -e"q [aabbcc] =〜/ ab*[cd] /" (15认同)
  • 我不认为在没有想到飞行意大利面怪物的情况下我能看到"FSM"的首字母缩略词. (15认同)
  • 有很好的工具来调试regexp:http://www.regexbuddy.com/ (4认同)
  • @Shabbyrobe:我不是故意冒犯.如果您愿意,可以使用确定性有限自动机(DFA). (4认同)

Jas*_*ers 37

人们倾向于认为正则表达很难; 但那是因为他们错了.编写复杂的单行,没有任何注释,缩进或命名捕获.(你不要在一行中填写复杂的SQL表达式,没有注释,缩进或别名,不是吗?).是的,对很多人来说,他们没有意义.

但是,如果你的工作有什么做解析文本(大致任何Web应用程序在那里......),你不知道正则表达式,你吮吸你的工作,你是在浪费自己的时间和你的雇主.有很好的资源可以教你一些你需要知道的关于它们的信息,等等.

  • 例如,@ Rado:Perl具有正则表达式的`x`修饰符,可以忽略空格.这允许您将正则表达式放在几行并添加注释. (14认同)
  • 同样,Python有`re.X`又叫're.VERBOSE`. (9认同)
  • 好吧..区别在于多个空格在正则表达式中有意义,在其他语言中它们没有,这就是为什么它们通常是一个衬里(有时包裹到多行:) (2认同)
  • 同样在tcl中的`x`修饰符.我认为这是非常标准的,因为与其他语言不同,tcl不使用PCRE. (2认同)
  • @AndrewC这是这篇文章可能得到的最粗略的误解之一. (2认同)
  • @AndrewC你评论的最后一部分几乎是重复我帖子的第一段所说的内容,剩下的就是重复默认的论点,每个人都听过一百万次.我并没有暗示任何事情,知道如果你的工作需要任何类型的文本处理,如何,什么以及何时使用正则表达式是一个基础.如果您使用或不使用它们,很好.但是如果不了解它们,你就无法完成这项任务.仅供参考,我没有明确说过的内容是故意遗漏的. (2认同)

dkr*_*etz 29

因为它们缺乏普遍接受的IDE中最流行的学习工具:没有正则表达式向导.甚至没有Autocompletion.你必须自己编写所有的东西.

  • 世界上你会如何自动完成正则表达式? (22认同)
  • 然后你使用错误的IDE ...甚至我的文本编辑器提供正则表达式提示. (3认同)
  • EditPad Pro在搜索框中为正则表达式提供了语法高亮显示,但我发现它比有用更烦人,并且保持关闭状态.但我很感激,当我有无与伦比的括号时让我知道; 特别是圆括号可以成为一个可以追踪的熊. (3认同)
  • @AmbroseChapel - 我讨论的时间晚了几年.但我在http://regexhero.net/tester/创建了一个自动完成机制.它由圆形`()`,方形`[]`或卷曲`{}括号内的常见结构启动.它也可以起到反斜杠的作用. (2认同)

Ant*_*ony 16

" 正则表达式:现在你有两个问题 "是杰夫阿特伍德关于此事的一篇很棒的文章.基本上,正则表达式"很难"!他们可以创造新的问题.然而,它们是有效的.


all*_*ode 16

我不认为他们是那么有争议.

我也认为你已经回答了自己的问题,因为你指出在任何地方使用它们是多么愚蠢(不是所有东西都是常规语言 2)或者完全避免使用它们.作为程序员,您必须明智地决定正则表达式何时会帮助代码或者对代码造成伤害.面对这样的决定时,要记住的两个重要事项是可维护性(这意味着可读性)和可扩展性.

对于那些特别厌恶他们的人,我的猜测是他们从未学过如何正确使用它们.我认为大多数只花几个小时使用体面教程的人会把它们搞清楚,并且很快就会流利.这是我建议从哪里入手:

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

虽然该页面讨论了Python上下文中的正则表达式,但我发现这些信息在其他地方非常适用.有一些特定于Python的东西,但我相信它们清楚地被注意到,并且易于记忆.

  • 该页面似乎已移至http://docs.python.org/howto/regex (2认同)

Cur*_*Dog 11

正则表达式是将算术运算符与数字串联起来,我不认为它们是有争议的.我认为即使是像我这样相当苛刻的OO活动家(他们倾向于选择其他物品而不是弦乐)也很难拒绝他们.


Sva*_*nte 7

问题是正则表达式可能是如此强大,以至于你可以用它们来做你应该使用不同的东西.

一个优秀的程序员应该知道在哪里使用它们,哪里不知道.典型的例子是解析非常规语言(请参阅确定语言是否规则).

如果你首先将自己局限于真正的正则表达式(没有扩展名),我认为你不会出错.一些扩展可以让你的生活更轻松,但如果你发现一些难以表达的真正的正则表达式,这很可能表明正则表达式不是正确的工具.


Amb*_*pel 6

我认为“有争议”这个词并不合适。

但我见过很多例子,人们说“我需要什么正则表达式来进行这样那样的字符串操作?” 这是 XY 问题。

换句话说,他们一开始就假设正则表达式就是他们所需要的,但他们最好使用 split(),一种类似于 Perl 的 tr/// 的翻译,其中字符被一个替换为另一个,或者只是一个索引()。


Tan*_*lus 5

你几乎可以问为什么goto是有争议的.

基本上,当你获得如此"明显"的力量时,人们很容易滥用它们,因为它们不是最好的选择.例如,要求在正则表达式中解析CSV或XML或HTML的人数让我震惊.这是工作的错误工具.但是有些用户仍坚持使用正则表达式.

就个人而言,我试图找到快乐的媒介 - 使用正则表达式来获取它们的优点,并在它们不是最佳时避免使用它们.

请注意,正则表达式仍可用于解析CSV,XML,HTML等.但通常不在单个正则表达式中.