码农要会写正则表达式

Posted 刘小绪同学

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了码农要会写正则表达式相关的知识,希望对你有一定的参考价值。

这两天一直在时不时的和neo4j图数据库打交道。它的查询语句可以使用正则表达式,有一段时间没有自己写过正则表达式了,现在处于能看懂别人写的正则表达式,但是自己写不出来,语法规则都忘了。为了方便接下来的工作,所以特地复习复习正则表达式的语法。

正则表达式简介

    正则表达式是用来匹配字符串的一系列匹配符,具备简介高效的特点,在很多语言中都有支持(java、python、javascriptphp等等)。在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]

    到这里应该就差不多了,再深入的,就自我查询知识了。

——END——


以上是关于码农要会写正则表达式的主要内容,如果未能解决你的问题,请参考以下文章

将来我一定要会写 README ...

玩好.NET高级调试,你也要会写点汇编

通过 Java 正则表达式提取 semver 版本字符串的片段

JAVAjava中实现map集合的数据存取详解三种方法。Android程序员也是要会写的

工作那些事谈谈码农与农民工区别和发展之路

text 正则表达式片段