正则表达式——献给2013的葬礼

时间2013.12.29

地点:软件大楼211

——————————————————————————————————————————

一、前言

2013年就那么可心里默数的几十个小时留下,没想默数着让它过完期待那么光明的一天,黑夜给了我黑色的眼睛,我用来寻找光明,等天明,太阳升起,若在是舞池中央最好。

这篇文章是一篇正则表达式入门级别的科普,若真的想入门,此处无门。

在材料院的设备改造项目里,我们已经用到牛逼哄哄的正则表达式,主要用于实验记录的查询,如下所示:


平时我们在文本编辑器里比如word,text等的查找与替换功能也恰是正则表达式的功劳,玩Linux的大神们应该知道grep命令,全称是Global Regular Expression Print,是一个强大的文本搜索工具,凭借正则表达式搜索文本,并把匹配行打印出来,正则表达式概念的普及正是始于此,当然这点应用只是皮毛。有些问题不深究往往被小看,有些问题深究却是死胡同,世界因为有问题才留给我们机会,不是吗?瞎闹与折腾,现在发现傻子其实最幸福,智商越高脑壳越疼,情商越高心越疼。

——————————————————————————————————————————

二、问世间正则表达式为何物,直叫人生死相许

在很多场合,我们需要查找出符合某一特定规则的字符串,这种需求有很多很多,比如Google浏览器的查找功能,在比如计算机的搜索查找功能,而正则表达式正是用于描述这一特定规则的工具。我们说某个字符串匹配某个正则表达式,即该字符串的部分满足于正则表达式给出得条件。是不是,因为这个工具,许多事情变得便捷,世界因为正则表达式而变得美丽,有阳光或许感觉到冷。


——————————————————————————————————————————

三、破门而入

我在前面说想入门,此处无门,但我说的是如果你真想入门的话此处无门。有些事就像钞票我们难以辨别真假,收到过100元假钞的同志们自我安慰下吧,学好正则表达式你的身价又涨上去了,丢100是为了明天不丢1000。

学习最好的方式是动手从例子从模仿开始,比如林平之练碧血剑谱,若只是天天在闺房里看剑谱永远都只是剑谱,编程有四境界:无意识无能力——不知道自己不知道(平民),有意识无能力——知道自己不知道(码农),有意识有能力——知道自己知道(攻城师),无意识有能力——不知道自己知道(码神)。

假设你想在你的word编辑器里查找hi的单词,可以简单地使用hi正则表达式,它由两个字符组成,前一个字符是h,后一个字符是i,但是这样做我们会把hi,himself,history,high都找出来,于是我们还可以加更多的约束,使之更严格地匹配,用 \bhi\b 可以实现,其中 \b 表示字符串的开始与结束,即词的分界,叫做元字符。这样的元字符还有很多,比如:

——————————————————————————————————————————

. (通配符,表示除换行符以外的任何字符,注意是一个字符,而不是字符串,举例 a.b——可匹配 abc a5c a6c a#c,若是ab5c ac这是不可匹配的,我们说了,只是任意一个字符对不对)

^ (匹配字符串的开头位置 举例 ^liu——可匹配liuze liuzekun

$ (匹配字符串的结尾位置 举例 $liu——可匹配 zeliu kunzeliu

结合以上两者 ^liu$ 则只能匹配以liu开始且以liu结尾的字符串了,那就是精确匹配liu本身

* (匹配零次或多次之前的部分,举例 a*b——可匹配 b ab aab aaab aaaab aaaaab

+ (匹配一次或多次之前的部分, 举例 a+b——可匹配 ab aab aaab aaaaab aaaaab 但不匹配 b ,我觉得*像是乘号,将前面部分可乘 0 次,1次,2次......都匹配,而 +像是加号,将前面部分加0次,1次,2次......都匹配,大概就是为什么 *匹配的是零次或多次前面部分,而+就是匹配一次或多次前面部分,纯属个人理解)

? (匹配零次或一次之前的部分,举例 a?b——只可匹配 b ab 这里 ?像编程语言里的双目表达式里的?,取值有而,要么true选前者,要么false选后者,只是这不涉及真假选择,但要么有要么没有大概一样的道理。

——————————————————————————————————————————

这里先总结这么几个掌握,更多在后面有总结,有了上面这些,我们来看一个复杂的正则表达式:\bHello\b.*\bWorld\b

首先 \bHello\b ——表示匹配一个精确的 hi 字符串

再有 .* ——表示零个或多个 任意一个字符(擦,绕口了)

最后 \bWorld\b ——表示匹配一个精确的World字符串

上述联合起来就是匹配 Hello.......World 的字符串的。(......表示任意个数任意字符)

再来看一个:0\d\d-\d\d\d\d\d\d\d 匹配的是诸如 0731-6633936 等电话号码,这样写看起来看烦躁,偷懒可表示如下: 0\d{2}-\d{7} 这样看起来是不是就雅观了。

——————————————————————————————————————————

四、常用元字符

现在我们已经知道好多有用的元字符了,但还远不够用啊,世界太复杂了。我把它们放在表格里也许更直观,没必要去背记,这不是要去参加闭卷考试,用得多了自然就熟了。

. (点号,12K的火眼金睛才看得清) 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符,包括制表符、空格
\d 匹配数字
\b
匹配单词的开始或结束
^
匹配字符串的开始
$
匹配字符串的结束
*
重复零次或更多次
+
重复一次或更多次
?
重复零次或一次
{n}
重复n次
{n,}
重复n次或更多次
{n,m}
重复n到m次

下面还给个反义表
\W
匹配任意不是字母,数字,下划线,汉字的字符
\S
匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B
匹配不是单词开头或结束的位置
[^x]
匹配除了x以外的任意字符
[^aeiou]
匹配除了aeiou这几个字母以外的任意字符


举例:

\ba\w*\b ——匹配步骤:1.开始处以字母a开头,2.跟任意的字母、数字、下划线或汉字字符,3.字符数的目任意,4.结束。

连起来说就是:我们想要匹配以a开始,后面紧跟数目任意的某字符这样一个字符串。

\d+ ——匹配步骤:1.数字一个,2.数目为一个或一个以上

连起来说就是:我们想要匹配某个数字的任意个数串(当然至少1个)

^\d{6,10}$ ——匹配步骤:1.一个数字字符开始,2.个数为6到12个

所以有些网上要你填写身份证号码或者电话号码或者QQ号码,当位数不对时马上就会给出提示,就是这样来得。
那么我们现在还来说说^\d{6,10}$与单纯\d{6,10}的区别:

^\d{6,10}$ 匹配的是以一个数字开始,整个字符串是6到10个数字组成的字符串,比如:12345678

而\d{6,10} 匹配的是字符串中含有6到10个连续数字组成的串即OK,比如: hu12345678nan

前者是标记有开始和结束,要求严格,后者是只要包含即可。

所以我们可知道,正则表达式所说的单词即连续个字符组成的字符串。不一定有含义,只要连续。

五、转义

和编程语言一样,元字符都有特殊的含义,如果我们要匹配元字符,这就需要转义,我们一如既往地选择\来取消这些字符的特殊含义,比如: \. \* \\ 等,这就不多描述了,业界通用,但还要记住一点,如果在编程语言中实现,我们还要转义一次,因为这些符号在编程语言里又有特殊含义,又要取消一下特殊含义,现在麻烦来了,拿 *来说:

1.先是在正则表达式里我们要转义: \*

2.然后在编程语言里为了让上面这个\是普通含义,我们给他一个\ 于是成了 \\* 然而到这一步我们还只是让它在程序里是个 *

3.为了然 *不具备特殊含义我们还要一个 \ 于是整个普通*的实现就是: \\\* 若是\的表示为: \\\\ 看得好揪心

好在C++11(别的语言我不知道)里有原始字符串变量一说,于是转义一次就OK,比如我要匹配 空格、换行符、回车符、和反斜杠,按往常要这么写: ( |\\n|\\r|\\\\) ——说明:1.(后紧跟一个空格字符,2.晓得为什么是\\n而不是\\\n吗?

在C++11里,我们可以这样写:R"~( |\n|\r|\\)~" ——漂亮吧,~是边界符,可不要,也可换成其它,小括号是必须的,里边包含原始字符串。

六、多字符匹配

说道这,我们已经能解决好多问题了,给定目标的查找都不是问题了,但还是不够用,社会太复杂了,比如你想找出某个文本里的所有元音字母:a e i o u ,办法很简单,把它们列在方括号里即可:[aeiou] ,这样我们就能匹配任何一个这样的元音字母,注意一个的含义,apple并不是它能匹配得到的,要么a 要么e 要么i等等。

也可以指定范围[0-9]匹配0到9的一个数字,同\d,同理注意只是一位的数字,来个123连续数位的无能为力。

再比如[a-zA-Z] 能匹配a到z和A到Z范围内的所有字母

再看个复杂的\(?0\d{2}[) -]?\d{8}

1. \(?说明右括号可有可无

2.然后是数字0

3.d{2}说明跟了2个数字

4.[) -]?说明)或者空格或者-可有可无

5.\d{8}说明后面是8个数字

于是乎可匹配 (010)88886666 010-888886666 010 88886666 (010-88886666(这个匹配并不是我们想要的,下面会讨论如何解决)之流的电话号码

要说明的是(和)也是元字符,所以也用\消除特殊含义,使之正常。

七、分支条件

上面匹配得到了一个不合理的匹配,用分支条件可解决这个问题,即使用 | 把不同的几部分分离开来

0\d{2}-\d{8}|0\d{3}-\d{7} 匹配: 0XX-XXXXXXXX 或者 0XXX-XXXXXXX

\d{5}-\d{4}|\d{5} 匹配: XXXXX-XXXX 或者 XXXXX

这里要注意的是分支条件的顺序,如果满足前一条件了,后面就不管了,比如这里写成\d{5}|\d{5}-\d{4} 就有问题了。

八、分组

括号()用于标记子表达式,子表达式也称捕捉组,好诡异的名字,括号括起来之后括号里的部分就是一个整体,你可视为单个字符,而括号里边我们也可以进行局部操作。比如

(\d{1,3}\.){3}\d{1,3}

1.(\d{1,3}\.) 说明是一个1到3位的数字,后面跟一个.(刚开始我一直这样理解,认为\b是一位数字,然后再重复1到3次,如此便是2,22,222之辈,后面想通了应该这样理解,{1,3}是作用于\d的,而不是作用于\d的一个实例,也就是说,展开后有1到3个\d,嗯,展开,先展开表达式,这样就好理解多了是不是)

2.(\d{1,3}\.){3} 即如果把小括号里的部分看成一个整体,那么我们在这里重复这样操作3次,比如可得到49.123.82.

3.(\d{1,3} 在前面基础上加上一个三位数。

擦,这就是传说中的IP匹配,但 256.257.455.520等不合理的IP也匹配进去了,所以我们还要继续加约束条件: ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

九、贪婪与懒惰

贪婪匹配即尽可能多的执行字符匹配,比如a.*b,意义上是匹配a开始,b结尾的字符串,当碰到aabab时,贪婪匹配会匹配完整即aabab,可不是aab(懒惰匹配),那么怎么进行懒惰匹配呢,那就是在重复符号(* + ? {...})后追加?,即a.*?b,如此为尽可能少地匹配字符重复。

——————————————————————————————————————————

十、葬礼

葬礼

那一刻,
我驻足玫园,
不等花鸟鱼闲,
只为守候你人嫣;

那一天,
我吹响陶笛天低悬,
不是幽思,
只为聆听你真言;

那一夜,
我撑灯窗檐,
不知南星疲倦,
只为云梦轻触你边缘;

那一月,
我扬起航帆,
不畏风雨,
只为予你投下心湖石岩;

那一年,
我在尘世低徊,
不寻罗绮逅风情啊,
只为来到你跟前;

那一世,
我转山转水转人间,
不问今生是何年,
只为途中与你相缘;

那一瞬,
我便相思满月弦,
不觉梦里绕魂牵,
只为你祈福安乐开心颜。
清啊轻得白溪涧,
蓝啊难得碧云天。
静默默念,
只盼湘水怡荷莲。

相关文章

jquery.validate使用攻略(表单校验) 目录 jquery.validate...
/\s+/g和/\s/g的区别 正则表达式/\s+/g...
自整理几个jquery.Validate验证正则: 1. 只能输入数字和字母...
this.optional(element)的用法 this.optional(element)是jqu...
jQuery.validate 表单动态验证 实际上jQuery.validate提供了...
自定义验证之这能输入数字(包括小数 负数 ) <script ...