锋利的正则表达式
RegExp 的一点总结
最近因为业务需要,在表单中要插入 “较为严谨的” 表单验证,勾起了我初学正则时的回忆,
当年钟爱的正则表达式现在只记得几个常用的字符搭配,本着念旧的态度,今天又简单的把正则表达式整理了一下基本用法。
正则用处无处不在: 匹配, 验证; 替换, 截取, 分析...总之一个字“方便!”
要求:其实大多数应用情况下开发人员只要记得他的一些字符搭配就足够你用,本人也是情愫上涌写的这篇随笔。
当然,如果是框架级的开发等需要严谨验证的情况还是需要去深入了解,总之玩好正则就只有一个方法:熟能生巧!
1.1. 记住元字符
-
基本元字符:
. 匹配任意的非换行字符, 常常表示通用元字符使用 [\s\S] [] 匹配一个出现在 [] 中的字符, [123456789123456789123456789], [a-z0-9A-Z] 计算机只认识 数字, 所以我们写的字符串计算机其实不是以字符串的形式存储 有一个编码系统: unicode '0' 48 '1' 49 ... '9' 57 写上: '012' , 在内存中存储: 48 49 50 需要记住的是: '0' 48 'a' 97 'A' 65 \u0030 () 1> 分组; 2> 提升优先级 | 或者 匹配 foot 或者 food foot|food 注意: | 具有最低优先级 ^(foot|food)$ 常见的用法 例如要匹配jq的属性选择器: [name] [name=value] 用正则: \[\w+=\w+\]|\[\w+\] jq中 的写法: \[\w+(=\w+|)\] (foot|food) 匹配: 'foot' 或者 'food' (foot|) 匹配: 'foot' 或者 '' food(=food|) 匹配: 'food=food' 或者 'food' \w+(=\w+|) 匹配: '字符=字符' 或 '字符' \[\w+(=\w+|)\] 匹配: '[xxx]' 或 '[xxx=vvv]' \[\s*(\w+)\s*(?:([|!^$~]?=)\s*(\w+)|)\s*\] => \[ \s* (\w+) \s* (?: ([|!^$~]?=) \s* (\w+) |) \s* \]
- 限定元字符
+ (abc)+ {1,} * {0,} ? {n} {n,} {n,m}
- 首尾元字符
*简写元字符^ 必须以 xxx 开头 表示否定: [abc] 用来匹配 一个 字符 a, 或 b, 或 c [^abc] 用来匹配一个字符, 要求不允许是 a, b, c $ 必须以 xxx 结尾 表示组引用 a(b)c
\w word \W \s space \S \d digit \D 其他: (?: ) \1
1.1.1. 基本的匹配案例
如何匹配
- 匹配任意的自然数, 就是 0, 1, 2, 3, ...
[0-9] 等价于 \d 分类讨论 -> 一位数字: \d -> 两位: [1-9]\d -> 任意位( 除了一位 ): [1-9]\d+ 合并: \d|[1-9]\d+ 可以进行演变( 用不同的方法写同一个例子 ) `[1-9]\d*|0` `[1-9][0-9]*|0` ...
-
匹配任意的整数, 0, +1, -1, +2, -2, ...
`[+-]?[1-9][0-9]*|0` `(+|-|)[1-9][0-9]*|0` ...
-
匹配任意的实数, 0.01, 0.0000123, -0.1234567
整数 + 小数部分 分类( 划归 ) ((+|-|)[1-9][0-9]*|0)(\.\d+|) 无法匹配 -0.1 无非就是不允许匹配 -0.0+ 使用正则的原则: 够用即可, 尽可能写成多步的判断, 要保证正则表达式的尽可能简洁 -0+(\.|)0* if ( !... ) { if ( ... ) } \d{17}|[\dxX] \d{11}
-
匹配邮箱
[^@]+@[^@]+ 如果想要严谨一点 邮箱分成用户名 和 域名 用户名: 可以有数字, 字母下划线组成( 还有一些特殊字符 ) [\w\-]+ 域名: xxx.com.cn.xxxx `[\w\-]+(\.[\w\-]+)+`
-
匹配 ip 地址
采用数字点分的方法描述, 而且每一个数字的取值在 0-255 之间, 都可以取到 0.0.0.0 255.255.255.255 123.124.125.126 ... `\d{1,3}(\.\d{1,3}){3}` 或者: `\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}` 利用正则实现匹配 0 到 255 的数字 分类: 一位: [0-9] 两位: 10-99 [1-9][0-9] 三位: 100-255 开始为 1 的: 100-199: 1[0-9][0-9] 开始为 2 的: 200-255: 中间为 0-4 的: 2[0-4][0-9] 中间为 5 的: 25[0-5] [0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5] IP: [0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]\.[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]\.[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]\.[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5] 练习: 如配匹配计算机中的一个文件路径全名 例如: C:\myMusics\list\myFavorite\rain.MP3 盘符: 一个字符: a-z, 简单的写 . 如果想要严格处理 [a-z] 冒号和斜线: :\\ 路径(文件夹的名字): .+\\ 如果想要精确: ([^|:\?\*><\\\/\"]+\\)* 文件名: .+\..+ 如果想要精确一点: [^|:\?\*><\\\/\"]+\.[^|:\?\*><\\\/\"]+ 组合起来: .:.*\\.+\..+
注意: 正则表达式的贪婪模式
C:\myMusics\list\myFavorite\rain.MP3 .:(.*)\\.+\..+ .:.*\\.+.+ 如果一个正则表达式中有多个 .+ 或 .* 的结构, 如果造成了匹配的歧义, 满足一个规则. 即贪婪模式 从左往右 匹配个数的能力是左强又弱, 而且只有两个级别, 要么强, 要么弱
例如: `` var s = '1234567890'; // 1 2 3 var r = /(.+)(.+)(.+)/; var res = r.exec( s ); `` 例如匹配 div 标签 `var r = /<div.*?>.*<\/div>/;` 如果不希望有贪婪模式在 限定元字符( +, * 等 )后写上 ?
1.1.2. 字符串提取( 解析 )
-
.exec 方法
利用正则表达式, 去处理字符串, 并将字符串中符合正则表达式的数据取出来
var s = '1234567'; var r = /\d/; var m = r.exec( s ); // m 是 match 的简写, 表示捕获
// 如果匹配上了, 就返回数组, 第 0 项 就是匹配到的结果, 如果正则表达式中有 分组 // 分组都是从 1 开始的编号, 那么对应的 第 n 组匹配到的结果就在这个数组的第 n 项 // 注意: 是真数组 // 如果没有匹配到 则返回 null 分组: 就是 () 从左往右数 (, 第一个开始编号, 从 1 开始编号, 此时编号完成以后, 对应圆括号组成的就是对应的组 // 1 2 34 5 例如:` r = /(\d)(\d((\d)\d))(\d)/;`
- 做坏事的小例子:
想要做一个推广, 在论坛中发布我有XXX的视频, 如果想要的留下邮箱
如果有人留下的是 QQ 号, 将其拉到一个群里
注意: exec 匹配提取, 只能匹配提取第一个被捕获的数据
如果需要匹配字符串中所有符合要求的字符串
1) 开启全局模式, 在正则后面写上一个 g, 或在构造函数的 第二个参数中提供 'g'
创建正则:
-> 字面量: /.+\\.+/g
-> 构造函数: new RegExp( '.+\\\\.+', 'g' )
2) 开启全局模式后, 使用 正则表达式对象 调用 exec 方法一次, 就会从 0 位置或上一次结束的位置开始
查找下一个匹配结果, 如果查找到结果则返回对应数组, 如果没有查找到( 即, 找完了 )就会返回 null.
如果还继续调用 exec, 那么就从头开始再查一次.
例如:
var s = '123';
var r = /\d/g;
r.exec( s ); // [ '1' ]
r.exec( s ); // [ '2' ]
r.exec( s ); // [ '3' ]
r.exec( s ); // null
r.exec( s ); // [ '1' ]
...
一般要取到所有的数据, 可以使用下面的代码结构
var m; while ( ( m = r.exec( s ) ) != null ) { // 此时 m 就是找到的结果 }
1.1.3. 字符串替换
-
.replace( 正则表达式, 需要替换的字符串 )
-
将一个字符串中所有的 - 换成一个连线
var s = 'a-----------------------------------b----------------------c----d'; // => a-b-c-d var res = s.replace( /-+/g, '-' ); 应用背景 /\*.*?([\r\n]+.*?)*\*/ 换行: \r\n 因此 \r 是回车的意思 \n 是换行的意思 平时在处理换行的时候要注意: 在 有些时候是 \r\n, 有些时候是 \n 在 类 Unix 操作系统中( unix, linux, mac ) 换行都是 \n 在 windows 中是 换行是 \r\n
-
在替换中还可以使用组
var s = '2012-3-4'; // 在不同的系统中想要显示成 // 2012年3月4日 // 3月4日2012年 // 4/3/2012 // ... var r = /(\d+)\-(\d+)\-(\d+)/; var res = s.replace( r, '$1年$2月$3日' )
-
替换的时候, 第二参数可以是函数, 函数的参数为, 匹配到的数组的散列值
利用函数的返回值替换匹配到的结果
var s = '2012-3-4'; var r = /(\d+)\-(\d+)\-(\d+)/; s.replace( r, function ( match, g1, g2, g3 ) { return g1 + '年' + g2 + '月' + g3 + '日'; });
例: 电话号码的加密
var list = [ '12345678901', '12345678912', '12345678923', '12345678934', '12345678945', '12345678956' ]; var res = list.map( function ( v ) { // 电话号码的前4位 // 电话号码的后 2 位 return v.replace( /(\d{4})(\d+)(\d{2})/, function ( a, b, c, d ) { // a 就是匹配到的 电话号码 // b 就是匹配到的 电话号码的前 4 位 //c 就是匹配到的电话号码的 中间 5 位 //d 就是匹配到的电话号码的 后 2 位 return b + '***********'.slice( 0, c.length ) + d; // return b + '*****' + d; }); });
1.1.4. 不捕获模式
(?:) 关闭元括号的搜索能力,而只是用它来做分组.
if(/(?:a)(b|c)/){ print "Fred wants a $1\n" ; }
例子中第一个圆括弧只是用来分组,而不会占用捕获变量,所以$1的内容只能是sb或者c,而永远不可能是a。
-