正则表达式 - 2018-08-07

参考:正则表达式30分钟入门教程

元字符 metacharacter

  1. \b : 匹配单词的开始或结尾,只匹配一个位置
  2. . (点) 匹配除换行符以外的任何字符
  3. \d 匹配数字 0-9
  4. \s 匹配任意的空白符,包括空格、制表符、换行符、中文全角空格等
  5. \w 匹配字母、数字、下划线
  6. ^ 匹配字符串的开始
  7. $匹配字符串的结束,比如 一个网站要求你填写的QQ号必须为5-12位数字时,可以使用^\d{5,12}$,单独的{2}表示不多不少重复2次,{5,12}表示重复的次数不能少于5次,不能多余12次。
    如果选中了处理多行的选项,^和$的意义为 匹配行的开始和结束处
  8. () 也是元字符,用来分组
  9. 如果要查找元字符本身,比如 . 和 * ,就需要使用反斜杠 \ 来进行转义,查找反斜杠 \,也得用 \\

限定符 指定数量的代码

代码 说明
* 重复 0 次或者重复多次
+ 重复 1 次或者重复多次
? 重复 0 次或者 1 次
重复了 n 次
重复了 n 次或者更多次
重复 m 到 n次

字符类

使用中括号 []

  1. [aeiou] 匹配任何一个元音字母
  2. [.?!]匹配标点符号 . ? !
  3. [0-9] 匹配数字 0-9 与 \d 代表的意义相同
  4. [0-9a-zA-Z]\w等价
  5. \(?0\d{2}[(-]?\d{8} 首先是一个转义字符\( 它出现0次或1次(?), 然后是数字 0,后面跟着 2 个数字 (\d{2}), 然后是空格或者-中的一个,它出现1次或者不出现(?),最后是8个数字(\d{8}) 。上式可以匹配010)88886666,或022-22334455,或02912345678等常见电话号码格式,但是也能匹配 010)12345678或(022-87654321这样的“不正确”的格式,因此就要使用下面的 分支条件

分支条件

|号把不同的规则分隔开
e.g. \(0\d{2}\)[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配三位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号支架你可以用连字号或空格间隔,也可以不用。
注意: 匹配分支条件时,将会从左到右地测试每个条件,如果满足了某个条件将不会再去管其他的条件
比如:\d{5}-\d{4}|\d{5} 用于匹配美国的邮政编码,美国邮编的规则是5位数字或者用连字号间隔的9位数字;如果写成:
\d{5}|\d{5}-\d{4}那么就只会匹配5位的邮编,以及9位邮编的前五位

分组

e.g. 描述一个IP地址
((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)
要点:2[0-4]\d|25[0-5]|[01]?\d\d? 分为三种情况:

  • 一个2,然后0-4中选一个,最后第三位任选(200-249)
  • 一个25,然后0-5中选一个 (250-255)
  • 0和1中选一个,可以不选,再是一个数字,最后第三位可有可没有(0-199)
    最后再是一个\.表示IP四个数字之间的分隔,由于最后面一个数字不带点,所以要分开,先写前面3个
  • 整个最外层的括号就代表分组,{3}代表前面的部分重复三次

反义

采用大写的方式,与原来的小写方式表达相反的含义

代码 说明
\W \w相对应,匹配不是字母、数字、下划线的字符
\S \s相对应,匹配任何不是空白符的字符
\D \d相对应,匹配任意非数字的字符
\B \b相对应,匹配不是单词开头或结束的位置
[^x] 匹配除x以外的字符
[^aeiou] 匹配除aeiou以外的任意字符

后向引用

使用小括号指定一个子表达式后,匹配这个子表达式的文本(也就是此分组捕获的内容)可以在表达式或其它程序中作进一步的处理。默认情况下,每个分组会自动拥有一个组号,规则是:从左向右,以分组的左括号为标志,第一个出现的分组的组号为1,第二个为2,以此类推。
后向引用用于重复搜索前面某个分组匹配的文本,\1代表分组1匹配的文本
e.g.

  • \b(\w+)\b\s+\1\b可以用来匹配重复的单词,像go go, 或者kitty kitty。这个表达式首先是一个单词,也就是单词开始处和结束处之间的多于一个的字母或数字(\b(\w+)\b),这个单词会被捕获到编号为1的分组中,然后是1个或几个空白符(\s+),最后是分组1中捕获的内容(也就是前面匹配的那个单词)(\1)。

  • 也可以自己指定表达式的组名,e.g.指定一个表达式的组名为Word,则可以使用 (?<Word>\w+),其中尖括号也可以用引号 ' 来替代,即(?'Word'\w+),当反向需要引用这个分组捕获的内容时,可以用\k<Word>

代码 说明
(exp) 匹配 exp ,并捕获文本到自动命名的组里
(?<name>exp) 匹配 exp ,并捕获文本到名称为 name 组里,也可以(?'name'exp)
?:exp 匹配 exp ,不捕获匹配的文本,也不给此分组分配组号

零宽断言

  • (?=exp)它断言自身出现的位置后面可以匹配表达式 exp ,比如:\b\w+(?=ing\b)匹配以 ing 结尾的单词的前面部分(除了 ing 以外的部分),比如在查找 I am singing while you are dancing. 它会匹配 sing 和 danc,称之为:零宽度正预测先行断言
  • (?<=exp)它断言自身出现的位置前面可以匹配表达式 exp ,比如 (?<=\bre)\w+\b,会匹配以 re 开头的单词的后半部分(除 re 以外的部分),比如在查找 *reading a book *,会匹配 ading。称之为零宽度正回顾后发断言

e.g. (?<=\s)\d+(?=\s) 匹配以空白字符间隔的数字(不包括这些空白字符)

负向零宽断言

(?!exp)断言此位置的后面不能匹配表达式 exp
e.g.
\d{3}(?!\d)匹配三位数字,而且这三位数字的后面不能是数字
\b((?!abc)\w)+\b匹配不包含连续字符串 abc 的单词
一个更复杂的例子:(?<=<(\w+)>).*(?=<\/\1>)
匹配不包含属性的简单HTML标签内的内容;(?<=<(\w+)>)指定了这样的前缀:被尖括号括起来的单词;然后是.*,即任意字符串,最后是一个后缀?=<\/\1>,这里\/(斜杠和反斜杠)b用到了前面的字符转义;\1用到了反向引用,引用的正是捕获的第一组\w+匹配的内容,这样,如果前缀是, 那后缀就是 ;整个表达式匹配的是和之间的内容(不包括前缀和后缀)

注释

  • 小括号的一种用途是通过?#comment来包含注释,例如:
    2[0-4]\d(?#200-249)|25[0-5](?# 250-255)|[01]?\d\d?(?#0-199)
  • 要包含注释的话,最好是启用“忽略模式里的空白符”选项,这样在编写表达式时能任意的添加空格,Tab,换行,而实际使用时这些都将被忽略。启用这个选项后,在#后面到这一行结束的所有文本都将被当成注释忽略掉。例如,我们可以前面的一个表达式写成这样:
(?<=     #断言要匹配的文本的前缀
<(\w+)>  #查找尖括号括起来的字母或数字(即HTML或XML标签)
)             #前缀结束
.*            #匹配任意文本
(?=          #断言要匹配的文本的后缀
<\/\1>       #查找尖括号括起来的内容
)               #后缀结束

贪婪与懒惰

  • 表达式 a.*b 将匹配 最长的以 a 开始,以 b 结束的字符串,如果用其来搜索 aabab 它会匹配整个字符串;这被称之为 贪婪匹配,匹配
  • 有时我们需要匹配尽可能少的字符,因此可在后面加一个限定符?,即.*?表示匹配任意数量的重复,但是在能使整个匹配成功的前提下使用最少的重复;
    比如:a.*?b匹配最短的,以 a 开始,以 b 结束的字符串;如果将其应用于 aabab,它会匹配到第一个aad(第1个字符到第3个字符)和 ab (第4-5个字符)
    • 为什么第一个匹配是aab(第一到第三个字符)而不是ab(第二到第三个字符)?简单地说,因为正则表达式有另一条规则,比懒惰/贪婪规则的优先级更高:最先开始的匹配拥有最高的优先权——The match that begins earliest wins。
代码 说明
*? 重复任意次,但是尽可能少的重复
+? 重复1次或更多次,但尽可能少的重复
?? 重复0次或1次,但尽可能少重复
{m,n}? 重复 m-n 次,但是尽可能少重复
{n,}? 重复n次以上,但是尽可能少重复

平衡组/递归匹配

有时我们需要匹配像( 100 * ( 50 + 15 ) )这样的可嵌套的层次性结构,这时简单地使用(.+)则只会匹配到最左边的左括号和最右边的右括号之间的内容(这里我们讨论的是贪婪模式,懒惰模式也有下面的问题)。假如原来的字符串里的左括号和右括号出现的次数不相等,比如( 5 / ( 3 + 2 ) ) ),那我们的匹配结果里两者的个数也不会相等。有没有办法在这样的字符串里匹配到最长的,配对的括号之间的内容呢?

为了避免(和(把你的大脑彻底搞糊涂,我们还是用尖括号代替圆括号吧。现在我们的问题变成了如何把xx <aa aa> yy这样的字符串里,最长的配对的尖括号内的内容捕获出来
这里将用到一下语法:

  • (?'group') 把捕获的内容命名为group,并压入堆栈(stack)
  • (?'-group')从堆栈上弹出最后压入堆栈的名为 group 的捕获内容,如果堆栈为空,则本组的匹配失败
  • (?(group)yes|no)如果堆栈上存在以名为group的不捕获内容的话,继续匹配 yes 部分的表达式,否则匹配 no 部分
  • (?!)零宽负向先行断言,由于没有后缀表达式,试图匹配总是失败

思路:每碰到左括号,就压入一个 "Open" ,每碰到一个右括号,就弹出一个,到了最后看堆栈是否为空,如果不为空则证明左括号比右括号多,那匹配就应该失败。正则表达式会进行回溯(放弃最前面或最后面的一些字符),尽量使整个班表达式得到匹配

<				#最外层的左括号
	[^<>]*		#最外层的左括号后面的不是括号的内容
	(

	 	(
		 	(?'Open'<)	#匹配到了左括号,往堆栈压入一个'Open'
			[^<>]*		#匹配左括号后面不是括号的内容
		)+				#可以出现多次

		(
		 	(?'-Open'>)	#碰到了右括号,则从堆栈弹出一个'Open'
			(^<>)*		#匹配右括号后面不是括号的内容
		)+				#可以出现多次

	)*					#整个上面可以出现多次

	(?(Open)(?!))		#在遇到最外层的右括号前面,判断堆栈上是否还有"Open",
						#如果还有,则执行 (?!),由于后面没有后缀表达式,匹配总是失败
>						#最外层的括号
posted @ 2018-08-21 19:46  默写年华  阅读(171)  评论(0编辑  收藏  举报