正则表达式必知必会

在不断填坑中前进。。

Posted by 三十一 on August 7, 2017

正则表达式必知必会

匹配单个字符

匹配纯文本

相当于文本查找的功能(CMD + F)。但是一般的正则表达式引擎默认返回第一个匹配结果。

大小写匹配

匹配任意字符

使用 . 来匹配任意字符。 譬如:使用 yunis. 来匹配,yunis1yunis2yunis3都是符合搜索结果的。

匹配特殊字符

如果需要匹配特殊字符( . 等)就需要使用元字符( \ 反斜杠)来对他进行转义。 譬如:使用 yunis\. 来匹配, yunis. 是符合匹配结果的。

匹配一组字符

匹配多个字符串中的任意一个

譬如:使用 [abc]hhhh 进行匹配。ahhhh,bhhhh,chhhh 都是符合条件的搜索结果。

例如字符集合区间

譬如:使用[a-c]hhh 进行匹配,ahhhh,bhhhh,chhhh 都是符合条件的搜索结果。 使用 [0-9]hhh 进行匹配 0hhh1hhh2hhh 。。。 9hhh 都是符合条件的搜索结果。

  • A-Z 匹配 A 到 Z 的所有大写字母。
  • a-z 匹配 a 到 z 的所有小写字母。
  • A-F 匹配 A 到 F 的所有大写字母。
  • A-z 匹配从 ASCII 字符 A 到 ASCII 字符 z 的所有字母。
  • 同一个字符集合里面可以给出多个字符区间。譬如 [A-Za-z0-9] 可以匹配任何字母和数字。

取非匹配

可以使用元字符 ^ 来标明你想对一个字符集合取非匹配结果。 譬如:使用 a[\^0-9] 进行匹配, as,ab,ac 都是符合调剂的搜索结果,a0,a1,a2不是符合条件的搜索结果。

使用元字符

对特殊字符使用转义

如果想匹配元字符需要使用 \ 反斜杠来转义。 譬如:匹配 \ 本身就需要转义,需要使用 \\来匹配 \。 匹配 [ 需要使用 \[ 来进行匹配。

匹配空白字符

  • \f 换页符
  • \n 换行符
  • \r 回车符
  • \t 制表符(Tab)
  • \v 垂直制表符

    匹配数字

  • \d 匹配任意一个数字字符 等价于 [0-9]
  • \D 匹配任意一个非数字字符 等价于 [^0-9]

    匹配字母与数字

  • \w 匹配任意一个字母、数字或者下划线字符,等价于 [a-zA-z0-9_]
  • \W 匹配任何一个非字母、数字或者下划线字符,等价于 [^a-zA-z0-9_]

匹配空白字符

  • \s 任意一个空白符 ,等价于 [\f\n\r\t\v]
  • \S 任意一个非空白符 ,等价于 [^\f\n\r\t\v]

重复匹配

匹配一个或多个字符

想要匹配同一个字符或者字符集合的多次重复,只需要简单的给这个字符或字符集合加上一个 + 字符作为后缀就行了。 + 匹配一个或者多个字符(至少一个,不匹配零个字符的情况)。譬如 a 匹配 a 本身,a+ 将匹配一个或多个连续出现的 a 。类似的 ,[0-9] 匹配任意一个数字,[0-9]+ 匹配一个或多个连续的数字。

匹配邮箱:[\w.]+@[\w.]\.\w+ 一般来说,在字符集合里面的元字符将被解释为普通字符,不需要被转义,但转义了也没有坏处。 [\w.][\w\\.] 是一样的。

+ 是一个元字符。如果需要匹配 + 本身,需要转义。

匹配零个或者多个字符

+ 匹配一个或者多个字符,* 匹配零个或者多个字符。 * 是元字符,匹配他本身需要转义。

匹配零个或者一个字符

? 元字符 ? 的意思是匹配一个字符一次或者零次。 譬如:https? 进行匹配,httphttps 都是符合条件的搜索结果。

匹配重复次数

  • +* 匹配的字符个数没有上限。我们无法为他们将匹配的字符个数设定一个最大值。
  • +* 至少匹配零个或者一个字符。无法为他们匹配的字符个数设定一个最小值。
  • 如果只使用 +* 我们无法将他们匹配的字符个数设定为一个精确的数字。
为重复匹配设定一个精确的值

使用 {6} 表示前一个字符匹配6次。 譬如 y{6} 进行匹配,yyyyyy 是符合条件的匹配结果。

为重复匹配设定一个区间

譬如使用 y{2,4} 进行匹配,yy,yyy,yyyy 都是符合条件的搜索结果。

为重复匹配设定一个最小重复数字

譬如使用 y{2,} 进行匹配,yy,yyy,yyyyyyyyyyyyyyyy 都是符合条件的搜索结果。 这个正则的意思是y至少重复2次。

防止过度匹配

下面一段文本 <b>1234</b>qweqweqweqweqweqweqw<b>5678</b> 当我们使用 <b>.*</b> 进行匹配时,我们希望得到的是 <b>1234</b><b>5678</b> ,但是我们得到的是 <b>1234</b>qweqweqweqweqweqweqw<b>5678</b>

这是因为什么呢? 因为 *+ 都是 贪婪型 的元字符,它们进行匹配时的模式是多多益善而不是适可而止。它们会尽可能的从一段文本的开头一直匹配到这段文本的结尾,而不是从文本的开头匹配到第一个匹配时为止。

当不需要这种贪婪型模式时怎么办?使用它们的懒惰型版本,懒惰型版本会尽可能少的匹配字符。

贪婪型元字符 懒惰型元字符
* *?
+ +?
{n,} {n,}?

当我们使用 <b>.*?</b> 进行匹配时,就能得到的是 <b>1234</b><b>5678</b> 的匹配结果。

位置匹配

单词边界

文本 dog hjhjdogkjhkj hhh dogs. 当我们使用 dog 进行匹配时,我们只希望匹配到第一个单词 dog,但是匹配的结果是 dog hjhjdogkjhkj hhh dogs.

这个时间就需要使用单词边界来限定了,单词边界由限定符 \b 表示。

当我们使用 \bdog\b 来匹配时,只会匹配到第一个 dog 单词。

字符串边界

  • ^ 匹配一个字符串的开始。
  • $ 匹配一个字符串的结尾。
  • (?m) 匹配一行的开始。

    使用子表达式

    子表达式是一个更大的表达式的一部分;把一个表达式划分为一系列子表达式的目的是为了把那些子表达式当作一个独立的元素来使用。子表达式必须使用 () 括起来。 譬如:使用

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

来匹配合法 IP 地址。 这个正则的意思是:

192.168.155.149

  • 任何一个1位或者2位数字 \d{1,2})
  • 任何一个以1开头的3位数字 (1\d{2})
  • 任何一个以2开头、第2位数字在0-4之间的3位数字 (2[0-4]\d)
  • 任何一个以25开头、第3位数字在0-5之间的3位数字 (25[0-5])
  • 然后拼接上 ..
  • 然后重复3次。
  • 最后拼接上一个1到3位数字,这个数字是:
    • 任何一个1位或者2位数字 \d{1,2})
    • 任何一个以1开头的3位数字 (1\d{2})
    • 任何一个以2开头、第2位数字在0-4之间的3位数字 (2[0-4]\d)
    • 任何一个以25开头、第3位数字在0-5之间的3位数字 (25[0-5])

回溯引用:前后一致匹配

先看一个正则表达式: [ ]+(\w+)[ ]+\1 这个表达式的意思是首先 [ ]+ 匹配一个或者多个空格,然后 (\w+) 匹配一个或者多个字母或者数字字符,然后 [ ]+ 匹配一个或者多个空格,最后 \1 的意思是一个回溯引用,它引用的是前面的表达式的第一个表达式。同理 \2 表示引用的是前面第2个表达式,\3 表示引用的是前面第3个表达式.

(Y)(N).+\2 这个表达式的意思是: 首先匹配一个字母Y,然后匹配一个字母N,然后匹配一个或者多个任意字符,最后的 \2 表示引用之前的第2个表达式,这里引用的就是 (N) 表达式。

这种引用是基于位置的引用,如果表达式的位置发生变化,可能就导致引用失效。

现在一些比较新的正则表达式支持“命名捕获”,支持位子表达式起一个唯一的名字,可以重复使用。

回溯引用在替换操作中的作用

在替换操作中,回溯引用可以起到很大的作用。 譬如下面一段文本

186-123-425
345-456-132
132-345-234

是以XXX-XXX-XXX 的形式来展示的,如果想改成 (XXX)(XXX)(XXX) 的话,只需要先使用 (\d{3})(-)(\d{3})(-)(\d{3}) 把之前的符合替换规则的文本找出来,然后使用 ($1)($3)($5) 进行替换就行了。

(186)(123)(425)
(345)(456)(132)
(132)(345)(234)
大小写装换
元字符 说明
\E 结束 \L 或者 \U 装换
\l 把下一个字符装换为小写
\L \L\E 之间的字符全部装换为小写
\u 把下一个字符装换大写
\U \U\E 之间的字符全部装换为大写

下面是一段 html 文本:

<html>
<head>
<title>Head First Lounge</title>
</head>
<body>
<h1>Welcome to the New and Improved Head First Lounge</h1>

<p><img src="http://img.sootuu.com/vector/2006-4/2006420114643989.jpg"></p>

</body>
</html>

当我们想把 h1标签中的文本全部改为大写时,可以这样操作。

(<h1>)(.*?)(</h1>)

然后

$1\U$2\E$3

前后查找

向前查找

从语法上看,一个向前查找模式其实就是一个以 ?= 开头的子表达式,需要匹配的文本跟在 = 后面。

文本:
http://www.baidu.com
https://yunissong.github.io/

当使用 .+(?=:) 查找时,: 前面的 http https 会被匹配出来。

向后查找

向后查找的操作符是 ?<=

文本:
http://www.baidu.com
https://yunissong.github.io/

当使用 (?<=:).+ 查找时, : 后面的 //www.baidu.com //yunissong.github.io/ 会被匹配出来。

前后查找

<h1>welcome to the New and Improved Head First Lounge</h1>

使用 (?<=<h1>)(.*?)(?=</h1>),就能把 h1 标签之间的内容匹配出来。

对前后查找取非

之前看到过对字符集合取非使用的是 ^ 操作符。但是对去前后查找,取非是使用 ! 来替换 = 取非。

操作符 说明
(?=) 正向前查找
(?!) 负向前查找
(?<=) 正向后查找
(?<!) 负向后查找

嵌入条件

(123)456-7890 和 123-456-7890 都是可以接受的北美号码,而1234567890、(123)-456-7890和(123-456-7890)虽然包含了正确的数字,但是格式不对,如果我们使用下面的正则来匹配:

文本:

123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890
1234567890
123 456 7890

正则:

\(?\d{3}\)?-?\d{3}-\d{4}

这段正则的意思:

  • \(? 匹配了一个可选左括号;
  • \d{3} 匹配了3位数字;
  • \)? 匹配了一个可选右括号
  • -? 匹配了一个可选的连字符;
  • \d{3} 匹配了3位数字;
  • - 匹配了一个连字符;
  • \d{4} 匹配了4位数字。

符合匹配的结果有:

123-456-7890
(123)456-7890
(123)-456-7890
(123-456-7890

并不能排除不正确格式的电话号码。

正则表达式里的条件

正则表达式里的条件要用 ? 来定义。 之前我们已经见过几种特定的条件了。

  • ? 匹配前一个字符或者表达式,如果它存在的话;
  • ?=?<= 匹配前面或者后面的文本,如果它存在的话;
  • 嵌入语法也使用了?
    • 根据一个回溯引用来进行条件处理。
    • 根据一个前后查找来进行条件处理。

回溯引用条件

回溯引用条件只有在前面一个表达式搜索取得成功的情况下才允许使用的一个表达式。

需要把文本里面 <IMG> 标签全都找去来,不仅如此,如果某个 <IMG> 标签是一个链接 (<A> 标签包裹),还需要把链接匹配出来。

文本:

<!-- Nav bar -->
<TD>
  <A href="/home"><IMG src="/images/home.gif"></A>
  <IMG src="/images/home.gif">
  <A href="/home"><IMG src="/images/home.gif"></A>
  <IMG src="/images/home.gif">
  <A href="/home"><IMG src="/images/home.gif"></A>
  <IMG src="/images/home.gif">
</TD>

正则: (<[Aa]\s+[^>]+>\s*)?<[Ii][Mm][Gg]\s+[^>]+>(?(1)\s*</[Aa]>)

解析:

  • (<[Aa]\s+[^>]+>\s*)? 将匹配一个 <A><a> 标签,以及标签内部的任何属性,这个标签可有可无,因为后面跟了一个
  • <[Ii][Mm][Gg]\s+[^>]+> 匹配一个 (大小写均可) 标签以及任意属性。
  • (?(1)\s*</[Aa]>) 是一个回溯引用条件,?(1) 的意思是:如果第一个回溯引用存在这个例子中就是前面的 <A> 标签),则使用 \s*</[Aa]> 继续匹配。只有前面的 <A> 标签匹配成功,才继续进行后面的匹配。如果(1) 存在,\s*</[Aa]> 将匹配 结束标签 </A> 之前的任意空白字符。
回溯引用条件否表达式

形式: (?(backreference)true-regex|false-regex) 如果 backreference 成立 执行 true-regex ,不成了,执行 false-regex。

!!!! 本来想用这个查找下代码里面有多少是换行后的{ 多少是空格后的{,Xcode 竟然不支持条件查找!!!.

再来看之前那个电话号码的例子,正则改为: (\()?\d{3}(?(1)\)|-)\d{3}-\d{4}

解析:

  • (\()? 匹配了一个可选左括号;
  • \d{3} 匹配了3位数字;
  • (?(1)\)|-)
    • ?(1) 判断前面的条件是否成立,( 是否存在。
    • 存在 执行 \)
    • 不存在,执行 -
  • \d{3} 匹配了3位数字
  • - 匹配了一个连字符;
  • \d{4} 匹配了4位数字。

前后查找条件

其实就是之前的回溯引用差不多,不同的就是,这里的回溯条件使用了自定义的子正则表达式。

d{5}(?(?=-)-\d{4})

(?(?=-)-\d{4}) 的意思是如果 (?=-) 成立,则进行后面 -\d{4} 的匹配。