36.5 利用基本R函数进行正则表达式处理
基本R函数grep, sub, gsub, regexpr, gregexpr, regexec中的
pattern参数可以是正则表达式,
这时应设参数 fixed=FALSE。
strsplit函数中的参数split也可以是正则表达式。
regmatches函数从regexpr, gregexpr,
regexec的结果中提取匹配的字符串。
以原样匹配为例。
x
regexpr("the", x, perl=TRUE)
## [1] 5 -1 4
## attr(,"match.length")
## [1] 3 -1 3
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
这里使用了regexpr函数。
regexpr函数的一般用法为:
x
regexpr(pattern, text, ignore.case = FALSE, perl = FALSE,
fixed = FALSE, useBytes = FALSE)
自变量为:
pattern 是一个正则表达式,如果用了fixed=TRUE选项,则当作普通原样文本来匹配;
text 是源字符串向量,要从其每个元素中查找pattern模式出现的位置;
ignore.case:是否要忽略大小写匹配;
perl 选择是否采用perl格式,如果不把pattern当作普通原样文本,应该选perl=TRUE,perl语言的正则表达式是事实上的标准,所以这样兼容性更好;
fixed 当fixed=TRUE时pattern作为普通原样文本解释;
useBytes 为TRUE时逐字节进行匹配,否则逐字符进行匹配。之所以有这样的区别,是因为有些编码中一个字符由多个字节构成,BGK编码的汉字由两个字节组成,UTF-8编码的汉字也是由两个字节构成。
regexpr()函数返回一个整数值的向量,
长度与text向量长度相同,
结果的每个元素是在text的对应元素中pattern的首次匹配位置;
没有匹配时结果元素取-1。
结果会有一个match.length属性,表示每个匹配的长度,
无匹配时取-1。
如果仅关心源字符串向量text中哪些元素能匹配pattern,
可以用grep函数,如
x
grep("the", x, perl=TRUE)
## [1] 1 3
结果说明源字符串向量的三个元素中仅有第1、第3号元素能匹配。
如果都不匹配,返回integer(0)。
grep可以使用与regexpr相同的自变量,
另外还可以加选项invert=TRUE,这时返回的是不匹配的元素的下标。
grep()如果添加选项value=TRUE,
则结果不是返回有匹配的元素的下标而是返回有匹配的元素本身(不是匹配的子串),
如
x
grep("the", x, perl=TRUE, value=TRUE)
## [1] "New theme" "In the present theme"
grepl的作用与grep类似,
但是其返回值是一个长度与源字符串向量text等长的逻辑型向量,
每个元素的真假对应于源字符串向量中对应元素的匹配与否。如
x
grepl("the", x, perl=TRUE)
## [1] TRUE FALSE TRUE
就像grep()与grepl()本质上给出相同的结果,只是结果的表示方式不同,
regexec()与regexpr()也给出仅在表示方式上有区别的结果。
regexpr()主要的结果是每个元素的匹配位置,
用一个统一的属性返回各个匹配长度;
regexec()则返回一个与源字符串向量等长的列表,
列表的每个元素为匹配的位置,并且列表的每个元素有匹配长度作为属性。
所以,这两个函数只需要用其中一个就可以,下面仅使用regexpr()。
regexec()的使用效果如
x
regexec("the", x, perl=TRUE)
## [[1]]
## [1] 5
## attr(,"match.length")
## [1] 3
## attr(,"useBytes")
## [1] TRUE
##
## [[2]]
## [1] -1
## attr(,"match.length")
## [1] -1
## attr(,"useBytes")
## [1] TRUE
##
## [[3]]
## [1] 4
## attr(,"match.length")
## [1] 3
## attr(,"useBytes")
## [1] TRUE
grep(), grepl(), regexpr(), regexec()都只能找到源字符串向量的每个元素中模式的首次匹配,
不能找到所有匹配。
gregexpr()函数可以找到所有匹配。
如
x
gregexpr("the", x, perl=TRUE)
## [[1]]
## [1] 5
## attr(,"match.length")
## [1] 3
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
##
## [[2]]
## [1] -1
## attr(,"match.length")
## [1] -1
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
##
## [[3]]
## [1] 4 16
## attr(,"match.length")
## [1] 3 3
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
其结果是一个与源字符串向量等长的列表,
格式与regexec()的结果格式类似,
列表的每个元素对应于源字符串向量的相应元素,
列表元素值为匹配的位置,
并有属性match.length保存了匹配长度。
匹配位置和匹配长度包含了所有的匹配,
见上面例子中第三个元素的匹配结果。
函数grep, grepl结果仅给出每个元素能否匹配。
regexpr(), regexec(), gregexpr()则包含了匹配位置与匹配长度,
这时,可以用regmatches()函数取出具体的匹配字符串。
regmatches()一般格式为
regmatches(x, m, invert = FALSE)
其中x是源字符串向量,
m是regexpr()、regexec()或gregexpr()的匹配结果。
如
x
m
regmatches(x, m)
## [1] "the" "the"
可以看出,regmatches()仅取出有匹配时的匹配内容,
无匹配的内容被忽略。
取出多处匹配的例子如:
x
m
regmatches(x, m)
## [[1]]
## [1] "the"
##
## [[2]]
## character(0)
##
## [[3]]
## [1] "the" "the"
当regmatches()第二个自变量是gregexpr()的结果时,
其输出结果变成一个列表,
并且不再忽略无匹配的元素,
无匹配元素对应的列表元素为character(0),
即长度为零的字符型向量。
对有匹配的元素,
对应的列表元素为所有的匹配字符串组成的字符型向量。
实际上,
如果pattern中没有正则表达式,
grep(), grepl(), regexpr(), gregexpr()
中都可以用fixed=TRUE参数取代perl=TRUE参数,
这时匹配总是解释为原样匹配,
即使pattern中包含特殊字符也是进行原样匹配。
36.5.1 不区分大小写匹配
在基本R中,
为了不区分大小写匹配,
可以在grep等函数调用时加选项ignore.case=TRUE;
如
grep("Dr", c("Dr. Wang", "DR. WANG", "dR. W.R."))
## [1] 1
grep("dr", c("Dr. Wang", "DR. WANG", "dR. W.R."), ignore.case=TRUE)
## [1] 1 2 3
grep("(?i)dr", c("Dr. Wang", "DR. WANG", "dR. W.R."))
## [1] 1 2 3
36.5.2 匹配单个字符
在模式中用“.”匹配任意一个字符(除了换行符"\n",能否匹配此字符与选项有关)。如
s
mres
## [1] 1 2 -1
## attr(,"match.length")
## [1] 3 3 -1
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
regexpr仅给出每个元素中模式的首次匹配位置而不是给出匹配的内容。
regmatches函数以原始字符型向量和匹配结果为输入,
结果返回每个元素中匹配的各个子字符串(不是整个元素),如:
regmatches(s, mres)
## [1] "abc" "abs"
注意返回结果和输入字符型向量元素不是一一对应的,仅返回有匹配的结果。
像句点这样的字符称为元字符(meta characters),
在正则表达式中有特殊函数。
如果需要匹配句点本身,用“[.]”或者“\.”表示。
比如,要匹配a.txt这个文件名,如下做法有错误:
grep("a.txt", c("a.txt", "a0txt"), perl=TRUE)
## [1] 1 2
结果连a0txt也匹配了。用“[.]”表示句点则将句点不做特殊解释:
grep("a[.]txt", c("a.txt", "a0txt"), perl=TRUE)
## [1] 1
grep("a\\.txt", c("a.txt", "a0txt"), perl=TRUE)
## [1] 1
注意在R语言字符型常量中一个\需要写成两个。
如果仅需按照原样进行查找,
也可以在grep(), grepl(),regexpr(),gregexpr()等函数中加选项fixed=TRUE,
这时不要再用perl=TRUE选项。
如
grep("a.txt", c("a.txt", "a0txt"), fixed=TRUE)
## [1] 1
36.5.3 匹配一组字符中的某一个
模式“[ns]a.[.]xls” 表示匹配的第一个字符是n或s,
第二个字符是a,第三个字符任意,第四个字符是句点,
然后是xls。
例:
regexpr("[ns]a.[.]xls", c("sa1.xls", "dna2.xlss", "na3.xls"), perl=T)
## [1] 1 2 1
## attr(,"match.length")
## [1] 7 7 7
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
36.5.4 原样匹配元字符
例:
grep("int x\\[5\\]", c("int x;", "int x[5]"), perl=TRUE)
## [1] 2
也可以用“[[]”表示“[”, 用“[]]”表示“]”,如
grep("int x[[]5[]]", c("int x;", "int x[5]"), perl=TRUE)
## [1] 2
36.5.5 匹配数字
例:
grep("n\\d[.]xls", c("n1.xls", "na.xls"), perl=TRUE)
## [1] 1
36.5.6 匹配开头和末尾
例:
grep("^n\\d[.]xls$", c("n1.xls", "na.xls", "cn1.xls", "n1.xlsx"), perl=TRUE)
## [1] 1
grep("\\An\\d[.]xls\\Z", c("n1.xls", "na.xls", "cn1.xls", "n1.xlsx"), perl=TRUE)
## [1] 1
只匹配了第一个输入字符串。
36.5.7 匹配字母、数字、下划线
例:
m
regmatches(c("file-s1.xls", "s#.xls"), m)
## [1] "s1."
可以看出,模式匹配了s1.而没有匹配s#.。
36.5.8 十六进制和八进制数
例如
gregexpr("\\x0A", "abc\nefg\n")[[1]]
## [1] 4 8
## attr(,"match.length")
## [1] 1 1
## attr(,"index.type")
## [1] "chars"
## attr(,"useBytes")
## [1] TRUE
匹配了两个换行符。
36.5.9 POSIX字符类
例如:
grep("[[:alpha:]_.][[:alnum:]_.]", c("x1", "_x", ".x", ".1"))
## [1] 1 2 3 4
36.5.10 加号重复匹配
例
s
mres
regmatches(s, mres)
## [1] "sa1" "sa123"
例如:
p
x
m
regmatches(x, m)
## [1] "abc123@efg.com"
匹配的电子邮件地址在@前面可以使用任意多个字母、数字、下划线,
在@后面由小数点分成两段,
每段可以使用任意多个字母、数字、下划线。
这里用了^和$表示全字符串匹配。
36.5.11 星号和问号重复匹配
^https?://[[:alnum:]./]+$可以匹配http或https开始的网址。
如
s
grep("^https?://[[:alnum:]_./]+$", s, perl=TRUE)
## [1] 1 2
(注意第二个字符串不是合法网址但是按这个正则表达式也能匹配)
36.5.12 计数重复
例:
grep("[[:digit:]]{3}", c("1", "12", "123", "1234"))
## [1] 3 4
模式匹配的是三位的数字。
日期匹配例:
pat
c("[[:digit:]]{1,2}[-/]",
"[[:digit:]]{1,2}[-/]",
"[[:digit:]]{2,4}"), collapse="")
grep(pat, c("2/4/1998", "13/15/198"))
## [1] 1 2
36.5.13 贪婪匹配和懒惰匹配
例如:
s 1st other 2nd"
p1 .*[Bb]>"
m1
regmatches(s, m1)[[1]]
## [1] "1st other 2nd"
我们本来期望的是提取第一个“……”组合, 不料提取了两个“……”组合以及中间的部分。
比如,上例中模式修改后得到了期望的结果:
s 1st other 2nd"
p2 .*?[Bb]>"
m2
regmatches(s, m2)[[1]]
## [1] "1st"
36.5.14 单词边界
例:
grep("\\bcat\\b", c("a cat meaos", "the category"))
## [1] 1
36.5.15 句点全匹配与多行模式
句点通配符一般不能匹配换行,如
s 1st\n\n"
grep(".*?[Bb]>", s, perl=TRUE)
## integer(0)
跨行匹配失败。而在HTML的规范中换行是正常的。
一种办法是预先用gsub把所有换行符替换为空格。
但是这只能解决部分问题。
另一方法是在Perl正则表达式开头添加(?s)选项,
这个选项使得句点通配符可以匹配换行符。
如
s 1st\n\n"
mres .*?[Bb]>", s, perl=TRUE)
regmatches(s, mres)
## [1] "1st\n"
多行模式例:
s 1st\n\n"
mres1 ", s, perl=TRUE)
mres2 ", s, perl=TRUE)
regmatches(s, mres1)[[1]]
## [1] ""
regmatches(s, mres2)[[1]]
## [1] "" ""
字符串s包含两行内容,中间用\n分隔。
mres1的匹配模式没有打开多行选项,
所以模式中的^只能匹配s中整个字符串开头。
mres2的匹配模式打开了多行选项,
所以模式中的^可以匹配s中每行的开头。
36.5.16 备择模式
例如,某个人的名字用James和Jim都可以,
表示为James|Jim, 如
s
pat
mres
regmatches(s, mres)
## [[1]]
## [1] "James"
##
## [[2]]
## [1] "Jim"
36.5.17 分组与捕获
例:
希望把“……”和“”删除,
可以用如下的替换方法:
x 1st other 2nd"
pat (.+?)[Bb]>"
repl
gsub(pat, repl, x, perl=TRUE)
## [1] "1st other 2nd"
替换模式中的\1(写成R字符型常量时\要写成\\)表示第一个圆括号匹配的内容,
但是表示选项的圆括号((?s))不算在内。
例:希望把带有前导零的数字的前导零删除,可以用如
x
pat
repl
gsub(pat, repl, x, perl=TRUE)
## [1] "123" "123" "123"
其中的\b模式表示单词边界,
这可以排除在一个没有用空格或标点分隔的字符串内部拆分出数字的情况。
例:为了交换横纵坐标,可以用如下替换
s
pat
"[(]([[:digit:].]+),",
"[[:space:]]*([[:digit:].]+)[)]")
repl
gsub(pat, repl, s, perl=TRUE)
## [1] "1st: (3.6, 5), 2nd: (1.1, 2.5)"
例如,要匹配yyyy-mm-dd这样的日期,
并将其改写为mm/dd/yyyy,
就可以用这样的替换模式:
pat
repl
gsub(pat, repl, c("1998-05-31", "2017-01-14"))
## [1] "05/31/1998" "01/14/2017"
分组除了可以做替换外,
还可以用来表示模式中的重复出现内容。
例如,([a-z]{3})\1这样的模式可以匹配如abcabc, uxzuxz这样的三字母重复。如
grep("([a-z]{3})\\1", c("abcabc", "aabbcc"))
## [1] 1
又例如,下面的程序找出了年(后两位)、月、日数字相同的日期:
x
m
regmatches(x, m)
## [1] "2008-08-08"
下面是一个非捕获分组示例。
设需要把1921-2020之间的世纪号删去,可以用
pat
repl
x
gsub(pat, repl, x, perl=TRUE)
## [1] "78" "17" "35"
其中用了非捕获分组使得备择模式19|20优先匹配。
注意模式并没有能保证日期在1921-2020之间。更周密的程序可以写成:
x
p1
r1
p2
x
x
x
## [1] "78" "17" "2035"