一、什么是通配符
通配符是一种特殊语句。在查找文件或文字内容时,如果不知道真正的字符或者不想提供完整的查询字符时,常常使用一些特殊符号来代替一个或多个实字符,这些符号就是“通配符”。基本通配符包括星号(*)和问号(?)。
“*” 表示多个实字符,“?” 表示一个实字符;使用通配符可以进行“模糊查询”。 |
二、什么是正则表达式
正则表达式就是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。
就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个表示某种规律或逻辑的“表达式”,使用这个“表达式”对字符串进行匹配或过滤。
按照约定的符号规则来表示具有一定规律的文字。可以认为是“特高级”的通配符。 |
三、正则表达式的作用
匹配、查找、替换字符,她可以代替累赘的运算。
例[1]: 判断一组字符串是不是合法的手机号码
- 分析手机号码特点
- 11位数字
- 第一个数字是1
- 第二个数字是3、5、8
- 前面允许是“0”(就变成12位)
- 使用正则表达式验证
^0?1[358]\d{9}$
以上正则表达式属于举例,暂时不需要理解
四、正则表达式的流派
1. Posix
(1). BRE
全称Basic Regular Expressions,基本正则表达式。
(2). ERE
全称Extended Regexp Expressions,扩展正则表达式。对BRE功能的扩展(增加了一些符号和表达式),可认为是BRE的加强版。
2. PCRE
全称 Perl Compatible Regular Expressions。最早出现在Perl语言中,后来被移植到各门编程语言里,成为内置的功能。
3.Posix和PCRE区别
- PCRE流派的正则表达式比Posix流派的功能强大;
- PCRE流派的正则表达式比Posix流派的性能更高;
- 两个流派的正表达式写法存在不同(例如:在Posix里,“[:digit:]”表示0~9的任意数字;在PCRE里,是用“\d”表示任意数字);
因为PCRE流派比Posix更被认可,所以有些编程语言(工具)就弃用了Posix流派的正则表达式,也有一些语言(工具)同时支持两种正则表达式。不过也有些早期的工具一直只沿用Posix流派的正则表达式。例如:
- PHP5.x以上的版本就弃用了Posix流派的正则表达式;
- Linux下的grep命令则支持BRE、ERE、PCRE多种正则表达式;
- Linux下的sed命令就只支持BRE、ERE的正则表达式;
- Linux下的awk命令就只支持ERE的正则表达式;
经验总结:
在使用一些标注支持正则表达式的工具命令,或在配置一些支持正则表达式的配置文件时,先确定这些工具命令或配置是支持哪些正则表达式。
五、PCRE正则表达式语法
正则表达式就是使用一套已约定的符号按照一定的规则来书写的,这些符号称为“元字符”。元字符可以分为:
1.转义符
“\”,在“\”后面加上一些特定的字母后,可以让“\”+“字母”的这个组合表示其他特殊的字符或字符组合。也可以在“\”后面加上符号,让这些符号回归为普通字符。
例[2]:
# 换行符
\n
# 所有空字符
\s
# 让“.”回归为普通字符
\.
# 让“[”回归为普通字符
\[
......
2.实字符
表示实际的字符内容。
字符 | 说明 | 备注/例子 |
---|---|---|
\n | 换行符 | |
\r | 回车符 | |
\t | 制表符 | |
\f | 分页符 | |
\\ | 斜杠本身 | |
\xAA | “AA”是16进制的ASCII码,表示该ASCII码对应的字符(“\x”后面的ASCII值只能是00~FF,即十进制 0~255)。 | 例如:\x20,“20”代表一个16进制的ASCII码(10进制就是32),所以\x20表示为一个空格 |
3.字符组合
表示一组实际字符中的任意一个字符。
字符 | 说明 | 备注/例子 |
---|---|---|
\s | 任意空字符 | 例如:\r \n \f \t 等 |
\S | 任意非空字符 | 就是 != \s |
. | 除了换行符外的所有字符 | 就是 != \n |
\w | 数字+字母+下划线 | 例如:a 8 _ |
\W | 除了数字、字母、下划线以外的字符 | 例如:, = ~ |
\d | 0~9任意数字 |
4.数词
数词不表示实际字符,用来表示在他前面的字符或字符组合数量。
字符 | 说明 | 备注/例子 | ||||||
---|---|---|---|---|---|---|---|---|
+ | 表示匹配≥1个 | 例如:A+,表示匹配1个以上的“A” | ||||||
* | 表示匹配≥0个 | 例如:A*,表示匹配多个“A”,但是也允许没有“A” | ||||||
? | 表示匹配0个或1个 | 例如:A?,表示匹配一个“A”,但是也允许没有“A” | ||||||
{} | 表示精确匹配的个数:
| 例如: A{3}:表示匹配“AAA”; A{2,}:表示匹配2个以上的“A”; A{2,4}:表示匹配2~4个“A”。 |
例[3]: 用以下各个正则表达式匹配字符串“hello world”。
正则表达式 | 表达式说明 | 匹配结果 |
---|---|---|
l+ | 匹配多个“l” | hello world |
l*d | 匹配多个“l”+“d”,或“d” | hello world |
wx? | 匹配“w”或“wx” | world |
l{2} | 匹配两个“l” | hello world |
l{2,} | 匹配两个以上的“l” | hello world |
5.小括号和中括号
字符 | 说明 | 备注/例子 |
---|---|---|
( ) | 使用小括号引用后,“()”里面的内容成为一个子匹配 (“子匹配”可以是一个字符,也可能是一串字符串) | 例如:(abc) |
(|) | 逻辑或,“()”内可以有多个“|”,“|”之间不能是空的没有内容 | 例如: (XXX|YYY|ZZZ):表示匹配“XXX”或“YYY”,或者“ZZZ”; (XXX||ZZZ):错误 |
[ ] | 匹配“[]”内的任意一个字符或字符组合(可理解为, 中括号引用是表示在特定可选范围内的一个字符)。 | 例如: [138]:等价于“(1|3|8)” [\w]:等价于“\w” [\w\d]:等价于“(\w|\d)”任意一个字符 [\w-]:等价于数字、字母、下划线和“-”任意一个字符 |
[X-Y] | 匹配从X到Y的字符。X和Y只能同时为0~9以内的 数字,或小写字母,或大写字母;而且X必须小于Y。 | 例如: [0-9]:等价于“\d” [a-z]:等价于(a|b|c|…z) [A-F]:等价于(A|B|C|D|E|F) [a-zA-Z]:等价于任意一个字母 [a-zA-Z\d_]:任意一个字母、数字、下划线,等价于\w [z-b]:错误 |
[^] | 匹配除了“[]”内所包含的字符以外的任意字符 | 例如: [^1]:除了“1”的任意一个字符 [^\d]:除了数字的任意一个字符 [^\s-]:除了空字符和“-”的任意字符 [^\S\s]:除了空字符和非空字符(这个表达式是没有内容 可匹配到的) [^1-3]:除了1~3的其他任意字符 |
子匹配:可以看作是一个整体的字符串。在子匹配后面使用“数词”,“数词”就作用于整个子匹配。
例-4:
(abc|123|h)+
# 可匹配多个“abc”或“123”,或“h”
(xi|lan|man|fei)(yang){2}
# “(xi|lan|man|fei)”就是匹配字符串“xi”、“lan”、“man”、“fei”,
# “(yang)”表示匹配“yang”这个字符串
# “(yang){2}”就是匹配两个“yang”
# 所以可匹配“xiyangyang”、“lanyangyang”、“manyangyang”、“feiyangyang”,
# 匹配不到“huitailang”。
[a-z]+
# 可配置多个小写字母
win[78]?
# “[78]”是匹配字符“7”或“8”,“[78]?”表示7/8是可有可没有,
# 所以就是配置win、win7或win8
经验总结1: 如果“|”不在“()”里面,那么只对其左右的两边字符或子匹配进行“or”判断
例[5]:
(abc|123)
# 可匹配“abc”或“123”
abc|123
# 匹配“abc23”或“ab123”
经验总结2: “[^]”是表示,除了“[]”内包含的任意单个字符;而不是表示不等于“[]”内的字符串
例[6]: 匹配“win”字符后面不等于“xp”的字符串
win[^xp]
# 这是不对的,这个正则表达式是匹配“win”后面不是“x”也不是“p”的字符串
6.位置符
位置符不表示实际字符,他只表示的起始或结束位置。
字符 | 说明 | 备注/例子 |
---|---|---|
^ | 被匹配字符的起始位置 | |
$ | 被匹配字符的结束位置 |
例[7]:
^(abc|123|h)
# 匹配起始为“abc”或“123”,或“h”的字符
# 与没有加上“^”时的“(abc|123|h)”区分:
# 当没有加上“^”时则在字符串中任意位置出现“abc”或“123”,或“h”的字符都可以匹配正确;
# 加上“^”就只能在字符的起始处出现才能匹配正确。
\.(gif|jpg|png)$
# 匹配结束为“.gif”或“.jpg”,或“.png”的字符
经验总结1: 需要加上“^”和“$”进行限制时如果没有加上,会导致数据验证时出现BUG。
例[8]: 判断输入的字符串是不是合法的手机号码
0?1[358]\d{9}
# 正则表达式前后没有加上“^”和“$”,那么对于类似的字符串
# “ABC13510086111”一样可以匹配成功。
# 因此需要在正则表达式前后加上“^”和“$”,改为“^0?1[358]\d{9}$”
经验总结2: 匹配字符串起始位置或结束位置指定的内容。
例[9]: 匹配字符串的前后空字符和空格
(^\s*|\s*$)
# 不能写为“^\s*|\s*$”
经验总结3: “^”和“$”不是一定只能写在正则表达式的最前面和最后面。
例[10]: 匹配一个URL路径的pathinfo等于“index.php”
^/index\.php($|/|\?|#)
# URL路径的pathinfo就是不含“http://域名”的那部分,例如:
# /index.html、/dir1/page.html、/dir2/list.php?id=1、/dir3/get#xxx …等。
# pathinfo后面可能带有“?...”的query参数、带有“#...”的fragment瞄点,或者是一个“/文件名/…”的伪静态地址,
# 所以pathinfo可能是以“/”、“?”、“#”结束;如果不带这些内容就是到结束位“$”。
# 最后正则表达式“^/index\.php”后面要加上“ ($|/|\?|#)”。
7.贪婪匹配和非贪婪匹配
- 贪婪匹配:尽可能多地匹配(直到下一个匹配的字符、子匹配或文本结束)
- 非贪婪匹配:尽可能少地匹配(直到下一个匹配的字符、子匹配或文本结束)
字符 | 说明 | 备注/例子 |
---|---|---|
.* | 允许为空贪婪匹配 | |
.+ | 非空的贪婪匹配 | |
.*? | 非贪婪匹配 |
例[11]: 分离出文件名的基本部分和扩展名
假设文件名为“windows.ppt”
(.+)(\.[^\.]+)?$
# “(.+)”就是一个非空的贪婪匹配
# “(\.[^\.]+)?$”就是贪婪匹配后面的一个子匹配,其中:
# “$”表示文件名结束处(“最后的地方”)
# “[^\.]+”表示除了“.”以外的多个字符
# “(\.[^\.]+)”表示“.”+除了“.”以外的多个字符(就是完整扩展名)
# “(\.[^\.]+)?”可以兼容没有扩展名的文件名
# 整体解读为:
# 从文件名的起始地方开始匹配,一直匹配到最后出现的“.扩展名”。如果没有扩展名就一直到结束
例[12]: 匹配URL地址的基本部分(不含“?”或“#”后面的内容)
假设URL地址为“http://www.baidu.com/?id=ddddd”
^(.*?)([#\?].*)?$
# “(.*?)”就是一个非贪婪匹配
# “([#\?].*)?”就是非贪婪匹配后面的子匹配,其中:
# “[#\?]”表示“#”或者“?”
# “.*”是一个可为空的贪婪匹配(后面没有再匹配字符或子匹配)
# “([#\?].*)?”可以兼容不含有“?……”或“#……”的URL路径
# 整体解读为:
# 从URL路径的起始地方开始匹配,一直到“?”或“#”字符止。如果没有“?”或“#”字符就一直到结束
经验总结: 使用“[^]”来代替非贪婪匹配,会显得更加直观。
例[13]: 匹配URL地址的基本部分(不含“?”或“#”后面的内容)
假设URL地址为“http://www.baidu.com/?id=ddddd”
^([^#\?]+)
# 因为是匹配到“?”、“#”就截止,也就是说要匹配的字符实际就是不等于“?”或“#”,
# 因此正则表达式可以改为“[^#\?]+”。
例[14]: 匹配HTML标签的属性部分
假设HTML标签为“<body style="color:#000000" οnlοad="foo">”
<body(\s+.*?)?>
# “.*?”是一个非贪婪匹配
# “>”就是非贪婪匹配后面要匹配的字符
# “(\s+.*?)”表示前面字符必须是一个和多个空字符,然后一直匹配到“>”为止
# “(\s+.*?)?”可以兼容没有任何属性的body标签
8.正向预查找和反向预查找
游标(指针位置):正则表达式匹配可以看成有一个“游标”指向被匹配文本的位置。开始匹配时“游标”位置等于0(就是在被匹配文本的最左边),每匹配到一个字符或子匹配后,游标就跟着右移。
例如有文本“windows xp professional”,使用正则表达式“win([a-z])\s+xp\s+.”进行匹配,游标的移动如下:
匹配 | 游标位置 |
---|---|
匹配前 | ⇓windows xp professional |
匹配到“win”后 | win⇓dows xp professional |
匹配到“([a-z]*)”(就是win后面可能有的多个字母) | windows⇓ xp professional |
往下匹配“\s+”(就是多个空字符) | windows ⇓xp professional |
往下匹配“xp”两个字符 | windows xp⇓ professional |
往下匹配“\s+” | windows xp ⇓professional |
最后匹配剩余部分 | windows xp professional⇓ |
- 正向预查找:从匹配到的地方继续匹配后面字符或子匹配,但是“游标”不移动
字符 | 说明 | 备注/例子 |
---|---|---|
(?=) | 正向预查找(等于) | |
(?!) | 正向预查找(不等于) |
- 反向预查找:从匹配到的地方去匹配其左边的字符或子匹配(就是先匹配后面的内容,再反过来匹配前面的内容),但是“游标”不移动
字符 | 说明 | 备注/例子 |
---|---|---|
(?<=) | 反向预查找(等于) | |
(?<!) | 反向预查找(不等于) |
例[15]: 假设有一组文本内容:
jiuzhou-nas
jiuzhou-dianqi
jiuzhou-jidinghe
要匹配出不是“jiuzhou-dianqi”的内容
(jiuzhou\-(?!dianqi).*)
# “jiuzhou\-”就是匹配“jiuzhou-”
# “(?!dianqi)”就是从“jiuzhou-”右边查看是不是“dianqi”,如果不是就从“jiuzhou-”后面继续匹配
匹配 | 游标位置 |
---|---|
匹配前 | ⇓jiuzhou-nas |
匹配到“jiuzhou-”后 | jiuzhou-⇓nas |
从“jiuzhou-”右边查看是不是“dianqi” | jiuzhou-⇓nas |
如果不是就从“jiuzhou-”后面继续匹配 | jiuzhou-nas⇓ |
例[16]: 假设有一组文本内容:
kaixin-chaoren 001
aixin-chaoren 002
huaxin-chaoren 003
xiaoxin-chaoren 004
tianxin-chaoren 005
要匹配出不是“huaxin-chaoren”的内容
.*?((?<!huaxin)-chaoren).*?
# “.*?”非贪婪匹配,一直匹配到下一个子匹配“((?<!huaxin)-chaoren)”
# “-chaoren”就是匹配“-chaoren”
# “(?<!huaxin)”就是查看匹配到的“-chaoren”左边是不是“huaxin”,如果不是就继续匹配
匹配 | 游标位置 |
---|---|
匹配前 | ⇓xiaoxin-chaoren 004 |
匹配到“-chaoren”后 | xiaoxin-chaoren⇓ 004 |
查看“-chaoren”左边是不是“huaxin” | xiaoxin-chaoren⇓ 004 |
如果不是就从“游标”后面继续匹配 | xiaoxin-chaoren 004⇓ |
注意: javascript的正则表达式API不支持反向预查找。
六、正则表达式的应用
1. 实现数据格式验证
各种开发语言的正则表达式API接口都提供有“匹配”方法,使用正则表达式进行匹配,可以实现对数据格式验证。
语言 | 方法/函数 |
---|---|
javascript | RegExp.test |
php | preg_match |
java | Pattern.matches |
python | re._pattern_type.search |
2.提取匹配结果
在开发中,开发语言的正则表达式API通常都提供有“匹配(查找)”、“替换”、“分割”的接口,使用“匹配”、“替换”时经常需要提取正则表达式匹配后的结果。
例如:HTML标签为“<body style="color:#000000"
οnlοad="foo">”,使用正则表达式进行匹配,但是只需从匹配结果中获取“ style="color:#000000" οnlοad="foo"”这部分内容。
获取正则表达式的匹配结果,可以使用“子匹配”保留匹配结果。在正则表达式中,关于使用“()”的语法,包括以下类型:
语法 | 说明 | 可否捕获 |
---|---|---|
() | 普通子匹配 | 可捕获 |
(?P<name>) | 署名子匹配 | 可捕获 |
(?: ) | 普通子匹配 | 不可捕获 |
(?=) | 正向查找(等于) | 不可捕获 |
(?!) | 正向查找(不等于) | 不可捕获 |
(?<=) | 反向预查找(等于) | 不可捕获 |
(?<!) | 反向预查找(不等于) | 不可捕获 |
例[17]: 使用以下正则表达式进行匹配
^(\+[1-9]\d{0,3}\-?|0(0?[1-9]\d{1,2}\-?)?)?(1[358]\d{9})$
Python
!/usr/bin/env python
# -*- coding:utf-8 -*-
# 导入正则表达式模块
import re
# 创建正则表达式对象
pattern = re.compile(r"^(\+[1-9]\d{0,3}\-?|0(0?[1-9]\d{1,2}\-?)?)?(1[358]\d{9})$")
# 匹配
matchs = pattern.search("086-13800138000")
# 查看结果
print(matchs.groups())
('086-', '86-', '13800138000')
PHP
# 匹配
preg_match(
"/^(\+[1-9]\d{0,3}\-?|0(0?[1-9]\d{1,2}\-?)?)?(1[358]\d{9})$/",
"086-13800138000",
$match
);
# 查看结果
print_r($match);
Array
(
[0] => 086-13800138000
[1] => 086-
[2] => 86-
[3] => 13800138000
)
Javascript
# 匹配
match = /^(\+[1-9]\d{0,3}\-?|0(0?[1-9]\d{1,2}\-?)?)?(1[358]\d{9})$/.exec("086-13800138000");
# 查看结果
console.log(match);
{
0: "086-13800138000"
1: "086-"
2: "86-"
3: "13800138000"
index : 0
input : "086-13800138000"
length: 4
}
Java
// 导入模块
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexMatches
{
public static void main( String args[] ){
// 按指定模式在字符串查找
String line = "086-13800138000";
String pattern = "^(\\+[1-9]\\d{0,3}\\-?|0(0?[1-9]\\d{1,2}\\-?)?)?(1[358]\\d{9})$";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 创建 matcher 对象
Matcher m = r.matcher(line);
if (m.find()) {
System.out.println("Found value: " + m.group(0));
System.out.println("Found value: " + m.group(1));
System.out.println("Found value: " + m.group(2));
System.out.println("Found value: " + m.group(3));
} else {
System.out.println("NO MATCH.");
}
}
}
例[18]: 使用以下正则表达式进行匹配(注意与“例[17]”的正则表达式的区别)
^(\+[1-9]\d{0,3}\-?|0(?:0?[1-9]\d{1,2}\-?)?)?(1[358]\d{9})$
Python
# 导入正则表达式模块
import re
# 创建正则表达式对象
pattern = re.compile(r"^(\+[1-9]\d{0,3}\-?|0(?:0?[1-9]\d{1,2}\-?)?)?(1[358]\d{9})$")
# 匹配
matchs = pattern.search("086-13800138000")
# 查看结果
print(matchs.groups())
('086-', '13800138000')
PHP
# 匹配
preg_match(
"/^(\+[1-9]\d{0,3}\-?|0(?:0?[1-9]\d{1,2}\-?)?)?(1[358]\d{9})$/",
"086-13800138000",
$match
);
# 查看结果
print_r($match);
Array
(
[0] => 086-13800138000
[1] => 086-
[2] => 13800138000
)
Javascript
# 匹配
match = /^(\+[1-9]\d{0,3}\-?|0(?:0?[1-9]\d{1,2}\-?)?)?(1[358]\d{9})$/.exec("086-13800138000");
# 查看结果
console.log(match);
{
0: "086-13800138000"
1: "086-"
2: "13800138000"
index : 0
input : "086-13800138000"
length: 3
}
Java
// 导入模块
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexMatches
{
public static void main( String args[] ){
// 按指定模式在字符串查找
String line = "086-13800138000";
String pattern = "^(\\+[1-9]\\d{0,3}\\-?|0(?:0?[1-9]\\d{1,2}\\-?)?)?(1[358]\\d{9})$";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 创建 matcher 对象
Matcher m = r.matcher(line);
if (m.find()) {
System.out.println("Found value: " + m.group(0));
System.out.println("Found value: " + m.group(1));
System.out.println("Found value: " + m.group(2));
} else {
System.out.println("NO MATCH.");
}
}
}
例[19]: 使用以下正则表达式进行匹配(注意与“例[17]”、“例[18]”的正则表达式的区别)
^(\+[1-9]\d{0,3}\-?|0(?:0?[1-9]\d{1,2}\-?)?)?(?P<code>1[358]\d{9})$
Python
# 导入正则表达式模块
import re
# 创建正则表达式对象
pattern = re.compile(r"^(\+[1-9]\d{0,3}\-?|0(?:0?[1-9]\d{1,2}\-?)?)?(?P<code>1[358]\d{9})$")
# 匹配
matchs = pattern.search("086-13800138000")
# 查看结果
print(matchs.groups())
('086-', '13800138000')
print(matchs.groupdict())
{'code': '13800138000'}
PHP
# 匹配
preg_match(
"/^(\+[1-9]\d{0,3}\-?|0(?:0?[1-9]\d{1,2}\-?)?)?(?P<code>1[358]\d{9})$/",
"086-13800138000",
$match
);
# 查看结果
print_r($match);
Array
(
[0] => 086-13800138000
[1] => 086-
[2] => 13800138000
[code] => 13800138000
)
Javascript
注意: javascript的正则表达式不支持命名子串。
Java
// 导入模块
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexMatches
{
public static void main( String args[] ){
// 按指定模式在字符串查找
String line = "086-13800138000";
String pattern = "^(\\+[1-9]\\d{0,3}\\-?|0(?:0?[1-9]\\d{1,2}\\-?)?)?" +
"(?<code>1[358]\\d{9})$";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 创建 matcher 对象
Matcher m = r.matcher(line);
if (m.find()) {
System.out.println("Found value: " + m.group(0));
System.out.println("Found value: " + m.group(1));
System.out.println("Found value: " + m.group(2));
System.out.println("Found value: " + m.group("code"));
} else {
System.out.println("NO MATCH.");
}
}
}
注意: java正则表达式的命名子串写法是“(?<name>)”。
- 使用“()”引用后成为一个子匹配,可以使用数字下标从匹配结果中获取匹配的内容(不同的语言的正则表达式接口不同);
- 使用“(?: )”引用后的子匹配是不可捕获的,也就是不存在返回结果中;
- 使用“(?P<name>)”(Java的命名子匹配写法是“(?<name>)”)引用后成为一个命名子匹配(也称作“命名分组”),可以使用相应的名称从匹配结果中获取匹配的内容(不同的语言的正则表达式接口不同,javascript不支持命名子匹配);
七、附录
1.常用的正则表达式对象修饰符
修饰符 | 说明 | 备注 |
---|---|---|
caseless | 不区分大小写 | |
multiline | 多行匹配 | |
global | 全局匹配 | 有些语言默认就是全局匹配,没有该修饰符 |
extended | 忽略表达式内的空白字符 | javascript不支持 |
dotall | “.”也匹配回车符 | javascript不支持 |
2.正则表达式符号类型的元字符
包括“. \ + * ? ^ $ [ ] ( ) { } = ! < > - | :”20个字符。
如果正则表达式中含有这些普通字符,必须在前面加上转义符,将其回归为普通字符!