正则除查找之外,另一实用场景就是替换,通过它可以灵活修改匹配到的字符串。
什么是分组
解释什么是分组前, 先来分析一个真实的场景,提取下文中所有的生日
姓名 / 籍贯 体重 生日
于小彤 / 中国辽宁 / 63 KG / 1994-05-27
张惠妹 / 台湾台东县卑南乡 / 46 KG / 1972-08-09
沈佳妮 / 中国上海 / 51 KG / 1983-05-22
沈丹萍 / 中国南京 / 65 KG / 1960-02-19
王紫逸 / 中国香港 / 65 KG / 1986-12-15
吴健 / 中国淄博 / 68 KG / 1978-01-03
金荷娜 / 韩国 / 48 KG / 1978-02-21
张曼玉 / 中国 / 40 KG / 1964-09-20
薛佳凝 / 哈尔滨 / 45 KG / 1978-08-13
基于前面所学,相信你能快速写出匹配正则:\d{4}-\d{2}-\d{2} 。然后我需要获取每个结果中的月份,大部分人做法是在程序中使用yyyy-MM-dd 对日期进行格式化,然后在提取月份。
理解正则分组之后,大可不必这么麻烦,稍微改一下正则: (\d{4})-(\d{2})-(\d{2}) ,给日期年月日部分都加上括号,人为把结果划分成了三组。每组都有一个组号分别是1,2,3。然后在程序中可通过组号获取指定组的内容,比如获取月份就是第2组。
如何在程序中提取分组,请查阅各语言的API,以下列举了java与javascript提取组方式.
// java
public static void main(String[] args) {
Pattern pattern=Pattern.compile("(\d{4})-(\d{2})-(\d{2})");
Matcher matcher = pattern.matcher("小丽生日是2011-11-23");
if (matcher.find()) {
String month = matcher.group(2);// 获取第2组中的月份
System.out.println("月份是:"+month);
}
}
// 输出如下结果:
"月份是:11"
// javascript
"小丽生日是2011-11-23".match(/(\d{4})-(\d{2})-(\d{2})/);
// 得出结果如下:
['2011-11-23', '2011', '11', '23']
定义
分组指将匹配的内容,使用( )划分成多个组块,分好的组可用于提取、反向引用以及替换操作。
你可能会有疑问,()不是用作子表达式么?子表达与分组是共存的,但侧重点不一样:子表达式针对正则的匹配逻辑,分组针对匹配的内容。
接下来学习分组中的反向引用与替换,很实用的功能,必须要掌握。
\1反向引用
在表达式中引用之前的分组,即反向引用。来看一个真实的场景:要提取以下html 中所有合法标题,
<h1>一级标题</h1>
<h2>二级标题</h2>
<h2>三级标题</h3>
<h4>四级标题</h4>
<h5>五级标题</h5>
<h6>六级标题</h6>
html中共有6级标题,标签是h[1-6],得出匹配正则<h[1-6]>.*?</h[1-6]>,正常情况下它能匹配所有标题,问题在于文中的标题三是错误的,
三级标题
,它是h2开头,结尾确是h3。如何解决呢?比较笨的方法可以这么写:<h1>.*?</h1>|
<h2>.*?</h2>|
<h3>.*?</h3>|
<h4>.*?</h4>|
<h5>.*?</h5>|
<h6>.*?</h6>
#注:实际匹配不存在换行,这么写是方便观看
使用反向引用可以更轻松解决这个问题: <(h[1-6])>.*?</\1> 步骤拆解如下:
- <(h[1-6])> 匹配开始标签,并把标签名加入分组1
- .*? 标签中间可以是任意内容。
- </\1> 匹配结束标签,标签内容通过\1引用分组1(意思是与分组1的内容一致)
练习
请匹配正确的html标题
<h1>一级标题</h1>
<h2>二级标题</h2>
<h2>三级标题</h3>
<h4>四级标题</h4>
<h5>五级标题</h5>
<h6>六级标题</ha>
/<(h[1-6])>.*?</\1>/gm
定义
反向引用指通过\组号引用之前的分组,可以把分组理解成一个变量,在通过变量名(组号)引用。它不能引用之后的内容,固作称作反向引用,比如这样正则是错误的:<\1>.*?</(h[1-6])>
引用替换
我们经常使正则然后进行替换操作,比如匹配文中所有空行,然后替换成空(删除它),又或者是找出错误的单词,替换成正确的。其实正则有更为强大的替换能力,比如把普通文中所有http链接,一键替换成标签,更厉害一点还可以把文本替换成 insert sql 语句。
但是在学习这些复杂替换操作前,先学习一些简单的。请找出下文中所有日期,并统一替换成yyyy-MM-dd格式:
姓名 / 籍贯 体重 生日
于小彤 / 中国辽宁 / 63 KG / 1994-05-27
张惠妹 / 台湾台东县卑南乡 / 46 KG / 1972/08/09
沈佳妮 / 中国上海 / 51 KG / 1983.05.22
沈丹萍 / 中国南京 / 65 KG / 1960/02/19
王紫逸 / 中国香港 / 65 KG / 1986/12/15
吴健 / 中国淄博 / 68 KG / 1978.01.03
金荷娜 / 韩国 / 48 KG / 1978/02/21
张曼玉 / 中国 / 40 KG / 1964/09/20
薛佳凝 / 哈尔滨 / 45 KG / 1978/08/13
实现替换需要3步:
- 写出匹配日期的正则:\d{4}[-./]\d{2}[-./]\d{2}
- 对日期年、月、日进行分组:(\d{4})-./-./
- 在替换字符中引用分组:2-$3
- 注意:替换操作只会针对被匹配的字符,未匹配部分会原封不动。来试试看:
定义:
替换操作指将正则匹配到的内容,替换成指定字符串,该字符串可通过0可以引用整个匹配的内容。比如:日期“1960/02/19”被匹配之后 1、3 分别表示年月日。请注意反向引用与替换引用的语法区别,前者是使用\组号,而替换使用$组号。
&代替
这种替换方式在各工具以及各编程语言中都支持,非常方便。以下列举了在Java及Js中的例子:
//Java
String s = "1978/08/13".replaceAll("(\d{4})[-.\/](\d{2})[-.\/](\d{2})",
"$1-$2-$3");
System.out.println(s);
// 输出结果
1978-08-13
//JavaScript
"1978/08/13 1991.28.11".replaceAll(/(\d{4})[-./](\d{2})[-./](\d{2})/g,"$1-$2-$3")
// 输出结果
1978-08-13 1991-28-11
大小写转换
操作符 | 描述 | 兼容性 |
---|---|---|
\u 单个转大写 | 转换一下个字符为大写 | |
\U 全部转大写 | 转换\U后所有字符转大写 | |
\U...\E 区间转大写 | \U与\E区间的内容转大写 | |
\l 单个转小写 | 转换一下个字符为小写 | |
\L 全部转小写 | 转换\L后所有字符转小写 | |
\L...\E 区间转小写 | \L与\U区间的内容转小写 |
具体使用方法是:在替换字符串中加入转换操作符。举例把单词首字母转成大写:
- 编写匹配正则:\w+
- 首字母转大写并替换:\u$0
- "my love" 被替换后结果就是"My Love"
分组的其它应用
关于分组还存在一些特殊情况,需要提前了解一下:
- (?<名称> )命名分组
- (?: ) 移除分组
- ( ( ) )嵌套分组
- ()+分组使用量词
(?<名称> )命名分组
默认情况下通过组号来取值,此外也可以自定义命名组,语法是(?<名称> ),然后在程序中就可以通过<>中的名称来取值。如:<(?h[1-6])>.*?</\1> 该表达式就命名了一个title的组,在js的结果中就可通过title属性取值。
let ret = "<h1>一级标题</h1>".match(/<(?<title>h[1-6])>.*?</\1>/)
consloe.log(ret)
注:数组中还包含了groups,index,input等属性
注意:这种命名组只能用于在程序中提取操作,不能进行反向引用,也不能用在替换操作中。上例在替换中如果使用 $title或在反向引用中使用 \title 都是无效的。只能通过组号\1进行引用。这也说明命名组后,组号一样有效。也正因为这种局限性所以命名组使用的很少。
(?: )移除分组
()即用于子表达式,同时也是一个分组。如果只想用作子表达式,而不想用于分组就可以使用(?: )从分组列表中移除。比如(?:\d{4})-(\d{2})-(\d{2}) 该表达式只存在两个组,月2。
你可能在想这么做的意义是什么呢?在一些复杂场景中这是有用的,用让组号变得更清晰。以下正则用于匹配http 链接:
"https://www.baid.com".match(/((https?)://)?((\w+.)?\w+.\w+)/)
// 得出结果如下:
0: "https://www.baid.com"
1: "https://"
2: "https"
3: "www.baid.com"
4: "www."
总共会有4个组,非常的乱,如果只想获取协议和域名组,就可以用(?:)把其它从组去中掉,同时子表达式的逻辑依然存在
"https://www.baid.com".match(/(?:(https?)://)?((?:\w+.)?\w+.\w+)/)
// 得出结果如下:
0: "https://www.baid.com"
1: "https"
2: "www.baid.com"
( ( ) )嵌套分组
在嵌套分组中组号是如何命名的呢?比如:生日((\d{4})-(\d{2})(\d{2})) 其组号的命名顺序是以开括号出现顺序为准。
"生日2019-09-21".match(/生日((\d{4})-(\d{2})-(\d{2}))/)
// 得出结果如下:
0: "生日2019-09-21"
1: "2019-09-21"
2: "2019"
3: "09"
4: "21"
()+分组使用量词
同一个分组如果使用了量词,该分组会代表多个值,这时通过1将得到5
本章练习
Sql语句转换
在日常开发工作中,经常有需求将文本内容导入到数据库,常见的做法是,使用数据导入工具,并按照一定格式解析文本导入。如果你需过滤掉错误行,又或者统一日期格式等自定义操作,这时就可以使用正则的替换操作来完成。把文本替换成 Insert Sql 然后在导入。
请将文本替换成 Insert Sql语句
规则说明:
- 表名:user
- 列名:name,city,weight,birthday
- 日期统一为:yyyy-MM-dd 格式
示例:insert into user (name,city,weight,birthday) values('张惠妹','台湾台东县卑南乡',46,'1972-08-09');
参考答案
- 正则:(.+?) / (.+?) / (\d{2}) KG / (\d{4}).(\d{2}).(\d{2})
- 替换:insert into user (name,city,weight,birthday) values('2',4-6');