正则表达式

正则表达式

正则表达式是对字符串类型数据进行匹配判断,提取等操作的一套逻辑公式。

处理字符串类型数据方面,高效的工具有Perl和Python。如果我们只是偶尔接触文本处理任务,则学习Perl无疑成本太高;如果常用Python,则可以利用成熟的正则表达式模块:re库;      如果常用R,则使用Hadley大神开发的stringr包则已经能够游刃有余。

正则表达式

“正则表达式是描述一组字符串特征的模式,用来匹配特定的字符串。”
—— Ken Thompson

上面的这句话已经说明了什么是正则表达式。我把正则表达式看作一组有特殊语法的字符串,用来方便地从文本中匹配想要获得的内容。

R支持三种正则表达式模式:

1.默认的是POSIX 1003.2 extended regular expressions。

2.通过设置参数perl=TRUE的方式使用Perl-like regular expressions。

3.通过设置参数fixed=TRUE的方式把正则表达式中的字符串视为其字面含义匹配。

如果已经熟悉Perl语言的正则表达式,则可以跳过这一节,直接了解R所提供的一些正则表达式的函数即可。

特殊字符

以下字符在正则表达式中不是其字面含义,是有特殊含义的:

\ | ( ) [ ] { } ^ $ * + ? .

要想在正则表达式中使用这些特殊字符的字面含义,需要在其前面加转义符“\\”,例如想要匹配左侧圆括号则使用“\\(”。需要注意的是,R中的正则表达式的转义符需要两个。

字符 含义
.      匹配除换行符 \n 之外的任何单个字符
-     连字符 当且仅当在字符组[]的内部表示一个范围,比如[A-Z]
|     或(or)
( )      表示一个字符组,括号内的字符串将作为一个整体被匹配
[ ]      括号内的任意字符将被匹配
{ }     包含指示出现次数上下限的数值
/       正则表达式模式的开始或结尾
\     转义符

匹配字符串的边界,如字符串的开头、字符串的结尾、单词边界等。

字符含义
^ 字符串的开头
$ 字符串的结尾
\\b 英文单词的边界(perl模式)
\\B 非英文单词的边界(perl模式)

注:将 ^ 用作括号[]表达式中的第一个字符,则会对字符集求反

量词

量词放在字符的后面,来控制前面字符出现的次数。

字符含义
* 匹配0次或更多次 {0,}
+ 匹配1次或更多次 {1,}
? 匹配0次或1次   {0,1}
{N} 匹配N次
{N,} 至少匹配N次
{N,M} 至少匹配N次,但不超过M次

默认的情况下,这些量词都是贪婪的,它们试图匹配尽可能多的字符。要想让它们匹配尽可能少的字符,则可以在量词后加”?”来实现。举例来说,在正则表达式“e+”,会匹配到字符串“beef”中的“ee”,而“e+?”,会分别匹配到其中的两个“e”,作为两次独立的匹配。

字符类

字符类表示一类字符的集合,如“[abc]”表示a或b或c,使用“[]”把一些字符括起来代表其中的任意字符。除了“[]”之外,还有两个特殊的字符“^”和“-”:

字符在字符类中的含义
^ 若出现在类的开头,表示非此类中的字符
- 若不出现在类的开头和结尾,表示一个范围,从“-”之前的字符到“-”之后的字符

常用的字符类及其简称:

R默认正则简称Perl正则字符类含义
[[:digit:]] \\d [0-9] 数字
[^[:digit:]] \\D [^0-9] 非数字
[[:alpha:]] [a-zA-Z] [a-zA-Z] 英文字母
[[:alnum:]] [a-zA-Z\\d] [a-zA-Z0-9] 英文字母或数字
[[:blank:]] [ \\t] [ \\t] 空格、tab
[[:space:]] \\s [ \\t\\r\\n\\f\\v] 空白字符
[^[:space:]] \\S [^ \\t\\r\\n\\f\\v] 非空白字符
  \\w [a-zA-Z0-9_] 任意单词字符
  \\W [^a-zA-Z0-9_] 任意非单词字符

分组及向后引用

“()”用来分组,并可以使用后向引用“\\1”、“\\2”等来引用第一个括号中匹配到的内容、第二个括号中匹配到的内容等。例如,正则表达式“(a)(b)\\1”等价于正则表达式“aba”。

需要注意的是若在括号内的开头出现“?:”,则此分组不编号,不可引用。例如,正则表达式“(?:a)(b)\\1”等价于正则表达式“abb”。

代表字符组的特殊符号

代码含义说明
\w 字符串,等价于[:alnum:]
\W 非字符串,等价于[^[:alnum:]]
\s 空格字符,等价于[:blank:]
\S 非空格字符,等价于[^[:blank:]]
\d 数字,等价[0-9]
\D 非数字,等价于[^0-9]
\b Word edge(单词开头或结束的位置)
\B No Word edge(非单词开头或结束的位置)
\< Word beginning(单词开头的位置)
\> Word end(单词结束的位置)

匹配

R-base提供了grep,grepl,regexpr和regexec四个函数从字符串向量中提取符合正则表达式的匹配结果。它们的区别主要在返回的结果上。
这几个函数的两个主要参数:

  • pattern: 正则表达式
  • x/text: 待匹配的字符串或字符串向量
y1 = "Li lei likes Han meimei."
y2 = "David likes Han meimei."
y = c(y1, y2)
y1
y2
y

grep(pattern="Li lei", x=y1)
## [1] 1
grep(pattern="Li lei", x=y2)
## integer(0)

grepl(pattern="Li lei", x=y1)
## [1] TRUE
grepl(pattern="Li lei", x=y2)
## [1] FALSE

grep(pattern="Li lei", x=y)
## [1] 1
grepl(pattern="Li lei", x=y)
## [1]  TRUE FALSE

regexpr(pattern='l', text=y1)
## [1] 4
## attr(,"match.length")
## [1] 1
## attr(,"useBytes")
## [1] TRUE
gregexpr(pattern='l', text=y1)
## [[1]]
## [1] 4 8
## attr(,"match.length")
## [1] 1 1
## attr(,"useBytes")
## [1] TRUE
regexec(pattern='l', text=y1)
## [[1]]
## [1] 4
## attr(,"match.length")
## [1] 1

regexpr(pattern='l', text=y)
## [1] 4 7
## attr(,"match.length")
## [1] 1 1
## attr(,"useBytes")
## [1] TRUE
gregexpr(pattern='l', text=y)
## [[1]]
## [1] 4 8
## attr(,"match.length")
## [1] 1 1
## attr(,"useBytes")
## [1] TRUE
## 
## [[2]]
## [1] 7
## attr(,"match.length")
## [1] 1
## attr(,"useBytes")
## [1] TRUE
regexec(pattern='l', text=y)
## [[1]]
## [1] 4
## attr(,"match.length")
## [1] 1
## 
## [[2]]
## [1] 7
## attr(,"match.length")
## [1] 1

默认情况下,grep返回匹配到的字符串在字符串向量x中的下标位置,没有匹配到结果时返回integer(0);grepl返回与x等长的logical向量,表示是否与pattern匹配。

regexpr返回与text等长的integer向量,表示第一个匹配pattern的起始位置,-1表示不匹配,属性match.length表示对应匹配的匹配长度。

gregexpr返回与text等长的列表,列表的每个元素是相应字符串的匹配结果。与regexpr不同的是,gregexpr会在字符串中找到所有匹配的子串并把结果返回,称之为全局匹配。

regexec返回与text等长的列表,列表的每个元素是相应字符串的匹配结果。与regexpr相同,regexec只返回第一个匹配到的结果。

大小写敏感:

参数ignore.cases用来控制正则匹配时是否是大小写敏感的,默认是大小写敏感的。

grep(pattern='li lei', x=y, ignore.case = TRUE)
## [1] 1
grepl(pattern='li lei', x=y, ignore.case = TRUE)
## [1]  TRUE FALSE

返回匹配到的整个字符串:

grep函数可以通过设置参数value=TRUE,直接返回匹配到的整个字符串。

grep(pattern='Li lei', x=y, value=TRUE)
## [1] "Li lei likes Han meimei."

不匹配pattern:

grep函数的参数invert用来控制返回的结果是匹配正则表达式的或不匹配正则表达式的结果。默认invert=FALSE,返回的是匹配正则表达式的结果;当invert=TRUE时,返回的是不匹配正则表达式的结果。

grep(pattern='Li lei', x=y, invert = TRUE)
## [1] 2

提取

在R中,提取符合pattern的子字符串需要经过两个步骤:先使用regexpr / gregexpr/regexec匹配,再使用regmatches提取。
regmatches的两个主要参数:

  • x: 待匹配的字符串
  • m: regexpr / gregexpr/regexec对字符串x返回的匹配结果
s = 'aababaaaba'
# 返回第一个匹配的部分
m = regexpr(pattern='a+', text=s)
regmatches(x=s, m)
## [1] "aa"

# 返回每一个匹配的部分
mg = gregexpr(pattern='a+', text=s)
regmatches(x=s, mg)
## [[1]]
## [1] "aa"  "a"   "aaa" "a"

# 返回第一个匹配的部分
m = regexec(pattern='a+', text=s)
regmatches(x=s, m)
## [[1]]
## [1] "aa"

替换

R-base提供了两个替换子字符串的函数sub和gsub。把目标字符串中与pattern匹配的部分用replacement代替。这两个函数的不同之处在于gsub会把目标字符串中所有与pattern匹配的部分用replacement代替。

sub(pattern='Li lei', replacement='Xiao ming', x=y)
## [1] "Xiao ming likes Han meimei." "David likes Han meimei."
gsub(pattern='l', replacement='L', x=y)
## [1] "Li Lei Likes Han meimei." "David Likes Han meimei."

拆分字符串

strsplit函数可以使用正则表达式作为拆分标志来把字符串拆分成向量。需要注意的是strsplit返回的是列表。

# 目标字符串中的数字使用不定个数的空白分隔开
strsplit('1  2 3  4', '\\s+')
## [[1]]
## [1] "1" "2" "3" "4"

# unlist可以去掉list结构
unlist(strsplit('1  2 3  4', '\\s+'))
## [1] "1" "2" "3" "4"

举一个例子:

strings <- c(
  "apple", 
  "219 733 8965", 
  "329-293-8753", 
  "Work: 579-499-7527; Home: 543.355.3679"
)
phone <- "([0-9]{2})[- .]([0-9]{3})[- .]([0-9]{4})"

在上面的代码中,变量strings里保存的是一个字符串向量,我们的目标是想检验该向量中的每一个字符串是否包含有电话号码。显然,直接精确搜索是不可能的,因为每个电话号码的内容不尽相同。但是由于电话号码都有相似的格式,这时候正则表达式就派上了用处。

我们定义了一个正则表达式phone,就是为了抓取这样的格式:[0-9]表示我门要匹配一个数字,用花括号{2}则表示[0-9]重复两次,即开头有两个数字,[- .]表示数字之间的分隔符可以“-”也可以是“ ”和“.”。

最终我们用grep函数就可以得到如下结果:

> grep(phone,strings)
[1] 2 3 4

这个函数的意思是用在strings里用phone所保存的正则表达式进行匹配。显然第一个字符串中没有包含电话号码,而其后三个字符串都是有电话的,这和strings变量保存的实际情况相符。

通过这个例子,相信大家对正则表达式已经有了初步的理解,下面就归纳一些正则表达式的常用写法:

1.匹配字符串:

首先,一个括号代表匹配一个位置, 想匹配某个字母就可以用把该字符写到括号里,多个括号连在一起就可以匹配一个字符串:

比如[a]表示匹配字符'a',同理[\]表示匹配字符‘\’
[c][a][t]表示匹配"cat"字符串

当然实际上如果只是匹配一个字符,完全可以不用中括号,直接写出cat就好

中括号在下列情况下才是必须要加的:即有时候在某一位置可能会出现几种可能的字符。这时我们便将这些字符都写入一个括号中,表达一种“或”的关系。

比如[aoeiu]就表示匹配'a'或'o'或'i'或'o'或'u',也就是表示匹配一个元音字母
同理[0-9]表示匹配0-9之间的任何一个数,也就是表示匹配一个数字
[0123456789]也可以简写为[0-9],于是匹配一个年份就可以写成[0-9][0-9][0-9][0-9]

[0-9]这种写法显然就比[0123456789]更为简明,这在正则表达式里非常常见,字母也可以这样写:

[d-f]表示匹配'd'或'e'或'f'
值得注意的是,正则表达式是区分大小写的,于是[D-F]表示匹配'D'或'E'或'F'
如果想匹配任意一个大写字母就可以写成[A-Z]
如果相匹配任意一个字母就可以写成[a-zA-Z]

此外我们还需知道'.','^',等特殊字符的用法:

'.'表示匹配任何一个字符,即通配符
于是[....]就表示匹配任何一个长度为4的字符串,或者直接用....
'^'表示“非”,就是表示对后面内容的否定,即不包含哪个字符
比如[^0-9]就表示除了数字之外的任何字符

还有几种特殊的字母,也是为了方便大家使用:

比如\\d等价为[0-9],即任何一个数字,[0-9][0-9][0-9][0-9]也可写为dddd
\w等价为[^0-9a-zA-Z],即非数字非字母的任何一个字符
\s等价为[.]即任何一个字符

现在我们做一个练习,如果我们想匹配2016-10-18这种形式的日期格式,我们应该用什么样的正则表达式呢?

如果仔细阅读了以上的内容,相信这个不是很难,应为如下:

[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]
或者dddd-dd-dd

然而这样写是不是还是太繁琐了呢?如果我要匹配36个连续的数字,难道也把[0-9]或者'd'复制36遍吗?

其是我们完全可以用花括号内的数字告诉正则表达式我们想要重复的次数,用下列方式实现:

[0-9]{4}-[0-9]{2}-[0-9]{2}
或d{4}-d{2}-d{2}实现

这样是不是就简单多了?

花括号里也不仅仅只能是一个数字,还有其他用法:

比如a{2,6}就表示匹配"aa"或"aaaaaa"
a{2-4}就表示匹配"aa","aaa","aaaa"
colou{0,1}r就表示匹配"color","colour"

更为精妙的,花括号内的数字可以是开区间:

a{2,}表示匹配连续次数两次以上的'a',即"aa","aaa","aaaa"...
.{0,}表示匹配任何文本,因为这是一个通配符'.'重复任何次数,由于我们是从0到正无穷,所以即便是一个空文本也可以被匹配到

我们利用上面的写法就可以轻松匹配到一对双引号及里面的文本:
".{0,}"

但是上述写法可能匹配到 "sdf"sdf"sdf"这样的文本,即双引号里还有双引号。
这时候我们可以用"[^"]{0,}"保证找到的双引号里面没有包括其他任何双引号;
此外我们还可以用".*?"实现上述效果。本来".*?"中'.*'表示任何文本,但在其之后加上'?'之后就表示匹配最少的字符,故而就不会出现匹配的双引号里文本还具有双引号的问题了。

正则表达式提供了一些字符刻画常见的花括号区间方法,比如:

'*'表示{0,},于是任何文本也可以通过".*"匹配
'?'表示{0,1},比如colou?r也可以匹配"color","colour"
'+'表示{1,},于是匹配一个及以上的 数字可以用d+

其他:

cat|dog可以表示匹配"cat"或"dog"
Mon|Tues|Wednes|Thurs|Fri|Satur|Sun|day表示匹配一周内任何一天 

2.匹配段落:

^表示匹配行的开始位置
$表示匹配行的结束位置
^&表示一个空行
^.*& 表示匹配全文内容,因为行的开始符号也是一个字符,"."会匹配这个符号。找到单独的一行,可以使用 ^.*?$

3.正则表达式函数

3.1 grep()

我们之前使用了一个正则表达式函数grep(),下面就细细讲讲正则表达式函数:

text = c("to be", "or not to", "be it is", "a question")
pat = "be"
grep(pat, text)

这个函数很简单,结果如下:

> grep(pat, text)
[1] 1 3

返回的是一个向量,告诉我们原字符串的哪些下标的字符串匹配成功了。

我们也可以加一个value属性,返回的就是被匹配到的字符串组成的向量了。

> grep(pat, text, value=TRUE)
[1] "to be"    "be it is"

此外,用invert属性可以返回没有被匹配到的下标向量:

> grep(pat, text, invert = TRUE)
[1] 2 4

3.2 grepl()

> grepl(pat, text)
[1]  TRUE FALSE  TRUE FALSE

这个函数返回的是逻辑向量

3.3 regexpr()

> regexpr(pat, text)
[1]  4 -1  1 -1
attr(,"match.length")
[1]  2 -1  2 -1
attr(,"useBytes")
[1] TRUE

这个函数返回前两行表示匹配部分在字符串中的起始位置与长度,-1表示未匹配到。因为我们匹配的是"be"在"to be"中就是第四个位置开始匹配,然后持续两个长度,也就是返回值里的前两行第一个下标位置的返回值4和2。

3.4 gregexpr(pat, text)

> gregexpr(pat, text)
[[1]]
[1] 4
attr(,"match.length")
[1] 2
attr(,"useBytes")
[1] TRUE

[[2]]
[1] -1
attr(,"match.length")
[1] -1
attr(,"useBytes")
[1] TRUE

[[3]]
[1] 1
attr(,"match.length")
[1] 2
attr(,"useBytes")
[1] TRUE

[[4]]
[1] -1
attr(,"match.length")
[1] -1
attr(,"useBytes")
[1] TRUE

这个函数和上一个regexpr()很相似,只不过返回的是一个list。

模式替换与拆分函数

在这里我不会讲R语言的自带模式替换与拆分,因为在下篇文章里我会介绍stringr这个字符串处理的神包。

参考资料

[1] R帮助文档?grep,?regex,?regmatches

[2] 《R for Data Science》Chapter12 Strings.

[3] Perl正则表达式速查手册

[4] 《学习正则表达式》MichaelFitzgerald著, 王热宇 译,人民邮电出版社。

[5] http://juntai.me/2016/03/30/R中的正则表达式/

posted @ 2016-11-15 23:44  Little_Rookie  阅读(1673)  评论(1编辑  收藏  举报