在探索正则表达式(也称为正则表达式)时,有许多人似乎将正则表达式视为圣杯。看起来如此复杂的东西——一定是任何问题的答案。他们倾向于认为每个问题都可以用正则表达式解决。
另一方面,也有很多人不惜一切代价试图避免正则表达式。他们试图找到一种绕过正则表达式的方法,并仅仅为了它而接受额外的编码,即使正则表达式将是一个更紧凑的解决方案。
为什么正则表达式被认为如此有争议?关于它们的工作原理是否存在广泛的误解?或者正则表达式通常比较慢是一种普遍的看法?
在探索正则表达式(也称为正则表达式)时,有许多人似乎将正则表达式视为圣杯。看起来如此复杂的东西——一定是任何问题的答案。他们倾向于认为每个问题都可以用正则表达式解决。
另一方面,也有很多人不惜一切代价试图避免正则表达式。他们试图找到一种绕过正则表达式的方法,并仅仅为了它而接受额外的编码,即使正则表达式将是一个更紧凑的解决方案。
为什么正则表达式被认为如此有争议?关于它们的工作原理是否存在广泛的误解?或者正则表达式通常比较慢是一种普遍的看法?
当前回答
我发现正则表达式有时是无价的。当我需要做一些“模糊”搜索时,可能会替换。当数据可能变化,具有一定的随机性时。 然而,当我需要做一个简单的搜索和替换,或检查字符串,我不使用正则表达式。尽管我知道很多人这样做,但他们什么都用它。这就是争议所在。
如果你想在墙上钉钉子,不要用锤子。是的,它会起作用,但等你拿到锤子,我可以在墙上钉20个钉子。
正则表达式应该用于它们设计的目的,而不是别的。
其他回答
正则表达式允许您以紧凑的方式编写自定义有限状态机(FSM)来处理输入字符串。为什么使用正则表达式很难,至少有两个原因:
Old-school software development involves a lot of planning, paper models, and careful thought. Regular expressions fit into this model very well, because to write an effective expression properly involves a lot of staring at it, visualizing the paths of the FSM. Modern software developers would much rather hammer out code, and use a debugger to step through execution, to see if the code is correct. Regular expressions do not support this working style very well. One "run" of a regular expression is effectively an atomic operation. It's hard to observe stepwise execution in a debugger. It's too easy to write a regular expression that accidentally accepts more input than you intend. The value of a regular expression isn't really to match valid input, it's to fail to match invalid input. Techniques to do "negative tests" for regular expressions are not very advanced, or at least not widely used. This goes to the point of regular expressions being hard to read. Just by looking at a regular expression, it takes a lot of concentration to visualize all possible inputs that should be rejected, but are mistakenly accepted. Ever try to debug someone else's regular expression code?
如果现在软件开发人员对使用正则表达式有抵触情绪,我认为主要是由于这两个因素。
使正则表达式可维护
让以前被称为“正则表达式”的模式变得神秘的一个主要进展是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) | (?"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。事实上,precrepattern手册页可能更容易阅读,而且涵盖了相同的领域。
现代模式与你在有限自动机课上所学的基本知识几乎没有任何共同之处。
人们倾向于认为正则表达式很难;但那是因为他们用错了。在没有任何注释、缩进或命名捕获的情况下编写复杂的一行程序。(你不会把复杂的SQL表达式塞进一行,没有注释、缩进或别名,对吧?)所以,是的,对很多人来说,它们没有意义。
然而,如果你的工作与文本解析有关(基本上所有的web应用程序都是如此),而你不懂正则表达式,那你的工作就糟透了,你在浪费自己和雇主的时间。有很好的资源可以教你关于他们的一切,你需要知道的,甚至更多。
问题是正则表达式潜在地非常强大,以至于可以使用它们做一些应该使用不同的东西来做的事情。
一个好的程序员应该知道在什么地方使用它们,在什么地方不使用。典型的例子是解析非常规语言(请参阅确定一种语言是否为常规语言)。
我认为如果一开始就限制自己使用真正的正则表达式(没有扩展),就不会出错。一些扩展可以使您的工作更简单一些,但是如果您发现一些很难用真正的正则表达式来表达的东西,这很可能表明正则表达式不是正确的工具。
在某些情况下,我认为你必须使用它们。例如构建lexer。
在我看来,这是会写regexp的人和不会(或几乎不会)写regexp的人的观点。 我个人认为这是一个很好的想法,例如,有效的表单输入,它在javascript警告用户,或在服务器端语言。