这两天一直在时不时的和neo4j图数据库打交道。它的查询语句可以使用正则表达式,有一段时间没有自己写过正则表达式了,现在处于能看懂别人写的正则表达式,但是自己写不出来,语法规则都忘了。为了方便接下来的工作,所以特地复习复习正则表达式的语法。
正则表达式简介
正则表达式是用来匹配字符串的一系列匹配符,具备简介高效的特点,在很多语言中都有支持(java、python、javascript、php等等)。在windows的cmd命令中也同样支持,例如使用命令dir j*,那么只会罗列出所有以j开头的文件和文件夹。
正则表达式基本语法
正则表达式在在不同语言的支持语法略有不同,本文采用js的进行说明。js中使用正则表达式的方法为str.match(/表达式/),即需要加两个斜杠。以下所有的代码段第一行为代码,第二行为返回结果,实验是在chrome控制台进行的。
一直认为最好的学习方式就是实际操作,理论谁都能讲一大堆,但是实际做没做出来还真不知道。一个奇葩现象就是教软件工程的老师可能并没有在软件行业待过。
普通匹配符
普通匹配符能匹配与之对应的字符,默认区分大小写。
"Hello Regx".match(/H/)
["H", index: 0, input: "Hello Regx", groups: undefined]
正则标记符
- i :不区分大小写
- g :全局匹配
- m :多行匹配(暂不管它,我用的少)
参数直接加在最后一个斜杠的后面,比如”Hello Regx”.match(/regx/==i==),可以加多个参数。
"Hello Regx".match(/regx/i)
["Regx", index: 6, input: "Hello Regx", groups: undefined]
之前是表达式一旦匹配成功,就不再向字符串后面查找了,加上g后,表示进行全局查找。最后返回的是一个数组。
"Hello Regx".match(/e/g)
(2) ["e", "e"]
多匹配符
- \d :匹配数字,即0~9
- \w :匹配数字、字母、下划线
- . :匹配除换行的所有字符
需要注意的是,上面所有的匹配符都只能匹配一个字符。
"Hello 2018".match(/\d/g)
// 使用\d,匹配字符串中的所有数字
(4) ["2", "0", "1", "8"]
"Hello 2018".match(/\w/g)
// 使用\w,匹配所有的数字和字母,需要注意没有匹配到空格
(9) ["H", "e", "l", "l", "o", "2", "0", "1", "8"]
"Hello 2018".match(/./g)
// 使用.,匹配所有字符,包括空格
(10) ["H", "e", "l", "l", "o", " ", "2", "0", "1", "8"]
"Hello 2018".match(/\d\w./g)
// 分析一下这个为什么匹配到的是201,
// 首先\d找到第一个数字2,匹配成功,紧接着\w匹配到0,然后.匹配到1
// 整个正则表达式匹配成功,返回201
["201"]
"Hello 20\n18".match(/\d\w./g)
// 这里匹配不成功,因为.不能匹配换行符,所以返回null
null
"Hello 2018".match(/\w.\d/g)
// 首先看这个正则式,\w.\d,它要求最后一个字符是数字
// \w.能一直匹配到空格,但是因为得满足\d,所以第一个匹配成功的是0 2
// 因为是全局匹配,所以会接着匹配后面的018,也匹配成功
(2) ["o 2", "018"]
自定义匹配符
比如中国的手机号都是以1开头,第二位只能是3、4、5、7、8,第3位只要是数字就行。如何匹配这样的字符串?
- [] :匹配[]中的任意一个字符
"152".match(/1[34578]\d/)
// 第二个字符可以选择中括号中的任意一个
["152", index: 0, input: "152", groups: undefined]
如果在[]添加了^,代表取反。即[\^]表示除了中括号中的字符都满足。
"152".match(/1[^34578]\d/)
null
"1a2".match(/1[^34578]\d/)
// 只要不是[]中的字符,都满足,包括回车符
["1a2", index: 0, input: "1a2", groups: undefined]
修饰匹配次数
我们的手机号有11位,除了前2位有要求,其他9位度没有要求,那么是不是正则表达式就应该这样写呢?
1[^34578]\d\d\d\d\d\d\d\d\d
很明显,这样写太麻烦,肯定有更好的方式,这里就可以修饰一下匹配次数啦。
- ? :最多出现1次
- + :至少出现1次
- * :出现任意次数
- {} :分下面四种情况
- {n}代表前面的匹配符出现n次
- {n, m}出现次数在n~m之间
- {n, }至少出现n次
- {, m}最多出现m次
例子很简单,一看就懂,不浪费时间。
"15284750845".match(/1[34578]\d{9}/)
["15284750845", index: 0, input: "15284750845", groups: undefined]
"15".match(/1[34578]\d?/)
["15", index: 0, input: "15", groups: undefined]
"152".match(/1[34578]\d?/)
["152", index: 0, input: "152", groups: undefined]
"152".match(/1[34578]\d+/)
["152", index: 0, input: "152", groups: undefined]
"15".match(/1[34578]\d+/)
null
完整匹配
按照上面的写法会出现下面的问题。
"ya15284750845".match(/1[34578]\d{9}/)
// 不是电话号码,也能匹配成功,需要进一步改进
["15284750845", index: 2, input: "ya15284750845", groups: undefined]
- ^ :在[]中代表取反,但在外面代表从开始匹配
"ya15284750845".match(/^1[34578]\d{9}/)
// 现在就能从一开始匹配而且还得符合正则式才算匹配成功
null
// 但是依旧会出现下面的问题
"1528475084523255".match(/^1[34578]\d{9}/)
// 不是电话号码也能匹配成功,还要改进
["15284750845", index: 0, input: "1528475084523255", groups: undefined]
- $ :代表持续匹配到结束
"1528475084523255".match(/^1[34578]\d{9}$/)
// 现在就能保证正确了,有^表示从开始匹配;
// 有$表示持续匹配到结束,即完全匹配
null
/*
需要注意的是,一个字符串从开始匹配和从结束匹配都没问题,
不代表整个字符串就没问题,比如15284750845-15284750845
这个字符串从开始和从结束匹配都能成功,但实际上是错的
*/
特殊符号
到这里发现正则表达式确实很强大,仅仅几个简单的符号就能匹配字符串,但是如果我们要匹配的字符本身就是前面用到的符号怎么办呢?
- 匹配像$、^等特殊符号时,需要加转义字符\
"1.".match(/./)
//因为.能匹配除换行的所有字符,所以匹配到1
//但实际上我们想匹配.这个字符
["1", index: 0, input: "1.", groups: undefined]
"1.".match(/\./)
// 只需要加一个转义字符就可以了,其他类似
[".", index: 1, input: "1.", groups: undefined]
条件分支
比如现在想匹配图片的文件名,包括jpg、png、jpeg、gif等等,这是多个选项,所以需要像编程语言一样,应该具备条件分支结构。
- | :条件分支
- () :有两层含义
- 括号中的内容成为一个独立的整体
- 括号的内容可以进行分组,单独匹配,若不需要此功能,则(?:)
"1.jpg".match(/.+\.jpe?g|gif|png/)
// 这样就可以满足条件分支了,不过下面又出问题了
["1.jpg", index: 0, input: "1.jpg", groups: undefined]
"1.png".match(/.+\.jpe?g|gif|png/)
// 这里没有匹配到.和前面的文件名
["png", index: 2, input: "1.png", groups: undefined]
/*
其实我们想告诉它的是,.和后面的每一个条件分支的值都是一个独立的整体
但是它把.+\.jpe?g、gif、png当成了各自独立的整体
我们并不想让它这样切分,所以我们来告诉它怎么分才是正确的
*/
"1.png".match(/.+\.(jpe?g|gif|png)/)
// 现在可以匹配成功了,但是它多匹配了一个
// 因为括号的内容可以进行分组,单独匹配
(2) ["1.png", "png", index: 0, input: "1.png", groups: undefined]
// 所以最终写法如下
"1.png".match(/.+\.(?:jpe?g|gif|png)/)
["1.png", index: 0, input: "1.png", groups: undefined]
贪婪与懒惰
// 首先看一个例子
"aabab".match(/a.*b/)
["aabab", index: 0, input: "aabab", groups: undefined]
/*
上面的匹配没有什么问题,但实际上aab也是可以的
也就是aab也是符合条件的,那又是为什么呢?
*/
因为在正则表达式中,默认是贪婪模式,尽可能多的匹配,可以在修饰数量的匹配符后面添加?,则代表懒惰。
// like this (^__^)
"aabab".match(/a.*?b/)
["aab", index: 0, input: "aabab", groups: undefined]
到这里应该就差不多了,再深入的,就自我查询知识了。