关于正则表达式

shanzm-2019年5月1日 00:07:35

1.简介

1.1 概念

正则表达式:使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。这个实际的正则表达式,也称为:模式(pattern)

1.2 具体用处

  • 【查找】:一些软件中自带的搜索功能,是支持正则表达式的,可以实现有目的的高效率的快速搜索目标文件
    如:Office软件,VS,一般的编程用的文本编辑器,Everything

  • 【替换】:批量提取/替换有规律的字符串,如网络爬虫和模板引擎的标签库的开发

  • 【检验】:各种开发语言中的使用,处理文本和大字符串,同时可以对用户输入的合法性验证(IP地址,特殊的订单号要求等)



2.具体的语法

2.1 常用的元字符

【匹配字符】:

元字符 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字

【匹配位置(边界)】:

元字符 说明
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束

【说明】

  • 一个元字符只匹配一个字符,如何匹配几个字符(即:字符串),要使用“重复匹配”符号。

  • 位置元字符,只是代表位置,不匹配任何字符



2.2 反义元字符

语法 说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符


2.3 转义

元字符是一些在正则表达式里有着特殊含义的字符。

因为元字符在正则表达式里有着特殊的含义,所以这些字符就无法用来代表它们本身。

故需要对一些特殊字符进行转义,使用 反斜杠“\” 转义。

这一点和C#语言中语法,甚至markdown语法都一样,不赘述。
eg.

“\.”    匹配“.” 
“\* ”   匹配“*”
“\?”    匹配“?”
“\[”    匹配“["
“\\”    匹配“\"



2.4 注释

(?# 注释内容)



2.5 重复匹配

语法 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次 =={0,1}
重复n次
重复n次或更多次
重复nm次 (不是n或m次)


2.6 字符集合

【定义】 使用方括号“[ ]”表示一个集合,使用连字符“-”表示从某某到某某

【注意】"-"(连字符)是一个特殊的元字符,作为元字符它只能用在[和]之间。在字符集合以外的地方,一只是一个普通字符,只能与一本身相匹配。因此,在正则表达式里,一字符不需要被转义。

【例子】

[A-Z]匹配从A到z的所有大写字母。
[a-z]匹配从a到z的所有小写字母。
[A-F]匹配从A到F的所有大写字母。
[abcd]匹配a或b或c或d
[0-9] == \d
[a-z0-9A-Z_]  == \w

【字符集取非】
字符集合通常用来指定一组必须匹配其中之一的字符。但在某些场合,我们需要反过来做,给出一组不需要得到的字符。换句话说:除了那个字符集合里的字符,其他字符都可以匹配。
使用用元字符“^”来对字符集合求非。

【例子】
[0-9]匹配的是任何不是数字的字符



2.7 贪婪和懒惰

【定义】贪婪,即过度匹配。
*和+都是所谓的“贪婪型”元子符,它们在进行匹配时的行为模式是多多益善而不是适可而止的。
它们会尽可能地从一段文本的开头一直匹配到这段文本的末尾,而不是从这段文本的开头匹配到碰到第一个匹配时为止。

正则表达式的作用就时快速准确的找到你想要的字符串,所以我们更喜欢“懒惰”。

在不需要这种“贪婪行为”的时候该怎么办?
答案是使用这些元字符的“懒惰型”版本(“懒惰”在这里的含义是匹配尽可能少的字符,与“贪婪型”元字符的行为模式刚好相反)。

【语法】懒惰型元字符的写法很简单,只要给贪婪型元字符加上一个?后缀即可。

常用的贪婪型元字符的懒惰型版本

语法 说明
*? 重复任意次,但尽可能少重复
+? 重复1次或更多次,但尽可能少重复
?? 重复0次或1次,但尽可能少重复
{n,m}? 重复n到m次,但尽可能少重复
{n,}? 重复n次以上,但尽可能少重复

【例1】:

字符串:
This offer is not available to customers living in <B>AK</B>and <B>HI</B>.

贪婪型正则表达式:<B>.*</B>
结果(匹配到一个符合的字符串):<B>AK</B>and <B>HI</B>

懒惰型正则表达式:<B>.*?</B>
结果(匹配到两个符合的字符串): <B>AK</B>
                             <B>AK</B>

【例2】:

字符串:aaabbb
贪婪正则表达式:a.*b
结果:aaabbb

懒惰正则表达式:a.*?b
结果:aaab



2.8 分支条件

【语法】用“|”把不同的规则分隔开

从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管它之后的的条件了。

\d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。
美国邮编的规则是5位数字,或者用连字号间隔的9位数字。
之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。
如果你把它改成 \d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。
原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。



2.9 子表达式(分组)

【语法】使用小括号“()”表示分组,分组的内容即子表达式

为什么要分组?

举个例子

你想要在在字符串中匹配“abab"四个连续的字母,你想到使用正则表达式"ab"重复两次,即“ab{2}”
但是其实它却得不到你想要的结果,它匹配的是“abb”,正确的写法是使用分组“(ab){2}”。

【例子】

匹配IP地址:IP地址由四组数字构成,每组数字由1到3个数字字符构成,它们之间以英文句号分隔。

正则表达式:(\d{1,3}.){3}\d{1,3}

且不论是否正确,但是确实是使用了分组把“\d{1.3}.”重复了三次

但是:这个模式有什么不对的地方吗?从语法上讲,它完全正确。说这个模式正确,是因为所有合法的IP地址都与之相匹配。但深入研究一下就会发现,这个模式还可以匹配其他一些东西;说得明白点儿,不合法的IP地址也能与之相匹配。
IP地址由4个字节构成,IP地址中的4组数字分别对应着那4个字节,所以IP地址里的每组数字的取值范围也就是单个字节的表示范围,即0-255。这意味着IP地址里的每一组数字都不能大于255,可是上面那个模式还能匹配诸如345、700、999之类的数字序列,而这些数字在P地址里都是非法的。

注意:有句话希望你能牢牢记住:把必须匹配的情况考虑周全并写出一个匹配结果符合预期的正则表达式很容易,但把不需要匹配的情况也考虑周全并确保它们都将被排除在匹配结果以外往往要困难得多。

那么一般怎么使用正则表达式匹配IP地址呢?

我们来分析一下:
下面是一个合法的IP地址里的各组数字必须且只能符合的规则:

  • 任何一个1位或2位数字。
  • 任何一个以1开头的3位数字。
  • 任何一个以2开头、第2位数字在04之间的3位数字。
  • 任何一个以25开头、第3位数字在0~5之间的3位数字

根据这些规则来构造一个相应的模式:

(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})l(1\d{2})|(2[0-4]\d)|(25[0-5]))

【注意】
1.一个“(子表达式)”就是一个分组,正则表达式的测试,默认是给他分配一个组号,并可以显示该组所匹配的内容。

关于组号的分配:

分组0对应整个正则表达式

实际上组号分配过程是要从左向右扫描两遍的:第一遍只给未命名组分配,第二遍只给命名组分配--因此所有命名组的组号都大于未命名的组号

你可以使用(?:exp)这样的语法来剥夺一个分组对组号分配的参与权.(这句话生命意思呢?就时某个分组(exp),你把它写成(?:exp),则不会单独显示这一组匹配的值(但是整体的正则表达式正常匹配)

2.若以我们不需要显示这组匹配的内容,则我们可以使用"(?:子表达式)"的形式。

eg.匹配百度云连接:

(?:https?:\/\/)?(?:yun|pan|eyun)\.baidu\.com\/(?:s\/\w*(((-)?\w*)*)?|share\/\S*\d\w*)

3.我们可以给该分组起一个名字,使用 "(?<组名>子表达式)"



2.10 后向引用(回溯引用)

【定义】后向引用(backreference)又称回溯引用:通过组号或是组名对之前定义的分组所匹配到的字符串的引用

【注意】:引用的是分组匹配到的字符串,而不是分组的正则表达式。所以:你可以把后向引用看作一个变量

后向引用方式:\组号\k<组名>

【例1】
查找文本中的重复单词

假设你有一段文本,你想把这段文本里所有连续重复出现的单词(打字错误,其中有一个单词输了两遍)找出来。
显然,在搜索某个单词的第二次出现时,这个单词必须是已知的。
回溯引用允许正则表达式模式引用前面的匹配结果(具体到这个例子,就是前面匹配到的单词)。

This is a block of of text. 

正则表达式:\s+(\w+)\s+\1

结果: of of

【例2】
寻找一段标签语言中所有合法的各级标题

这个问题并没有你想的那么简单,你要考虑的是每一级标题的标签的相互匹配。

文本:
<BODY>
<H1>Welcome to my Homepage</H1>
Content is divided into two sections:<BR>
<H2>C01dFusion</H2>
Information about Macromedia ColdFusion.
<H2>Wireless</H2>
Information about Bluetooth,802.11, and more.
<H2>This is not valid HTML</H3>
</BODY>

正则表达式:<[Hh]([1-6])>.*</[Hh]\1>

结果:
<H1>Welcome to my Homepage</H1>
<H2>C01dFusion</H2>
<H2>Wireless</H2>

注意:<H2>This is not valid HTML</H3>这个就不匹配

后向引用在替换中的使用:

注意在替换中,引用分组的方式是:$组号

【例子】

313-555-1234
248-555-9999
810-555-9000

正则表达式:(\d{3})(-)(\d{3})(-)(\d{4})

替换为:($1) $3-$5

结果:
(313) 555-1234
(248) 555-9999
(810) 555-9000



2.11 前后查找(零宽断言)

【定义】前后查找:使用正则表达式对某一个位置匹配,之后对位置的前、后内容进行查找.
【注意】匹配到的定位字符串是不返回的(专业术语:不消费)

向前查找(lookahead):在匹配的位置之前(左侧)查找
【语法】:(?=exp)
【例子】

字符串:I'm singing while you're dancing.
正则表达式:\b\w+(?=ing\b)
结果:sing和danc

向后查找(lookbehind):在匹配的位置之后(右侧)查找
【语法】:(?<=exp>)
【例子】

字符串:reading a book
正则表达式:(?<=\bre)\w+\b
结果:ading

把向前查找和向后查找结合起来

【例1】
正则表达式:(?<=\s)\d+(?=\s)
匹配以空白符间隔的数字(再次强调,不包括这些空白符)。

【例2】

文本:
<HEAD>
<TITLE>Ben Forta's Homepage</TITLE>
</HEAD>

正则表达式:(?<=TITLE>).+(?=</TITLE)
结果:Ben Forta's Homepage

分析:
向后查找:(?<=TITLE>),我们匹配到“TITLE>”做为开始定位
向前查找:(?=</TITLE), 我们匹配到“</TITLE”作为结束定位
(?<=TITLE>).+(?=</TITLE):这句正则表达式:即寻找一个不确定长度的非换行符的字符串,这个字符串的开始位置是:TITLE>,结束位置是:</TITLE

【说明】
关于 “零宽断言” 是什么?
所谓的零宽断言其实就是前后查找。

其实:我们使用前后查找首先要匹配一个字符串作为定位,这个字符串即使正则表达式最终匹配,最终匹配结果我们也是不返回这个用来定位的字符串。即:所谓的返回的是0字节,也就是“零宽度(zero-width)”。

断言则就是字面理解那样--下一个结论。
正则表达式中只有当断言为真时才会继续进行匹配。

那么这么高大上 的名字怎么能不使用呢!
向前查找,也称:零宽度正预测先行断言
向后查找,也称:零宽度正回顾后发断言



2.12 对前后查找取非(负向零宽断言)

【引入】
字符集合我们使用“^”取非,表示不去匹配这些字符,同样向前向后查找一样有取非。

到目前为止正如你看到的那样,向前查找和向后查找通常用来匹配文本,其目的是为了确定将被返回为匹配结果的文本的位置(通过指定匹配结果的前后必须是哪些文本)。

这种用法被称为正向前查找(positive lookahead)和正向后查找(positive lookbehind)。

术语“”指的是寻找匹配的事实。

而对向前向后查找取非,则称为负前后查找(negative lookaround)

负前后查找的语法就是把正前后查找的等号换为感叹号

【定义1】负向前查找(negative lookahead)将向前查找不与给定模式相匹配的文本
【语法】:(?!exp)

【定义2】负向后查找(negative lookbehind)将向后查找不与给定模式相匹配的文本
【语法】:(?<!exp)

【例子】

文本:I paid $30 for 100 apples,50 oranges, and 60 pears.I saved $5 on this order.

只查找文本中的金额数字
分析:使用正向前查找
正则表达式:\b(?<=\$)\d+\b
结果:30和5

只查找文本中的非金额数字
分析:使用符向后查找
正则表达式:\b(?<!\$>)\d+\b
结果:100和50和60

注意:若是:(?<!\$>)\d+,会取到$30中的那个0

【分析】
\b(?<!$>)\d+\b :寻找一串数字(这串数字在文本中单独作为一个单词),但是这串数字的开头不能是"$"

【注意】深入理解"零宽":

举一个例子:在文本中寻找一个单词,这个单词中需要含有q(可以在单词中的任何位置),但是q后面一定不能l连着字母u

若是不使用前后查找,你可能会这样写:\b\wq[^u]\w\b

注意这样写是不准确的,若是某一个单词以字母q结尾,也是满足我们要寻找的单词的要求:含有q。

但是我们写的那个正则表达式是匹配不到的,

为什么呢?就是因为"[^u]"是占一个位置的,而零宽即意味着不占位置。

所以我们这样写:\b\wq(?!u)\w\b

【例子】

文本:hello benq ,I'm qik 哈哈
正则表达式:\b\w*q[^u]\w*\b
结果:qik

正则表达式:\b\w*q(?!u)\w*\b
结果:benq和qik



### 2.14 嵌入条件

平衡组/递归匹配

未完待续......



3. .NET Framework中的正则表达式

.NET Framework通过它的基本类库提供了强大和灵活的正则表达式支持,这些支持在所有的.NET语言和工具(包括ASP.NET、C#和Visual Studio.NET在内)里都可以使用。

NET里的正则表达式支持是通过Regex类(以及其他一些辅助类)提供的。

Regex类有以下一些方法:

IsMatch():测试在某个给定的字符串里是否可以找到一个匹配。
Match():搜索一个单个的匹配,该匹配将被为一个Match对象。
Matches():搜索所有的匹配,它们将被返回为一个MatchCo1lection对象。
Replace():在一个给定的字符串上进行替换操作。
Split():把一个字符串拆分为一个字符串数组。

利用各种静态函数,在无须创建和使用一个Regex类的情况下也可以执行一个正则表达式。

 Regex.IsMatch():在功能上等价于IsMatch()方法。
 Regex.Match():在功能上等价于Match()方法。
 Regex.Matches():在功能上等价于Matches()方法。
 Regex.Replace():在功能上等价于Replace()方法。
 Regex.Split():在功能上等价于Split()方法。

注意事项:

  • 要想使用正则表达式,必须用Imports System.Text.Regular-Expressions语句导入正则表达式对象。

  • 如果只是临时需要使用正则表达式,上述静态函数是理想的选择。

  • 正则表达式的选项需要使用Regex.Options属性给出,它是一个Regexoption枚举集合,你可以对这个枚举集合的各有关成员如Ignorecase、Multiline、singleline等进行设置。

  • .NET支持命名捕获,即允许对子表达式进行命名(这样就可以使用名字而不是编号来引用它们了)。
    参见:2.9 子表达式(分组)
    命名一个子表达式的语法是?
    引用这个回溯引用的语法是\k
    在一个替换模式里引用它的语法是${name}。

  • 在使用回溯引用的时候,$ `(反引号)将返回被匹配字符串前面的所有东西,$'(单引号)将返回被匹配字符串后面的所有东西,$+将返回最后一个被匹配的子表达式,$_将返回整个原始字符串,$&将返回整个被匹配字符串。

  • .NET Framework不支持使用\E、\1、L、\u和\U进行大小写转换。

  • .NET Framework不支持POSIX字符类。



4. 参考

posted @ 2019-04-29 16:17  shanzm  阅读(654)  评论(0编辑  收藏  举报
TOP