使用Matlab处理字符串及文本时,需要与正则表达式打交道。正则表达式是一个非常重要的编程概念,主流的编程语言都对正则表达式进行了很好的支持,Matlab也不例外。
本期推文就让我们来总结一下Matlab提供的正则表达式吧!
1 引言
正则表达式(Regular Expression),又称规则表达式,本质上是一串字符,它定义了某种字符串模式,通常被用来检索、替换那些符合某个模式(规则)的文本。
Matlab 提供的正则表达式库函数主要有三个:
(1) regexp---用于对字符串进行查找,大小写敏感;
(2) regexpi---用于对字符串进行查找,大小写不敏感;
(3) regexprep---用于对字符串进行查找并替换。
下面主要介绍一下 regexp 这个函数的主要用法:
用法1:
[startIndex, endIndex] = regexp(str, expression) 返回所有匹配项的开始和结束索引。
用法2:
out = regexp(str, expression, outkey) 返回 outkey 指定的输出。例如,如果 outkey 为 'match',则 regexp 返回与该表达式匹配的子字符串而非其开始索引。outkey通常的取值有:
输出关键字返回
start 默认值所有匹配项的开始,startIndex
end所有匹配项的结束,endIndex
tokenExtents所有表文的开始和结束索引,和tokens一起用,指示出现tokens的位置
match与 expression 中的模式匹配的每个子字符串的文本
tokensstr 中每个捕获的标文的文本,即表达式中标记(tokens)的字串
names为匹配到的命名标记的标记名
splitstr 的非匹配子字符串的文本
用法3:
[out1, ..., outN] = regexp(str, expression, outkey1, ..., outkeyN) 按指定的顺序返回多个输出关键字指定的输出。例如,如果指定 match、tokens,则 regexp 返回与整个表达式匹配的子字符串以及与部分表达式匹配的标文。
用法4:
___ = regexp(___, option1, ..., optionM)使用指定的选项标志修改搜索。例如,指定 'ignorecase' 以执行不区分大小写的匹配。可以包括任何输入并请求之前语法中的任何输出。
option - 搜索选项覆盖说明
allonce尽可能多次地匹配表达式(默认值),或仅匹配一次。
nowarningswarnings取消警告(默认值),或显示警告。
matchcaseignocase匹配字母大小写(默认值),或忽略大小写。
noemptymatchemptymatch忽略零长度匹配项(默认值),或包括这些匹配项。
dotalldotexceptnewline将点与任意字符匹配(默认值),或与除换行符 (\n) 之外的所有字符匹配.
stringanchorslineanchors将 ^ 和 $ 元字符应用于字符向量的开头和结尾(默认值),或应用于行开头和结尾。换行符 (\n) 指定行的结尾。行的开头指定为第一个字符,或紧跟在换行符后面的任何字符。
literalspacingfreespacing在匹配时包括空格字符和注释(默认值),或忽略它们。借助 freespacing,使用 '\ ' 和 '#' 匹配空格和 # 字符。
用法5:
___ = regexp(___, 'forceCellOutput')以标量元胞的形式返回每个输出参数。元胞包含被描述为上述语法输出的数值数组或子字符串。可以包括任何输入并请求之前语法中的任何输出。
2 单个字符匹配
正则表达式,指定为字符向量、字符向量元胞数组或字符串数组。
每个表达式可包含字符、元字符、运算符、标文和用于指定在 str 中匹配的模式的标志。
2.1 元字符
元字符表示字母、字母范围、数字和空格字符。使用它们来构造广义的字符模式。
. 匹配任何单个字符,包括空格和换行符。例..ain 与以 ain 结尾的五个连续字符序列匹配。
[] 匹配括号内的任意字符,$, |, ., *, +, ?, -这些字符按字面匹配。例[rp.]ain'与 rain、pain 或 .ain'匹配。
[^] 匹配除括号内字符的其它任意字符。例[^*rp]ain 与以 ain结尾的所有由四个字母组成的序列(rain、pain'和 *ain 除外)匹配。如,它与 gain、lain 或 vain匹配。
[a-b] 匹配a-b范围中的任意字符,如[a-z], A-Z, [0-9], A-y, [10-99]。
\w 匹配任意英文字母、数字、下划线,等价于 [a-z_A-Z0-9]或 [a-zA-Z_0-9]。例 \w*标识一个单词。
\W 匹配任意非英文字母、数字、下划线,等价于 [^a-zA-Z_0-9]。例 \W*标识非单词项。
\s 匹配任意非空白(空格)字符,等价于 [\f\n\r\t\v]。 如 \w*n\s 与以字母 n 结尾且后跟空白字符的单词匹配。
注: 字符表示
运算符
\a警报(蜂鸣)
\b退格符
\f换页符
\n换行符
\r回车符
\t水平制表符
\v垂直制表符
\S 匹配任意非空白字符,等价于 [^ \f\n\r\t\v]。例 \d\S 与数字(后跟任意非空白字符)匹配。
\d 匹配任意数字,等价于 [0-9]。例 \d* 与任意数量的连续数字匹配。
\D 匹配任意非数字,等价于 [^0-9]。例 \w*\D\> 与不以数字结尾的单词匹配。
oN或\o{N} 匹配八进制数N对应的Unicode字符。例 \o{40} 与八进制 40 定义的空格字符匹配。
xN或\x{N} 匹配十六进制数N对应的Unicode字符。例 \x2C 与十六进制 2C 定义的逗号字符匹配,[\x4e00-\x9fa5] 匹配任意汉字。
2.2 特殊字符
\char 匹配表达式中的特殊字符,进行转义。$,^,*,(,),+,=,{,},<,>,|,.,\,?, -。这些特殊字符前边加上 \ 将会对其转义,使得匹配他们的字面值。
3 字符串匹配
3.1 多次匹配
元字符及其特殊字符的匹配,每次只能匹配一个字符,如果需要匹配多个字符,即字符串的匹配,那么就要重复好几次元字符的表达式。
比如,匹配 mmm,那么就可以用正则表达式 mmm, 但还有一种更简单的表示法 m{3}。其中 {}表示匹配前面的表达式出现的次数,称为限定符,m{2, 3} 匹配 mm 和 mmm。
除了 {} 限定符,还有其他常用的一些限定符,描述如下:
expr* 与expr匹配的元素出现0或更多次,相当于{0, }。例 \w*与任意长度的单词匹配。
expr? 与expr匹配的元素出现0或1次,相当于{0, 1}。例 \w*(\.m)? 与单词或以扩展名 .m 结尾(此条件为可选条件)的单词匹配。
expr+ 与expr匹配的元素出现1或更多次,相当于{1, }。例 <img src="\w+\.gif"> 与 <img> HTML 标记匹配(当文件名包含一个或多个字符时)。
expr{n} 与expr匹配的元素出现n次,相当于{n, n}。 例 \d{4} 与四个连续数字匹配。
expr{n, m} 与expr匹配的元素至少出现n次但不多于m次 。例 \S{4,8} 与四到八个非空白字符匹配。
expr{n, } 与expr匹配的元素至少出现n次。 例 <a href="\w{1,}\.html">'与 <a> HTML 标记匹配(当文件名包含一个或多个字符时)。
假设我们要在文本中搜索美国的社会安全号码,号码的格式是 000-00-0000,那么匹配它的正则表达式可以写为 \d{3}\-\d{2}\-\d{4}。 注意到 - 是特殊字符,所以用 \- 进行了转义。
如果希望字符号码连续出现,如000000000,也可以不出现,即 000-00-0000。上面两种形式都属于正确的格式,这时可以在字符 - 后面加上数量限定符 ? ,因此表达式可改写为 \d{3}\-?\d{2}\-?\d{4}\-?。
另外,当我们使用 expr*时,Matlab 将尽可能的匹配最长的字符字串,如
s='<tr valign=top><td><a name="19184"></a>xyz';
regexp(s,'<.*>','match')
输出:
{'<tr valign=top><td><a name="19184"></a>'}
如果我们希望匹配尽可能短的字符字串时,可以在上面使用的表达式后加个 ?, 即 .*?,如
s='<tr valign=top><td><a name="19184"></a>xyz';
regexp(s,'<.*?>','match')
输出:
{'<tr valign=top>'} {'<td>'} {'<a name="19184">'} {'</a>'}
上面的例子说明了对限定符的一种显示模式之一。
下面具体说明限定符的三种模式——限定修饰符,简单起见,用 q 表示上述6个限定符中的任意一个。
expr q 积极表达式:与尽可能多的字符匹配。如给定文本 <tr><td><p>text</p></td>,表达式 </?t.*> 匹配介于 <tr 和 /td> 之间的所有字符。
expr q? 消极表达式:与所需的尽可能少的字符匹配。如文本 <tr><td><p>text</p></td>,表达式 </?t.*?> 在第一次出现右尖括号 (>) 时结束每个匹配项:{'<tr>'} {'<td>'} {'</td>'}。
expr q+ 主动表达式:最大程度地匹配,但不重新扫描文本的任何部分。如给定文本 <tr><td><p>text</p></td>,表达式 </?t.*+>不返回任何匹配项,这是因为右尖括号是使用 .* 捕获的且不进行重新扫描。
3.2 零宽断言/环顾断言(左顾右盼)——利用上下文匹配
利用上下文匹配可以说非常常用,比如想要提取一段文本中特定的部分内容,那么零宽断言就非常适合干这类事情。
零宽断言查找紧邻预期匹配项前后但并非该匹配项一部分的模式。
指针停留在当前位置,并且将放弃或不捕获对应于 test 表达式的字符。因此,前向断言可匹配重叠字符组。
在介绍零宽断言之前,先介绍下定位点。表达式中的定位点与文本或单词的开头或结尾匹配。
^expr 匹配以expr开头的字符串。如 ^M\w* 与以 M 作为文本开头的单词匹配。
expr$ 匹配以expr结尾的字符串。如 '\w*m$ ' 与以 m 作为文本结尾的单词匹配。
\<expr 匹配以expr开头的英文单词。如 \<n\w* 与以 n 开头的任何单词匹配。
expr\> 匹配以expr结尾的英文单词。如 \w*e\> 与以 e 结尾的任何单词匹配。
回过头来,下面我们介绍零宽断言
expr(?=test) 先行断言:向前查找与test匹配的字符。即匹配后面跟的字符是test的字符。如 \w*(?=ing) 匹配后跟 ing 的词汇,匹配输入文本 Flying, not falling. 中的 Fly和 fall。特殊情况:expr(?=$)等价于expr$和 expr(?=\>) 等价于 expr\>。
expr(?!test) 负向先行断言:向前查找与test不匹配的字符。即匹配后面跟的字符不是test的字符。如 i(?!ng) 匹配字母 i 的后面不跟 ng 的字符。
(?<=test)expr 后发断言:向后查找与 test 匹配的字符。即匹配前面的字符有test的字符。如 (?<=re)\w* 匹配紧跟 re 的词汇,例如输入文本 renew, reuse, recycle 中的 new、use和 cycle。特殊情况:(?<=^)expr等价于 ^expr和 (?<=\<)expr等价于 \<expr。
(?<!test)expr 负向后发断言:向后查找与 test 不匹配的字符。即匹配前面的字符没有test的字符。如 (?<!\d)(\d)(?!\d) 与一位数字匹配(不紧随其他数字前后的数字)。
实际应用中,可以通过组合上面4中断言进行文本内容的提取,比如我们想要提取'<a href="www.baidu.com">' 文本中的网址,那么可以同时使用先行和后发断言,“左顾右盼”来提取网址内容:
s='<a href="www.baidu.com">';
regexp(s,'(?<=<a href=").*?(?=">)','match')
输出:
{'www.baidu.com'}。
3.3 逻辑条件匹配
如果我们在表达式之前指定先行断言,即(?=test)expr等,则运算等同于逻辑 AND。这一点比较容易混淆。
(?=test)expr 同时与 test 和 expr 匹配。如 (?=[a-z])[^aeiou] 与辅音匹配。
(?!test)expr 匹配 expr,但不匹配 test。如 (?![aeiou])[a-z] 与辅音匹配。
逻辑和条件运算符允许我们测试给定条件的状态,然后使用结果确定哪个模式(如果有)与下一条件匹配。这些运算符支持逻辑 OR、if 或 if/else 条件。
条件可以是标文、环顾运算符或 (?@cmd) 形式的动态表达式。动态表达式必须返回逻辑值或数值。
条件运算法:
expr1 | expr2 匹配表达式 expr1 或表达式 expr2。比如 (let|tel)\w+ 匹配以 let 或 tel 开头的单词。
(?(cond)expr) 如果条件 cond 为 true,则匹配 expr。如 (?(?@ispc)[A-Z]:\\) 匹配驱动器名称,例如 C:\(在 Windows® 系统上运行时)。
(?(cond)expr1 | expr2) 如果条件 cond 为 true,则匹配 expr1。否则,匹配 expr2。如 Mr(s?)\..*?(?(1)her|his) \w* 匹配包含 her 的文本(当文本以 Mrs 开头时),或包含 his 的文本(当文本以 Mr 开头时)。
4 标记(tokens)匹配
标记这部分是较难的一部分,但是使用得当可以实现非常强大的功能。
任何的正则表达式都可以用圆括号括起来作为一个标记。可以按标记在文本中的顺序引用该标记(顺序标记),或将名称分配给标记以便于代码维护和使输出更易于阅读。
(expr) 匹配expr并创建标记。如 Joh?n\s(\w*) 捕获一个标记,该标记包含名字为 John 或 Jon 的任何人的姓氏。
(?:expr) 匹配expr,不创建标记。
(?>expr) 匹配expr,不创建标记,不回溯扫描。
(expr1|expr2) 匹配expr1或者expr2,创建标记。
\N 匹配第N个标记,N从1开始。如 <(\w+).*>.*</\1> 从文本 <title>Some text</title> 捕获 HTML 标记的标文,例如 title。
(?(N)expr1|expr2) 如果找到第 N 个标文,则匹配 expr1。否则,匹配 expr2。如 Mr(s?)\..*?(?(1)her|his) \w*匹配包含 her 的文本(当文本以 Mrs开头时),或包含 his的文本(当文本以 Mr 开头时)。
(?<name>expr) 匹配 expr并创建命名为name的标记。 (?<month>\d+)-(?<day>\d+)-(?<yr>\d+) 在 mm-dd-yy 形式的输入日期中创建命名月、日和年标记。
\k<name> 匹配名为name的标记。<(?<tag>\w+).*>.*</\k<tag>>从文本 <title>Some text</title> 捕获 HTML 标记的标文,例如 title。
(?(name)expr1|expr2) 若存在名为name的标记,则匹配expr1,否则匹配expr2。如 Mr(?<sex>s?)\..*?(?(sex)her|his) \w* 匹配包含 her 的文本(当文本以 Mrs 开头时),或包含 his 的文本(当文本以 Mr 开头时)。
5 动态正则表达式
动态表达式允许我们执行 MATLAB 命令或正则表达式以确定要匹配的文本。
将动态表达式括起来的括号不创建捕获组。
(??expr) 解析 expr 并将得到的项包括在匹配表达式中。解析后,expr 必须对应于完整的有效正则表达式。使用反斜杠转义字符 (\) 的动态表达式需要两个反斜杠:一个用于 expr 的初始解析,一个用于完整匹配。如 ^(\d+)((??\\w{$1})) 通过读取匹配项开头的数字确定匹配的字符数。(\w,\W,\d,\D,\s,\S,和\char这些在动态表达式中需要再加个\)。动态表达式括在另一组括号中,以便在标记中捕获生成的匹配项。例如,匹配 5XXXXX 将捕获 5 和 XXXXX 的标文。
(??@cmd) 执行 cmd 表示的 MATLAB 命令,并将该命令返回的输出包括在匹配表达式中。(.{2,}).?(??@fliplr($1)) 查找长度至少为四个字符的回文,例如 abba。
(?@cmd) 执行 cmd 表示的 MATLAB 命令,但放弃该命令返回的任何输出。(对诊断正则表达式有帮助。)\w*?(\w)(?@disp($1))\1\w* 匹配包括双字母(例如 pp)的单词并显示中间结果。
在动态表达式中,使用下列运算符定义替代文本。
替代运算符
$&或$0当前作为匹配项的输入文本部分,即获取匹配成功的字串。
$`获取匹配成功的字符串前面部分
$'紧随当前匹配项的输入文本部分(使用 $''表示$')
$N获取第N个标记
$<name>获取名为name的标记
${cmd}在 MATLAB 执行命令 cmd 时返回的输出
注释:
(?#comment) 在正则表达式中插入注释。匹配输入时将忽略注释文本。如 (?# Initial digit)\<\d\w+ 包括一个注释,并匹配以一个数字开头的单词。
6 搜索标志
搜索标志修改匹配表达式的行为。在表达式中使用搜索标志的替代方法是传递 option 输入参数。
标志说明
(?-i)匹配字母大小写(regexp 和 regexprep 的默认值)。
(?i)不匹配字母大小写(regexpi 的默认值)。
(?s)将模式中的点 (.) 与任意字符匹配(默认值)。
(?-s)将模式中的点与并非换行符的任意字符匹配。
(?-m)匹配文本开头和结尾的 ^ 和 $ 元字符(默认值)。
(?m)匹配行开头和结尾的 ^ 和 $ 元字符。
(?-x)在匹配时包括空格字符和注释(默认值)。
(?x)在匹配时忽略空格字符和注释。使用 `\ ' 和 '#' 匹配空格和 # 字符。
该标志修改的表达式可显示在括号后,例如
(?i)\w*
或显示在括号内并使用冒号 (:) 与该标志分隔开,例如
(?i:\w*)
7 多行字符串与多正则表达式
7.1 多字符串与单个正则表达式匹配
多个字符串存在一个元胞数组里之后,每一个字符串与正则表达式匹配,返回值的维数与元胞数组的维数相同。
s= {'letter';'tell';'lethal';'television';'other'};
regexp(s,'(let|tel)\w+','match')
输出:
5x1的cell。
7.2 多个字符串与多个正则表达式匹配
这种情况下,应该满足字符串元胞数组中字符串的个数和正则表达式的个数相等,但维数不一定要相等。
如可以用 5x1的元胞数组与1x4的正则表达式相匹配。
s= {'letter';'tell';'lethal';'television';'other'};
expr= {'^t\w+?','\w+r?$','\<le\w+?','\w+er\>?','l.*r?'};
regexp(s,expr,'match')
7.3 多字符串的替换
这个功能是在匹配的基础上,在表达式后面加入要替换的字符串即可。
s= {'letter';'tell';'lethal';'television';'other'};
s1=regexprep(s,'(.)\1','**','ignorecase');
输出:
8 结语
使用正则表达式的过程中,如果是每天与它打交道,也就很容易记住这些规则,孰能生巧,然而长时间不使用正则表达式,就会渐渐的生疏。
因此推文总结了正则表达式常用的用法和一些相关示例说明,没事干的时候就刷一刷,帮助记忆、理解。同时,希望能够帮助有相同需要的朋友。
参考文献
匹配正则表达式(区分大小写) - MATLAB regexp - MathWorks 中国
Luo H . MATLAB GUI设计学习手记 (第3版)[J]. 2014.