在探索正则表达式(也称为RegEx-es)时,似乎有很多人将正则表达式视为圣杯。看起来如此复杂的东西-仅仅是任何问题的答案。他们倾向于认为使用正则表达式可以解决每个问题。
另一方面,也有许多人试图不惜一切代价避免使用正则表达式。他们试图找到一种围绕正则表达式的方法,并仅出于此目的接受其他编码,即使正则表达式将是一个更紧凑的解决方案。
为什么正则表达式这么有争议?关于它们的工作原理是否存在广泛的误解?还是可以广泛相信正则表达式通常很慢?
在探索正则表达式(也称为RegEx-es)时,似乎有很多人将正则表达式视为圣杯。看起来如此复杂的东西-仅仅是任何问题的答案。他们倾向于认为使用正则表达式可以解决每个问题。
另一方面,也有许多人试图不惜一切代价避免使用正则表达式。他们试图找到一种围绕正则表达式的方法,并仅出于此目的接受其他编码,即使正则表达式将是一个更紧凑的解决方案。
为什么正则表达式这么有争议?关于它们的工作原理是否存在广泛的误解?还是可以广泛相信正则表达式通常很慢?
Answers:
我不认为人们反对正则表达式是因为它们很慢,而是因为它们很难读写,而且很难正确。尽管在某些情况下正则表达式可以为问题提供有效,紧凑的解决方案,但有时它们会纠缠于最好使用易于阅读且可维护的代码部分的情况。
|
或.*
),因为它们使用堆栈机并回溯。这就是为什么您必须在Perl,Java,Python,Ruby中仔细调整正则表达式的原因。老式的正则表达式引擎(grep
例如在中)首先将模式编译为DFA。此后,模式的复杂性在很大程度上是无关紧要的。我只是使用Java和grep来表示相同的文本和模式:22min vs 2s。这是科学所在:swtch.com/~rsc/regexp/regexp1.html
揭开先前称为“正则表达式”的模式的神秘面纱的主要进步是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;
最好的是,可以将这些命名捕获放置在一个(?(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) | (?"ed_string))
(?<domain> (?&dot_atom) | (?&domain_literal))
(?<domain_literal> (?&CFWS)? \[ (?: (?&FWS)? (?&dcontent))* (?&FWS)?
\] (?&CFWS)?)
(?<dcontent> (?&dtext) | (?"ed_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) | (?"ed_pair))
(?<quoted_string> (?&CFWS)? (?&DQUOTE) (?:(?&FWS)? (?&qcontent))*
(?&FWS)? (?&DQUOTE) (?&CFWS)?)
(?<word> (?&atom) | (?"ed_string))
(?<phrase> (?&word)+)
# Folding white space
(?<FWS> (?: (?&WSP)* (?&CRLF))? (?&WSP)+)
(?<ctext> (?&NO_WS_CTL) | [\x21-\x27\x2a-\x5b\x5d-\x7e])
(?<ccontent> (?&ctext) | (?"ed_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的手段。实际上,pcrepattern联机帮助页可能更容易阅读,并且涵盖了相同的领域。
现代模式与您在有限自动机课程中教过的原始事物几乎没有共通之处。
/x
。它在语法上使用了(?&name)
正则表达式以及内部正则表达式子例程,这确实使它大放异彩。
re.VERBOSE
标志。
正则表达式是一个很棒的工具,但是人们认为“嘿,多么伟大的工具,我将用它来做X!” X是其他工具更适合的东西(通常是解析器)。这是在需要螺丝刀问题时使用锤子的标准配置。
split($pattern,$string)
vs- explode($delimiter,$string)
值得庆幸的是,前者已贬值,但是许多代码只在需要后者功能的情况下才使用前者。Aggreed,RegEx提供了一种简单的工具来执行某些操作,但是除非您需要正则表达式的全部功能,否则它们
我认识的几乎每个定期使用正则表达式(意在双关语)的人都来自Unix-ish背景,他们使用将RE视为一流编程结构的工具,例如grep,sed,awk和Perl。由于使用正则表达式几乎没有语法上的开销,因此使用正则表达式时,它们的生产力会大大提高。
相反,使用RE作为外部库的语言的程序员往往不考虑正则表达式可以带给表什么。程序员的“时间成本”非常高,以至于a)RE从未作为培训的一部分出现,或者b)他们不对RE进行“思考”,而是倾向于使用更熟悉的模式。
正则表达式允许您以紧凑的方式编写自定义有限状态机(FSM),以处理输入字符串。很难使用正则表达式的原因至少有两个:
老式软件开发涉及许多计划,论文模型和仔细的思考。正则表达式非常适合此模型,因为正确编写有效的表达式会涉及很多盯着它,并可视化FSM的路径。
现代软件开发人员宁愿敲定代码,而是使用调试器逐步执行,以查看代码是否正确。正则表达式不能很好地支持这种工作方式。正则表达式的一个“运行”实际上是一种原子操作。很难在调试器中观察到逐步执行。
编写一个正则表达式,意外地接受了比您预期更多的输入,这太容易了。正则表达式的值并不是真正匹配有效输入,而是不能匹配无效输入。对正则表达式执行“负测试”的技术不是很先进,或者至少没有广泛使用。
正则表达式难以理解。仅通过查看正则表达式,就需要花费大量精力来可视化应拒绝但被错误接受的所有可能的输入。是否曾经尝试调试别人的正则表达式代码?
如果今天在软件开发人员中反对使用正则表达式,我认为主要是由于这两个因素。
人们倾向于认为正则表达式很困难。但这是因为他们错误地使用了它们。编写复杂的单行代码,不加任何评论,缩进或命名捕获。(您不会在没有注释,缩进或别名的情况下一行就塞满复杂的SQL表达式,对吗?)。所以是的,对于很多人来说,他们没有道理。
但是,如果你的工作有什么做解析文本(大致任何Web应用程序在那里......),你不知道正则表达式,你吮吸你的工作,你是在浪费自己的时间和你的雇主。那里有出色的资源可以教您所有您需要知道的所有知识,以及更多。
x
表达式的修饰符,导致空白被忽略。这使您可以将正则表达式放在几行上并添加注释。
re.X
又名re.VERBOSE
。
x
,tcl中的修饰符。我相信这是相当标准的,因为tcl与其他语言不同,它不使用PCRE。
因为它们缺乏通用的IDE中最流行的学习工具,所以没有Regex向导。甚至没有自动完成功能。您必须自己编写整个代码。
()
,方括号[]
或大{}
括号内的常见构造启动的。反斜杠也可以使用。
“ 正则表达式:现在您有两个问题 ”是Jeff Atwood在此问题上的精彩文章。基本上,正则表达式是“硬”的!他们会产生新的问题。但是,它们是有效的。
我认为他们没有那么大的争议。
我还认为您已经回答了自己的问题,因为您指出了在任何地方使用它们(并非所有内容都是常规语言 2)或完全避免使用它们是多么愚蠢。您(程序员)必须对正则表达式何时对代码有所帮助或不利做出明智的决定。面对这样的决定时,要牢记的两个重要事项是可维护性(这意味着可读性)和可扩展性。
对于那些特别讨厌他们的人,我猜他们从来没有学会正确使用它们。我认为大多数只花几个小时学习一个体面的教程的人都会发现它们并很快变得流利。这是我从哪里开始的建议:
http://docs.python.org/howto/regex
尽管该页面讨论了Python上下文中的正则表达式,但我发现该信息在其他地方也非常适用。有一些特定于Python的东西,但是我相信它们已经清楚地指出并且易于记忆。
问题在于正则表达式可能功能强大,以至于您可以使用正则表达式执行某些操作,因此应使用其他功能。
一个好的程序员应该知道在哪里使用它们,而不是在哪里使用。典型示例是解析非常规语言(请参阅确定语言是否为常规语言)。
我认为,如果您一开始将自己限制为真正的正则表达式(无扩展名),那么您不会出错。某些扩展可以使您的生活更轻松一些,但是,如果您发现很难将其表达为真正的正则表达式,则很可能表明正则表达式不是正确的工具。
您几乎还可能会问为什么goto会引起争议。
基本上,当您获得如此多的“显而易见”的力量时,人们往往会在并非最佳选择的情况下滥用它们。例如,要求解析正则表达式中的CSV或XML或HTML的人数令我震惊。这是工作的错误工具。但是有些用户仍然坚持使用正则表达式。
就我个人而言,我尝试找到一种快乐的媒介-使用正则表达式来实现它们的优势,并在它们不是最佳选择时避免使用它们。
请注意,正则表达式仍可用于解析CSV,XML,HTML等。但通常不能在单个正则表达式中使用。
这是一个有趣的主题。
许多正则表达式爱好者似乎将公式的简洁性与效率混淆了。
最重要的是,需要大量思考的正则表达式对其作者产生了极大的满足感,使它立即合法化。
但是... 当性能不成问题并且您需要快速处理文本输出(例如在Perl中)时,正则表达式非常方便。同样,虽然性能是一个问题,但人们可能不希望通过使用可能有错误或效率较低的自制算法来尝试击败regexp库。
除此之外,正则表达式受到不公正批评的原因有很多,例如
我认为学习正则表达式并保持正则表达式不受欢迎的原因是,大多数开发人员都很懒惰,或者大多数开发人员都依赖外部库来为他们进行解析...他们依靠google寻求答案,甚至在论坛中要求他们问题的完整代码。但是,当实施或修改/维护正则表达式时,它们只会失败。
有一种流行的说法:“朋友不要让朋友使用正则表达式来解析HTML”
但是就我而言,我已经使用Regex制作了完整的HTML解析器,我发现正则表达式在解析html字符串时在速度和内存方面都更好(如果您有一个想法,您将要实现什么:))
正则表达式对于包括我自己在内的许多人都是一个严重的谜。它的效果很好,但是就像看一个数学方程式一样。我很高兴地向您报告,尽管有人终于在http://regexlib.com/上创建了各种正则表达式函数的合并位置。现在,如果Microsoft仅创建一个正则表达式类,它将自动执行许多常见的工作,例如消除字母或过滤日期。
我发现正则表达式有时非常重要。当我需要进行一些“模糊”搜索时,也许会替换掉。数据何时可能变化并具有一定的随机性。但是,当我需要进行简单的搜索和替换或检查字符串时,我不使用正则表达式。尽管我认识很多这样做的人,但他们将它用于所有工作。那就是争议。
如果您想在墙上钉钉子,请不要使用锤子。是的,它可以工作,但是当您拿起锤子时,我可以在墙上放20个大头钉了。
正则表达式应用于其设计目的,无庸置疑。
尽管我认为正则表达式是必不可少的工具,但最令人讨厌的是它们有不同的实现。语法,修饰符和“特别是”“贪婪”之间的细微差别可能会使事情变得非常混乱,需要反复试验,有时还会产生令人困惑的错误。
我认为这是程序员中鲜为人知的技术。因此,它没有被广泛接受。而且,如果您有非技术经理来审查您的代码或审查您的工作,则正则表达式非常不好。您将花费数小时编写一个完美的正则表达式,并且由于认为他/她已经编写了很少的代码行,因此您在模块上不会得到多少分。另外,正如在其他地方所说,读取正则表达式是非常困难的任务。
正则表达式的最佳有效和正常用法是用于电子邮件地址格式验证。
这是一个很好的应用。
在TextPad中,我无数次使用正则表达式作为一次性消息来处理平面文件,创建csv文件,创建SQL插入语句之类的事情。
编写良好的正则表达式不应太慢。通常,其他选择(例如,大量调用“替换”)的速度要慢得多。一口气也可以这样做。
在许多情况下,都需要正则表达式,而别无其他。
用无害字符替换特殊的非打印字符是另一种很好的用法。
我当然可以想象有些代码库过度使用了正则表达式,从而损害了可维护性。我自己从未见过。实际上,代码审查员避开了我对正则表达式的使用不足。