美文网首页Linux
正则表达式

正则表达式

作者: 书上得来终觉浅 | 来源:发表于2019-04-16 13:09 被阅读3次

正则表达式(Regular Expression,简称regex)作为一种工具,它擅长的领域是文本处理。最基本的用途是查找(search/match)和替换(replace)特定的文本内容,在此之上可以延伸到:

  • 验证(validation)内容是否符合指定的规则

  • 提取(Extract)指定的内容

1 JavaScript中正则表达式

JavaScript中为正则表达式提供了专门的RegExp类型,可以使用new RegExp()实例化一个正则表达式,不过通常我们使用如下的语法创建一个正则表达式。

let expression = /pattern/flags;

/pattern/表示正则表达式的匹配模式,flags表示该表达式支持的标志。

1.1 正则表达式的标志

一个正则表达式可以带一个或多个flag,flag有如下取值:

  • g 表示全局模式(global),默认情况下,js的正则表达式在找到第一个匹配项时,就会停止匹配,加上g标志后,表达式会查找目标字符串中所有的匹配项。
  • i 忽略大小写
  • m 多行模式

flag可以组合使用,比如/exp/gi表示exp表达式用于全局匹配,且忽略大小写。

1.2 正则表达式的用法

我们先不看正则表达式是怎么来的,先看它是怎么用的。使用RegExp主要是其实例方法以及外部支持RegExp的方法。后续的章节中,我们以patt表示表达式,text表示目标字符串。

  • RegExp.exec()方法,exec是RegExp的实例方法,用法是patt.exec(text),在text中查找匹配patt的内容,并以数组的方式返回第一个匹配的且未返回的结果,当无匹配时返回null。注意前面这句话中的"第一个"和"未返回的"我打了重点符号,
let text="112233";
let patt=/(\d)\1/g;

let matchs = patt.exec(text);
//["11", "1", index: 0, input: "112233", groups: undefined]

matchs = patt.exec(text);
//["22", "2", index: 2, input: "112233", groups: undefined]

matchs = patt.exec(text);
//["33", "3", index: 4, input: "112233", groups: undefined]

matchs = patt.exec(text);
//null

本列中/(\d)\1/g表达式用来查找连续相同的数字,因为还没有讲表达式,所以这里不需要知道表达式的细节。patt.exec(text)查找text中匹配patt表达式的内容,第一次返回了一个数组并赋值给matchs,这个数组有index和input属性,index表示当前匹配的内容在text中的起点位置,input表示目标字符串(及text文本内容)。matchs[0]表示当前匹配的内容,这里匹配的内容是11,如果表达式产生了分组(还没讲分组),会在matchs中添加分组内容,这里是1就是分组内容。

再次执行exec方法,返回第二个匹配的内容数组,直到返回为null,表示所有的匹配内容都已提取完。

如果表达式没有设置全局模式g,那么每次执行patt.exec(text)时,都会从头开始匹配,及每一次执行exec()方法就会返回第一次的内容。

  • RegExp.test()方法用于测试patt与text是否匹配,它只会返回目标字符串是否与表达式匹配,不会返回文本内容,这个方法一般用于验证用户的输入。

    let patt = /^d{3}-d{8}$/;
    let phoneNumber = "010-88888888";
    
    let flag = patt.test(phoneNumber);
    //true
    

    上例中,patt表达式用于验证电话号码的格式,而使用test方法,只返回目标字符串是否为匹配,而不返回具体的内容。

  • string.match()方法是string的实例方法,与RegExp的方法类似,返回匹配的字符集合,注意,这里不包含组信息。

    let text="11112223455";
    let patt=/(\d)\1/g;
    
    text.match(patt);
    // ["11", "11", "22", "55"]
    
  • string.search()方法,返回第一个匹配的字符串的位置,如果没有匹配的内容,返回-1。

    let text="12345";
    let patt=/(\d)\1/g;
    
    text.search(patt);
    //-1 没有任何匹配的内容
    
    text = "1233445";
    text.search(patt);
    //2 返回字符串的第一个匹配内容的位置,这里是33,而后面44的位置不会返回
    
  • string.replace()方法是string的实例方法,用于替换指定的字符串,返回一个替换完成的字符串,相对之前的方法要复杂些,它接收两个参数:

    • 第一个参数时要匹配的内容,是一个RegExp对象或字符串。
    • 第二个参数要替换的内容,可以是字符串或函数。

    当第一个参数时字符串时,只能替换第一个匹配的内容,如果要全局替换,需要使用全局模式的正则表达式。

    let text = "cat,bat,sat,fat";
    let tmp = text.replace("at","ond");
    //text "cat,bat,sat,fat"
    //tmp "cond,bat,sat,fat"
    

    第一个参数使用字符串时,只能替换第一个匹配的内容,后面的bat并没有被替换。

    let text = "cat,bat,sat,fat";
    let tmp = text.replace(/at/g,"ond");
    // tmp "cond,bond,sond,fond"
    

    第一个参数使用带全局标志(g)的正则表达式,会替换全部的匹配的内容。替换内容还可以使用特殊的字符(如:$n,这个叫回溯引用),引用前面匹配的结果。

    let text = "01088888888,01077777777,01066666666";
    let patt = /(\d{3})(\d{8})/g
    let tmp = text.replace(patt,"$1-$2");
    //tmp "010-88888888,010-77777777,010-66666666"
    

    上面使用特殊字符引$n引用了匹配结果的分组信息,并将匹配的结果格式化为自定的样式。$1引用了第一个分组信息(\d{3}),$2引用了第二个分组信息(\d{8})

    replace方法的第二个参数是Function时,replace会向该函数传递三个参数,分别是,匹配的内容,匹配项的位置,原生字符串。使用function可以做更精细的替换,这里不深入讲解。

  • string.split()方法,该方法用指定的分隔符将指定的字符串分隔为多个子字符串。这个分隔符可以是字符串也可以是RegExp对象。使用RegExp作为分隔符时,该方法在不同浏览器存在差异。

2 编写正则表达式

正则表达式的符号较多,可分为普通字符,转义字符,特殊符号,定位符,限定符与组共6大类。

2.1 普通字符

普通字符代表字符本身,如:

let text = "cat,bat";

let patt = /at/g;
let tmp = text.match(patt);
//tmp ["at", "at"]

目标字符串text,会找到两处与patt相关的内容。

2.2 转义字符

有些普通字符转义后代表特殊的含义,比如:w在转义后\w表示任意的0-9,a-z,A-Z以及_下划线。

转义字符 说明
\w 匹配任一0-9,a-z,A-Z,_字符
\W 匹配任一非0-9,a-z,A-Z,_的字符
\d 匹配任一0-9的数字
\D 匹配任一非0-9的数字
\s 匹配任一空格,tab和换行符\n
\S 匹配任一非空格,tab和换行符\n的字符

转义字符有很多,这里我只列出了常用的3个w、d、s,他们的大写是对应的含义取反。

let text = "要出差3天,还是1周";
let patt = /\d天/g;

let tmp = patt.exec(text);
// ["3天", index: 3, input: "要出差3天,还是1周", groups: undefined]

上面的例子使用\d表示匹配数字,后面跟普通字符"天",这样就能从上面的一段文字中提取出几天,注意:虽然1也是数字,但是它不符合表达式,因为表达式是数据后面跟着"天"。

2.3 特殊符号

普通字符代表字符本身,有些普通字符通过转义代表了另一类含义。而有些字符不需要转义就代表了与本身不同的含义,如果要表示这些字符本身的含义,还得通过转义符来转义。这些字符就是我们说的特殊字符:

特殊字符 说明
\ 转义符,如果要表示\本身,还得通过转义符,写成\\,后面的特殊字符同理,不在累述
. 表示任意字符,但不包括换行符
| 表示或
[ ] 表示匹配中括号中的任意一个字符,如[ab],表示匹配a或b,与|的含义相同
[^ ] 表示不能匹配中括号中的任意一个字符

.表示任意字符,但是包括换行符,如果我们要代表任意字符可以使用前面的\s\S的组合[\s\S]

2.4 定位符

定位符比较特别,前面我们讲的不管是普通字符、还是特殊字符它匹配的要么是单个字符,要么就是字符集中的某个字符,但终归说来就是匹配的字符,而定位符却是匹配的位置,注意是位置而不是字符。如果要表示字符本身,也需要使用转义符。

定位符 说明
^ 匹配字符串开始位置,注意^[^ ]中的^不同,[^ ]中^与[ 是一个整体的,表示非的意思
$ 匹配字符串的结束位置
\b 表示单词的边界
\B 表示非单词边界

^$表示字符串的开始和结束位置,这个很好理解。而\b表示单词的边界,我们知道单词是以空格隔开的,那么单词的边界就是一个单词的开始或结尾处,\B表示非单词的边界,即非单词的开始或结尾处。例如:

let text = "element release bele";
let patt = /ele/g;

let tmp = patt.exec(text);
//["ele", index: 0, input: "element release", groups: undefined]
tmp = patt.exec(text);
//["ele", index: 9, input: "element release", groups: undefined]
tmp = patt.exec(text);
//["ele", index: 17, input: "element release bele", groups: undefined]
tmp = patt.exec(text);
//null

通过普通字符能够找到3个匹配的字符,位置分别在0,9和17。下面我们看看通过\b\B查找。

let text = "element release bele";

//使用\b定位在单词的开始位置
let patt = /\bele/g;
patt.exec(text);
//["ele", index: 0, input: "element release", groups: undefined]


//使用\B定位ele元素不在开始或结束的任意位置
patt = /\Bele/g;
patt.exec(text);
//["ele", index: 9, input: "element release", groups: undefined]

//使用\b定位在单词的结束位置
patt = /ele\b/g;
tmp = patt.exec(text);
//["ele", index: 17, input: "element release bele", groups: undefined]

使用\B能找到不是以ele开头或结尾的内容,\b表示单词的边界,边界分为开始处和结尾处,\b如果加在前面代表匹配单词的开始处\bele,加在后面表示匹配单词的结尾处ele\b

2.5 限定符

限定符表示重复的次数,与其它的类搭配使用。如果要表示字符本身的含义,也需要使用转义符

限定符 说明
+ 表示+前面的内容,重复1次到N次
* 表示*前面的内容,重复0次到N次
? 表示?前面的内容,重复0次或1次
{n} 表示{}前面的内容,重复n次
{n,m} 表示{}前面的内容,重复n次到m次
{n,} 表示{}前面的内容,至少重复n次
? 限定符之后,表示懒惰模式

上面出现了两个号,表示有2种用法,一种是在字符或分组之后,表示重复0次或1次。第2中是在限定符之后,表示懒惰模式

通常正则表达式在加了限定符后,默认都会匹配尽可能多的字符,这种叫做贪婪模式,比如:

let text = "gooooogle;

// 贪婪模式
let patt = /o+/g;
text.match(patt);
//["ooooo"]

// 懒惰模式
let patt = /o+?/g;
text.match(patt);
//["o", "o", "o", "o", "o"]

可以看出贪婪模式下,表达式匹配了5个连续o,返回了一个"ooooo"字符串,在懒惰模式下,表达式匹配了1个连续o5次,所以返回了5个o。

2.6 分组

分组使用()来表示,它将括号内的表达式看做一个整体。可以将分组内的表达式看做整个表达式的一个子表达式。子表达式还可以嵌套子表达式。

2.6.1 分组的限定

我们前面做限定(如+,*,?,{n,m})时,都是针对限定符前面的单个字符,比如https?表示的是对s这个字符重复0或1次,代表的是http和https,分组的一个用途是将字符组成一个整体,这样就可以对这个整体(分组)做限定。

let text = "haha"

let patt = /haha/g
let tmp = text.match(patt);
// ["haha"]

patt = /(ha)+/g
tmp = text.match(patt);
// ["haha"]

/(ha)+/g是对ha分组做+限定。

2.6.2 回溯引用

回溯引用的含义是,在同一个表达式中,后面的部分可以用\n的方式引用前面分组匹配的结果。正则表达式会为每个分组从1开始顺序的分配一个编号。结合前面的\n方式,当n=1,表示第一个分组的内容,以此类推。比如我们要查找html文档中所有的h标签,可以使用如下表达式:

let patt = /<(h[1-6])[\S\s]*?<\/\1>/ig

/<(h[1-6])[\S\s]*?<\/\1>/ig表达式使用全局标记g和忽略大小写标记i,<是普通字符,(h[1-6])作为一个分组,能匹配h1-h6的内容,这个分组的编号为1,表达式的后面部分使用\1引用这个分组的内容,[\S\s]*表示任一空格和非空格字符重复0到n次,及表示的是任意行数的全部内容,注意.*表示的是单行内容,因为.不包含换行符,所以重复n次,也只能是一行的内容,这里的加载限定符之后,表示懒惰模式。

回溯引用还可以用于高级的替换场景,这个需要结合具体的脚本语言来实现,下面以js为例

let text = "<p>如有需要,请发邮件到 xxx@gmail.com </p>";
let patt = /(\b\w+[\w\.]*@[\w+\.]+\.\w+\b)/g;
let tmp = text.replace(patt,"<a href='mailto:$1'>$1</a>");
//<p>如有需要,请发邮件到 <a href='mailto:xxx@gmail.com'>xxx@gmail.com</a> </p>

上面的场景是将邮箱地址替换为可点击的链接,在js中使用$符号来获取回溯引用。

2.7 前后查找

前后查找(Lookaround)有时又叫零宽度匹配,它和定位符(^,$,\b,\B)有点像,都是匹配的是位置,与定位符不同的是,定位符的位置是预先定义好的,要么是字符串的开始或结尾,要么就是单词的边界与非边界。而前后查找可以通过子表达式(定位条件)来定位位置。

与单词边界\b相似,在前后查找中,定位的位置(表达式所匹配的内容)也分为前与后,及向前查找与向后查找

类型 语法格式 说明
向前查找 (?=exp) 返回表达式exp所匹配内容之前的位置
向后查找 (?<=exp) 返回表达式exp所匹配内容之后的位置

前后查找也可以组合起来使用,比如要取标签p中的内容:

let text = "<p>如有需要,请发邮件到 <a href='mailto:xxx@gmail.com'>xxx@gmail.com</a> </p>";

let patt =/(?<=<p>)[\s\S]*?(?=<\/p>)/g
let tmp = text.match(patt);
//["如有需要,请发邮件到 <a href='mailto:xxx@gmail.com'>xxx@gmail.com</a> "]

上面组合使用了向前查找和向后查找,(?<=<p>)是向后查找,返回条件<p>后面的位置,(?=<\/p>)是向前查找,返回</p>结束标签前面的位置。[\s\S]*?中间采用懒惰模式匹配两个位置(及p标签)之间的内容。

let text = "1234567890";
let patt = /\B(?=(\d{3})+$)/g

let tmp = text.replace(patt,",");
//1,234,567,890

text = "1234567890.54";
patt = /\B(?=(\d{3})+\.)/g
tmp = text.replace(patt,",");
//"1,234,567,890.54"

上例是给数字按3位加逗号,使用了向前查找表达式,整个表达式开始使用了\B,表示位置只会出现在字符串的非边界上,防止逗号加在整个字符串的外面。(?=(\d{3})+$)是向前查找,返回满足(\d{3})+$内容前面的位置,注意表达式最后的定位符是$,可以理解为结束位置是字符串的最后。当有小数点的时候,结束位置应该换为\.

前后查找是查找满足条件的内容的前或后的位置。还有一种是查找不满足条件的内容的前后位置,称为负向前后查找,这种用的比较少。

类型 语法格式 说明
负向前查找 (?!exp) 返回不能匹配表达式exp内容之前的位置
负向后查找 (?<!exp) 返回不能匹配表达式exp内容之后的位置

2.8 正则表达式中的条件

在处理复杂的业务逻辑时,可能需要在正则表达式中嵌入条件,及只有满足特定条件时,才执行相应的表达式。嵌入条件语法使用?,它有两种情况:

  • 根据回溯引用来进行条件处理
  • 根据前后查找来进行条件处理
类型 语法格式 说明
回溯引用 (?(ref)true_exp|false_exp) ref是前面的表达式的引用编号,这里填写数字即可,从1开始(与回溯引用的\n相同),true_exp表示ref匹配到内容时执行,false_exp表示ref匹配不到内容时执行,false_exp是可选内容
前后查找 (?(?=exp)true_exp|false_exp) (?=exp)是前后查找的表达式,也可以换为(?>=exp),true_exp表示前后查找能匹配时执行,false_exp表示前后查找不能匹配时执行,false_exp是可选内容

3 参考

[1] 正则表达式30分钟入门教程

[2] JavaScript高级程序设计(第3版)

[3] 正则表达式必知必会(修订版)

相关文章

  • Linux命令行与Shell脚本编程大全-shell正则表达式

    本章内容: 定义正则表达式 了解基本正则表达式 扩展正则表达式 创建正则表达式 定义正则表达式 正则表达式是你定义...

  • 正则相关

    正则表达式基本语法 正则表达式常见字符 正则表达式特殊字符 正则表达式数量词 正则表达式边界匹配 正则表达式逻辑或...

  • 正则表达式系列-1

    正则表达式系列-1正则表达式系列-2正则表达式系列-3正则表达式系列-4 什么是正则表达式 正则表达式就是用事先定...

  • 正则表达式

    正则表达式 - 教程正则表达式 - 简介正则表达式 - 语法正则表达式 - 元字符正则表达式 - 运算符优先级正则...

  • Python基础入门 - 正则表达式与综合实战

    1. 初识正则表达式 1.1 介绍 步骤介绍正则表达式入门及应用正则表达式的进阶正则表达式案例 1.2 正则表达式...

  • Java正则表达式参考

    Java正则表达式入门 java正则表达式应用 深入浅出之正则表达式(一) 深入浅出之正则表达式(二) 正则表达式...

  • 正则表达式

    正则表达式 正则表达式就是记录文本规则的代码 正则表达式常用的元字符 正则表达式常用的限定符 正则表达式举例:这里...

  • Python爬虫(十)_正则表达式

    本篇将介绍python正则表达式,更多内容请参考:【python正则表达式】 什么是正则表达式 正则表达式,又称规...

  • python正则表达式

    本篇将介绍python正则表达式,更多内容请参考:【python正则表达式】 什么是正则表达式 正则表达式,又称规...

  • 正则表达式

    了解正则表达式基本语法 能够使用JavaScript的正则对象 正则表达式简介 什么是正则表达式 正则表达式:用于...

网友评论

    本文标题:正则表达式

    本文链接:https://www.haomeiwen.com/subject/oqqbwqtx.html