-
-
最实用的RegExp(正则表达式)技巧
-
2018-5-31 21:41 3852
-
最实用的RegExp(正则表达式)技巧
我并不经常使用RegExp,一旦需要,我会使用多种方法。
注意:此博客文章使用JavaScript作为示例,但并不是只有JavaScript才可以使用这些技巧。
我们时常需要从一大段文本中提取出带引号的字符串、HTML标签或是大括号之间的内容。尽管写一些适当的分析程序会使代码更加健壮、更易于维护和可读,但我们经常选择正则表达式(或简称RegExp),因为你只需要搜索现成的RegExp并使用它就行。你在网络上找到的基于RegExp的解决方案都不是最优的,并且很难读懂它们的工作原理。
简便方法
让我们来看一下提取字符串的例子;你面临的情况可能是,需要从一段 JavaScript中,提取出带引号的字符串:
输入:console.log("Hello world!"); console.log("Hello back!"); 理想的匹配:['"Hello world!"', '"Hello back!"']
刚接触到RegExp的人可能会想是/".*"/gus完成了提取工作,让我们来仔细分析这个RegExp的含义:
/…/gus ——在JavaScript(以及许多其他语言)中RegExp以斜线分隔,后跟模式标志。g表示在同一个字符串中可能有多个匹配,我们对它们都感兴趣。u能够将模式视为Unicode,这通常来说更有意义;s允许.匹配\n。
" ——匹配一个"字符
. ——匹配任何字符
* ——重复最后一次操作符0次或多次
" ——再一次匹配一个"字符
注意:在JavaScript中,s和u都是相当新的标志,可能不是所有的浏览器都会支持。
在我们的输入字符串中运行这个RegExp会产生一个意想不到的结果: "Hello world!"); console.log("Hello back!" 这就是计算机“在技术上是正确的”的情况——这个字符串两端确实有引号,并且在这之间确有一段的任意字符,但并没有达到我们想要的效果。
我最经常看到的解决方案是人们切换到“non-greedy”模式*:
/".*?"/gus
这里的RegExp和上面那个相似,但是它却让*通配符尽量少的去匹配字符(即non-greedy,非贪婪模式),以此来得到我们期望的结果.
就个人而言,我对非贪婪模式的匹配存在信任问题,更关键的是:在console.log("Hello \"world\"!");上运行正则表达式会发生什么呢?
显然没有达到效果。
诀窍
反斜杠背叛了我们!所以现在怎么办?这就是我即将要说的诀窍了,我将我的RegExp告诉你,而且会向你展示我是如何做到的:/"([^"\\]|\\.)*"/gus
没错,我们只是用了RegExp中的一个,漂亮不?
第一个问题是,虽然/".*?"/gus能够起一点作用,但还是没能达到我们的目的。我们并不想得到双引号间的任何字符,只需要双引号。如何做到这一点呢?在RegExps中,你可以使用字符组来匹配整个字符集:
[abc]——匹配所包含的任意一个字符,如a,b,c;
[a-z]——匹配 ‘a’ 到 ‘z’ 范围内的任意小写字母字符;
[^abc]——匹配除a,b,c外的任何字符;
考虑到这一点,我们可以在没有非贪婪匹配的情况下编写我们的原始RegExp:
/"[^"]*"/gus
但是,这仍然不能解决反斜杠问题。为此,我们需要强调上面提到的问题:我们想要的是双引号而并非双引号间的任何字符,但如果是一个反斜线,我们将忽略其后的字符。 这让我们回到了神秘的原始RegExp(为分组添加了一些空间):
/" ( [^"\\] | \\. )* "/gus
(...|...) ——匹配列出的选项中的任意一个,用 | 分界(此项中只有两个选项);
[^"\]——匹配除双引号和反斜线之外的任意字符;
\. ——匹配一个反斜线和其后的任何单字符
这里的诀窍是提供两个互斥的选择方案。第一种方案不能匹配双引号。如果一个带有双引号的字符串应当匹配这个RegExp,那么第二个选择必须是与之匹配的字符串,只有在前面加上反斜杠时才会发生这种情况。
你看,这个RegExp甚至可以匹配字符串中溢出的引号!
真的是酷毙了!
奖励:HTML标签
HTML标签是非常有趣的,因为它的(无限数量的)闭合字符">"在转义之后并不包含原字符">"(而是类似>)。因此,在大多数情况下,正则表达式“/<[^>]*>/”可以完成匹配标签的作用。
直到你在属性值中使用">"字符!
输入<a href="javascript:alert('>');" target="_blank">lol</a> 理想输出: <a href="javascript:alert('>');" target="_blank">
为了处理这种情况,我们必须两次使用我们的新技巧。结束标记“ >”出现在HTML标记中的唯一方法是包含在一个字符串中。因此我们的第一个选项将接受任除结束标签和双引号外的任何内容。第二个是我们之前的字符串 RegExp:
/<([^>"]| string RegExp )*>/gus
或者将它写完整:
/<([^>"]|"([^"\\]|\\.)*")*>/gus
很难懂吧?但它确实起作用了!
部分建议
这是 Jamie Zawinski的名言为何这么出名的原因之一:
有些人在面对难题时,会想“我将使用正则表达式来解决它”,之后他们就有了两个难题。
RegExp往往很快就能造成难以维系的混乱,因此我们应当谨慎的使用它。请记住,大多数语言的语法都不能使用RegExp进行分析。但最重要的是:最好不要自作聪明,而应该用简单的方法调用来进行字符串操作。
翻译:看雪翻译小组 Logdty
校对:看雪翻译小组 StrokMitream
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法