上一节通过几个很微型的实例认识了可爱的正则表达式,期间也有不少网友怀疑自己的能力,说不管学了多久,但是每次遇到要写正则的时候总要调试很久或者是直接去抄人家的。我想说,编程这东西,本来就是调试时间要远大于编写时间的。所以,不应该认为自己一次就能完全编写正确,不是每次都100%一次成功的。
本节将要延续上一节的话题,讲一下神奇的分组。
上一节我们用这个语句“ 作为一个搞WEB技术的人才,你现在牛逼了,终于开始用 正则表达式了 ”讲述了很多种匹配方法,从第一种到最后一种,其实都是在慢慢简化的。最开始,我们是看到一个句子,然后用最自然的逻辑从左至右去描述它,然后生成一个正则表达式,然后慢慢的精简精简,最后诞生了超级无敌小短炮: /]*>/gi
本章我们就来解读一下 “[]” 的秘密,以及说说他的兄弟:“()”的那些事。
[]的秘密
严格上来讲,“[]”符号的使用并不能算作将要匹配的字符进行“分组”的概念,他只是一个单词选择范围界定符。也就是说,不管你在[]中放多少内容,系统都会将其中的内容看做一个一个的字符来解读,而不会将它们看成一个整体。
举例说明,在上面蓝色的例句中,你想匹配一下看有几个“a或g”,那就应该用 /[ag]/g,你看这个图片,a和g都被匹配到了:
如果我们想匹配“ag”这个组合,那就应该用/ag/g 或者 /(ag)/g :
这也就是说,在没有[]干预的情况下,正则表达式中要匹配的字符总是从左到右作为一个整体进行匹配的。那么,如果[]和()同时出现,会出现什么状况呢? 我们来看看 /[(ag)]/g
事实证明,[]比()牛逼,不管带不带(),只要最外层被[]罩着,里面的内容就会被当成字符来看,而非整体。
略懂编程的人应该知道,一个逻辑里面,会包含这样几种基本关系:与、非、或。这在正则表达式也是存在的:
与:所有没有被[]包含的、连续的字符,如,ag、(ag)就是a与g都存在并且g在a后面才为真,才会匹配。
非:^。这个符号很勤劳,它既是匹配字符串的开始的限定符,又是表示“非”的符号。比如这个小短炮 [^>] 就表示匹配所有不等于”>”的符号,因此我们在上一节中用这个来匹配标签中的各项属性,因为这些属性不可能包含“>”,这样,就用最精练的语句做到了最大限度的匹配。
或:被 | 隔开的左右字符或者被[]包含的每一个字符,他们都是“或”的关系。所以 c [abc] 跟 c [a|b|c]是等效的,都只能匹配ca或者cb或者cc
因为[]老喜欢把人家拆散了干,所以,正则表达式的缔造者们也赋予了它一些特殊的才能,下面我们来看连字符号“- ”
大家在抄别人的正则表达式的时候一定见过诸如 [0-9]、[a-z] 这样的东西吧?不得不说这个真的是让人太兴奋了!
连字符号表示匹配一些计算机认为的约定熟成的连续的东西,比如数字,字母,汉字(连续的16进制编码)。所以,我们就可以这样总结如下:
[0-9] 匹配0-9的数字。当然,我知道聪明的你会去试一下看是不是[6-8]只能匹配6、7、8三个数字。你看,多么灵活呀!当我们用[0-9]来匹配所有数字的时候,我们就可以把它简写成 \d。\d表示匹配一个数字。
[a-z] 和[A-Z]匹配一个小写或者大写字母,如果想要匹配所有英文字母,你可以用[a-zA-Z]来匹配,当然,还有更简单的办法: [A-z] 。如果只需要匹配其中一段连续字母,比如 c、d、e、f就灵活的运用 [c-f] ,就行了。
一个原则:匹配式的越简单越好,速度越快。
有正就有反,那么以下几个式子的意思你就该明白了!——
[^0-9],匹配非数字的字符,等于\D
[^a-z],匹配非小写字符
[^a-zA-Z] , 匹配非英文字符。
神奇的()
()是一个神奇的东西。在我们的固定思维中,总是认为被()括起来的东西是一个整体,在正则表达式中也是如此。除此之外,他还有着一些很好玩的特性。
说他代表一个整体,这个很好理解,我用一个例子来说明:
给出一串数字:1000200300040050000006007000080900
然后我们分别写下这样两个正则: /00{2,4}/ 和 /(00){2,4}/
然后告诉我,你看到什么了?
/00{2,4}/ 在这串数字中匹配到了以下红色字体:1000200300040050000006007000080900。他能匹配 000、0000、00000。也就是说,要先保证第一位是0,然后接下来的2个位置到4个位置都是0,都能匹配。
/(00){2,4}/ 在这串数字中匹配到了以下红色字体:1000200300040050000006007000080900。他能匹配 0000、000000、00000000。也就是说,“00”是一个打包货,2个“00”、3个“00”和4个“00”在一起的组合都是OK的。
这就是我们思维中的“分组”。
但是,正则表达式的开发者总能提供给我们一些惊喜!
回想一下我们以前写HTML的时候遇到的问题:我们想找出一个段落中所有成对的HTML标签,怎么办?能不能有一个办法能够一次性找出来,而不需要先去肉眼观察有哪些标签,然后对应的构建一些正则表达式分批查找出来呢?
就比方说这段话:“ 作为一个搞WEB技术的人才,你现在牛逼了,终于开始用 正则表达式了 ”
里面的和两个 都是成对出现的标签,我们能不能一次性的把他们都匹配出来呢?难点就在于,你怎么知道你当前匹配的是还是?如何找到他们的结束标签?
答案是,能!
只需要一句话&#xff1a; /<(\w*)[^>*\/]*>.*<\/\1>/gi
看截图&#xff1a;
下面让我们拆解分析一下&#xff1a;
/ #正则表达式开始
< #匹配一个<
(\w*) #匹配<接下来的内容&#xff0c;比如是span或者a&#xff0c;因为\w不能匹配空格&#xff0c;所以遇到空格后就停了下来&#xff0c;我们把这次匹配到的东西放在一个组里面&#xff0c;用括号括起来。正则表达式为本次的括号里面找到的内容自动分配了一个组ID&#xff0c;id&#61;1。如果有面还有被()括起来的&#xff0c;ID&#61;2、3、4、5…自动分配ID
[^>*\/]* #匹配一些非>非/的内容。非>的内容主要是该标签的一些属性&#xff0c;比如style、href等。非/的内容主要是为了防止内关闭的标签诸如和
、
等被匹配到
> #匹配一个>&#xff0c;到这里头标签&#xff08;、等&#xff09;的匹配就结束了。
.* #匹配标签中的内容
< #匹配尾标签(、等结束标签)
\/ #匹配一个/。用\来转义
\1 #这里是关键&#xff0c;匹配刚才被分配为1的那个组&#xff0c;用这个\1&#xff0c;这个就叫“反向引用”。就能获取到刚才匹配的span或者是a等头标签的文字。因为头尾标签的这部分是一样一样一样滴&#xff01;
> #匹配尾标签尾
/gi #匹配完美结束
有人说&#xff0c;你这个图一下就匹配完了&#xff0c;完全看不出到底是怎么匹配的&#xff0c;如果我要匹配没有内嵌HTML标签的那些标签该如何办呢&#xff1f;就拿这段演示来说&#xff0c;内部包含了两个标签&#xff0c;这种标签我们暂时不想匹配他&#xff0c;我们现在只想要找出这种内部没有其他标签的标签怎么办呢&#xff1f;没事&#xff0c;哥有办法——
<(\w*)[^>*\/]*>[^<]*<\/\1>
很简单&#xff0c;紫色部分就是区别&#xff0c;我们只要将匹配标签中的内容从 .* (匹配所有)改为 [^<]* (匹配非<的字符)就行了。看图&#xff1a;
大家可以举一反三&#xff0c;好好利用&#xff08;&#xff09;的分组功能&#xff0c;匹配一些诸如此类会重复用到某个相同字符或者字符串来形成配对的特殊的情况。
到现在为止&#xff0c;我们都还只讲了需要消耗位置的一些匹配&#xff0c;诸如匹配标签本身以及标签中的东西。但其实&#xff0c;我们有时候会有一些特殊的匹配需求&#xff0c;比如说&#xff0c;仅匹配标签中的东西而无需将标签本身也匹配出来&#xff0c;就像刚才那段被我们拿来演示的段落&#xff0c;我们要把里面的所有html标签过滤掉&#xff0c;但是又想保留标签中的文字&#xff0c;该如何搞&#xff1f;
下一节给大家讲讲“零宽断言”&#xff0c;我们就会知道怎么做了。