1 正则基础知识
1.1 元字符和特殊字符
R中的元字符与其他编程语言有些许的不同,常用的元字符参见 表 1,表 2。
符号 | 描述 |
---|---|
. | 匹配除换行符”/n”以外的任意字符 |
\ | Z换义字符,匹配元字符时使用”\元字符\ |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
( ) | 提取匹配的字符串,即括号内的字符串看作一个整体 |
[ ] | 匹配方括号内的任意一个字符 |
{ } | 大括号前面的字符或表达式重复的次数:{n}表示重复n次,{n, }表示重复n到更多次,{n,m}表示重复n~m次 |
* | 前面的字符或表达式重复0次或更多次 |
+ | 前面的字符或表达式重复1次或更多次 |
? | 前面的字符或表达式重复0次或1次 |
符号 | 描述 |
---|---|
\d与\D | 匹配数字,匹配非数字 |
\s与\S | 匹配空白符,匹配非空白符 |
\w与\W | 匹配字母或数字或下划线或汉字,匹配非\w 的字符 |
\b与\B | 匹配单词的开头或结束的位置,匹配非\b 的位置 |
\h与\H | 匹配水平间隔,匹配非水平间隔 |
\v与\V | 匹配垂直间隔,匹配非垂直间隔 |
[^…] | 匹配除……以外的任意字符 |
在了解元字符和特殊字符之后,我们就可以利用它们书写一些简单的正则表达式了:
匹配有abc开头的字符串:
^abc
。-
匹配8位数字的QQ号码:
-
^\\d\\d\\d\\d\\d\\d\\d\\d$
。 -
^\\d{8}$
。
-
-
匹配1开头11位数字的手机号码:
-
^1\\d\\d\\d\\d\\d\\d\\d\\d\\d\\d$
。 ^1\\d{10}$
-
匹配以a开头、0个或多个b结尾的字符串:
^ab*b$
1.2 POSIX字符类
在模式中方括号内可以用[:alpha:]
表示任意一个字母。 比如,[[:alpha:]]
匹配任意一个字母(外层的方括号表示字符集合, 内层的方括号是POSIX字符类的固有界定符)。
1.3 分组
上面的例4中,限定符 * 是作用在它左边最近的一个字符,如果想ab
同时被*
限定那怎么办呢?
正则表达式中用小括号
()
来做分组,也就是括号中的内容作为一个整体。
因此当我们要匹配多个ab
时,我们可以这样写:^(ab)*$
1.4 转义
我们看到正则表达式用小括号来做分组,那么问题来了:
如果要匹配的字符串中本身就包含小括号,那是不是冲突?应该怎么办?
针对这种情况,正则提供了转义的方式,也就是要把这些元字符、限定符或者关键字转义成普通的字符,做法很简答,就是在要转义的字符前面加个斜杠,也就是\即可。值得注意的是,R语言中的转义符号为\\
。 如:要匹配以(ab)开头,需要用如下表达式:^(\\(ab\\))$
1.5 条件-或
回到我们刚才的手机号匹配,我们都知道:国内号码都来自三大网,它们都有属于自己的号段,比如联通有130/131/132/155/156/185/186/145/176等号段,假如让我们匹配一个联通的号码,那按照我们目前所学到的正则,应该无从下手的,因为这里包含了一些并列的条件,也就是”或”,那么在正则中是如何表示”或”的呢?
正则表达式中用符号
|
来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。
那么我们可以使用或条件来处理这个问题:
^(130|131|132|155|156|185|186|145|176)\d{8}$
1.6 区间
看到上面的例子,我们发现还是有些复杂,是否可以再简化?
实际上,
正则表达式中提供了一个元字符
[]
表示区间,来匹配方括号内的任意字符。
- 限定0到9可以写成
[0-9]
。- 限定字母A到Z可以写成
[A-Z]
。- 限定某些数字
[165]
。
那么上述的联通电话号码的正则表达式可以简化为:
^(13[0-2]|15[5-6]|18[5-6]|145|176)\d{8}$
以上就是正则表达式的基本用法。下面我们需要了解一些进阶的用法。
2 正则进阶知识点
2.1 零宽断言
零宽断言可划分为两个词:零宽和断言。
- 断言:就是我断定什么什么,即指明在指定的内容前面或后面会出现满足指定规则的内容。例如,
ss1aa2bb3
,正则可以用断言找出aa2
后面有bb3
,也可以找出aa2
前面有ss1
。 - 零宽:就是没有宽度 ,表明断言只是匹配位置,不占字符,即匹配结果不会返回断言本身。
了解了定义后,我们还是通过例子来简单说明。
假设我们要用爬虫抓取csdn里的文章阅读量。通过查看源代码可以看到文章阅读量这个内容是这样的结构:
“<span class=”read-count”>阅读数:641</span>”
其中,641
这个是变量,对应不同文章的不同值。当我们拿到这个字符串时,需要获得其中641
的部分的正则表达式该如何匹配呢?
2.1.1 先行断言
- 语法:
?=pattern
。
- 作用:匹配
pattern表达式
前面的内容,不返回本身。
回到例子本身,我们要匹配641这个数字,意味着我们要匹配</span>
前面的数字内容。
string_test <- "<span class=\"read-count\">阅读数:641</span>"
str_extract(string_test, ".+(?=</span>)")
[1] "<span class=\"read-count\">阅读数:641"
好像不对,我们只想要641的部分。
str_extract(string_test, "\\d+(?=</span>)")
[1] "641"
2.1.2 后行断言
有先行就有后行,先行是匹配前面的内容,那后行就是匹配后面的内容啦。 我们注意到,641
也可以是表达式之后的内容,同样也可以用后行断言来处理。
- 语法:
(?<=pattern)
。 - 作用:匹配
pattern
表达式之后的内容,同样不返回本身。
str_extract(string_test, "(?<=<span class=\"read-count\">阅读数:)\\d+")
[1] "641"
2.2 贪婪匹配和懒惰匹配
无上限的重复匹配如*
, +
, {3,}
等缺省是贪婪型的, 重复直到文本中能匹配的最长范围。 比如我们希望找出圆括号这样的结构, 很容易想到用\(.+\)
这样的模式(注意圆括号是元字符,需要用反斜杠保护), 但是这不会恰好匹配一次, 模式会一直搜索到最后一个)
为止。
例如:
str_view_all("(1st) other (2nd)", "\\(.+\\)")
[1] │ <(1st) other (2nd)>
我们本来期望的是提取两个”(1st)“和”(2nd)“组合, 不料整个地提取了”(1st) other (2nd)
“。 这就是因为.+
的贪婪匹配。 如果要求尽可能短的匹配, 使用*?
, +?
, {3,}?
等”懒惰型”重复模式。 在无上限重复标志后面加问号表示懒惰性重复。 那么上述的正则表达式可以改写为:
str_view_all("(1st) other (2nd)", "\\(.+?\\)")
[1] │ <(1st)> other <(2nd)>
值得注意的是,懒惰匹配会造成搜索效率的降低,应仅在需要的时候使用。
2.3 分组与捕获
在正则表达式中用圆括号来分出组, 作用是
- 确定优先规则;
- 组成一个整体;
- 拆分出模式中的部分内容(称为捕获);
- 定义一段供后续引用或者替换。
圆括号中的模式称为子模式,或者捕获。
2.3.1 确定优先级
在使用备择模式时,James|Jim
是在单词James和Jim之间选择。 如果希望选择的是中间的mes和Ji怎么办? 可以将备择模式保护起来, 如Ja(mes|Ji)m
, 就可以确定备择模式的作用范围。
正则表达式有比较复杂的优先级规则, 所以在不确定那些模式组合优先采纳时, 应该使用圆括号分组, 确定运算优先级。
2.3.2 组成整体
元字符问号、加号、星号、大括号等表示重复, 前面的例子中都是重复一个字符或者字符类。 如果需要重复由多个字符组成的模式, 如x[[:digit:]]{2}
怎么办? 只要将该模式写在括号中,如:
str_view_all(c("x01x02", "_x11x9"), "(x[[:digit:]]{2})+")
[1] │ <x01x02>
[2] │ _<x11>x9
str_extract(c("x01x02", "_x11x9"), "(x[[:digit:]]{2})+")
[1] "x01x02" "x11"
上例的元数据中, 第一个元素重复了两次括号中的模式, 第二个元素仅有一次括号中的模式。
2.4 捕获与向后引用
有时一个模式中部分内容仅用于定位, 而实际有用的内容是其中的一部分, 就可以将这部分有用的内容包在圆括号中作为一个捕获。