5
第        章
正则表达式实用技巧
Practical Regex Techniques
现在我们已经掌握了编写正则表达式所需的基本知识,我希望在更复杂的环境中应用这些知识来处理更复杂的问题。每个正则表达式都必须在下面两个方面求得平衡:准确匹配期望匹配的内容,忽略不期望匹配的字符。我们已经看过许多例子都说明,如果应用得当,匹配优先非常有用,但如果不够小心,也可能带来麻烦,在本章我们还将看到许多例子。
NFA引擎还需要平衡另外一个因素:效率,这也是下一章的主题。设计糟糕的正则表达式——即使可以认为没犯错误——也足以让引擎瘫痪。
本章出现的主要是各种实例,我会带领读者循着我的思路去解决各种问题。某些例子或许对读者并没有现实价值,但我仍然推荐读者阅读这些实例。
例如,即使你的工作不涉及HTML,我仍推荐你从处理HTML的实例中吸取知识。原因在于,
编写巧妙的正则表达式不仅仅是一种手艺(skill)——而且还是一种艺术(art)。它的教授和学习,不是依靠罗列规则,而是依靠经验,所以,我用这些例子告诉读者,自己在过去的若干年从经验中获得的深刻启示。
当然,读者仍然需要自己掌握这些知识,但是研究本章的例子是个好的起点。

正则表达式的平衡法则
Regex Balancing Act
好的正则表达式必须在这些方面求得平衡:
只匹配期望的文本,排除不期望的文本。
必须易于控制和理解。
如果使用NFA引擎,必须保证效率(如果能够匹配,必须很快地返回匹配结果,如果不能匹配,应该在尽可能短的时间内报告匹配失败)。
这些方面常常是与具体文本相关的。如果我只使用命令行,只需要快速地grep某些东西,可能不会过分关心匹配的准确性,通常也不会花太多精力来调校。我不在乎多花点时间来手工排查,因为我能够迅速地在输出中到自己需要的内容。但是,如果处理重要的程序,就需要花费时间精力来保证正确性:如果需要,正则表达式也可能很复杂。这些因素都需要权衡。
即使使用同样的程序,效率也是与具体文本相关的。如果是NFA,用^-(display| geometry|cemap|…|quick24|random|raw)$之类长长的正则表达式来检验命令行参数的效率就很低,因为多选分支过多,但如果它只用于检验命令行参数(可能只是在程序开始的时候运行若干次),即使所需时间比正常的长100倍也不要紧,因为这时候效率并不是问题。但是,如果要逐行检查很大的文件,低效率的程序运行起来会让你痛苦不堪。
若干简单的例子
A Few Short Examples
匹配连续行(续前)
Continuing with Continuation Lines
继续前一章中匹配连续行的例子(178),我们发现(在传统型NFA中使用^\w+=.*(\\\n. *)*并不能匹配下面的两行文本:
SRC=array.c builtin.c eval.c field.c gawkmisc.c io.c main.c\
          missing.c msg.c node.c re.c version.c
问题在于,第一个.*一直匹配到反斜线之后,这样(\\\n.*)*就不能按照预期匹配反斜线了。所以,本章出现的第一条经验就是:如果不需要点号匹配反斜线,就应该在正则表达式中做这样的规定。我们可以把每个点号替换成[^\n\\](请注意,\n包含在排除性字符组中。你应该记得,原来的正则表达式的假设之一就是,点号不会匹配换行符,我们也不希望它的替代品能够匹配换行符正则化的具体做法119页)。

于是,我们得到:
^\w+=[^\n\\]*(\\\n[^\n\\]*)*
它确实能够匹配连续行,但因此也产生了一个新的问题:这样反斜线就不能出现在一行的非结尾位置。如果需要匹配的文本中包含其他的反斜线,这个正则表达式就会出问题。现在我们假设它会包含,所以需要继续改进正则表达式。
迄今为止,我们的思路都是,“匹配一行,如果还有连续行,就继续匹配”。现在换另一种思路,这种思路我觉得通常都会奏效:集中关注在特定时刻真正容许匹配的字符。在匹配一行文本时,我们期望匹配的要么是普通(除反斜线和换行符之外)字符,要么是反斜线与其他任何字符的结合体。在点号通配模式中,\\.能匹配反斜线加换行符的结合体。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。