Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

正则表达式 #28

Open
XXHolic opened this issue Feb 4, 2019 · 0 comments
Open

正则表达式 #28

XXHolic opened this issue Feb 4, 2019 · 0 comments

Comments

@XXHolic
Copy link
Owner

XXHolic commented Feb 4, 2019

目录

想法

想起之前的工作,碰到需要用正则表达式的时候,就到网上找相关的资料,但总觉得不太符合自己想要的东西。索性根据自身的需求,整理出自己可以随时使用和补充更新的文档。

简介

正则表达式是一连串字符定义的搜索模式。通常这个模式被字符串搜索算法,用于在字符串中进行”查找“和”查看并替换“操作,也用于输入验证。它是从 TCSformal language 理论中发展出来。

这个概念产生于 20 世纪 50 年代,当时美国数学家 Stephen Cole Kleene 将一种常规语言的描述形式化。这个概念的广泛使用是在 Unix 文本处理程序中。自 20 世纪 80 年代以来,编写正则表达式存在不同的语法,一种是 POSIX 标准,另一种广泛使用的是 Perl 语法。许多编程语言通过内置或类库的方式,来提供正则表达功能。

以下是 JavaScript 中的正则表达式的使用,有些正则特性 JavaScript 并不支持,具体见这里

一个简单的示例

在下面的字符串中匹配出单词“love”:

var str = 'RegExr is great, we love it. RegExr is lovely, we love it.';
var result = str.match(/love/g);
console.info(result); // ["love", "love", "love"]

这个例子里面的中 /love/g 的含义是匹配所有由 l、o、v、e 4 个字符组成的字符串。这其中正则相关知识点有:

  • /love/g:正则表达式的创建。
  • match():能使用正则表达式的方法。

下面由此为切入点开始进行相关的介绍。

创建

创建正则表达式的语法,又两种形式如下:

var reg1 = new RegExp(pattern,flags);
var reg2 = /pattern/flags;
  • pattern:正则表达式的主体,描述表达式的模式,可以包含字符类、限定符、分组、向前查找以及反向引用。
  • flags:修饰符,用于执行区分大小写和全局分配,可选。

pattern

模式中可以包含多种类型,下面就先介绍几个常用的类型:普通字符、特殊字符、方括号、限定符、定位符。

普通字符

在上面的例子中就是属于这种类型,还包括没有显式指定为元字符的所有可打印和不可打印字符。这包括所有大写和小写字母、所有数字、所有标点符号和一些其他符号。

元字符是拥有特殊含义的字符:

元字符 描述
. 查找单个字符,除了换行和行结束符。
\w 查找单词字符。
\W 查找非单词字符。
\d 查找数字。
\D 查找非数字字符。
\s 查找空白字符。
\S 查找非空白字符。
\b 匹配单词边界。
\B 匹配非单词边界。
\0 查找 NULL 字符。
\n 查找换行符。
\f 查找换页符。
\r 查找回车符。
\t 查找制表符。
\v 查找垂直制表符。
\xxx 查找以八进制数 xxx 规定的字符。
\xdd 查找以十六进制数 dd 规定的字符。
\uxxxx 查找以十六进制数 xxxx 规定的 Unicode 字符。

特殊字符

特殊字符是一些拥有特殊含义的字符,例如 /lo*e/ 中的 * 表示任何字符,但如果想要查找字符串中的 *,则需要对 * 进行转义,方式是在前面加反斜杠字符 \,就变成了/lo\*e/。这类特殊字符还有:

字符 描述
$ 匹配输入字符串的结尾位置。
如果设置了 RegExp 对象的 Multiline 属性,则 $ 也匹配 '\n' 或 '\r'。
要匹配 $ 字符本身,请使用 \$
( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。
要匹配这些字符,请使用 \( \)
* 匹配前面的子表达式零次或多次。要匹配 * 字符,请使用 \*
匹配前面的子表达式一次或多次。要匹配 字符,请使用 \
. 匹配除换行符 \n 之外的任何单字符。要匹配 . ,请使用 \.
[ 标记一个中括号表达式的开始。要匹配 [,请使用 \[
? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配 ? 字符,请使用 \?
\ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。
例如, 'n' 匹配字符 'n'。\n 匹配换行符。序列 \\ 匹配 \,而 \( 则匹配 "("。
^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。
要匹配 ^ 字符本身,请使用 \^
{ 标记限定符表达式的开始。要匹配 {,请使用 \{
| 指明两项之间的一个选择。要匹配 |,请使用 \|

方括号

方括号用于查找某个范围内的字符。例如下面的例子:

  • [0-9]:查找任何从 0 至 9 的数字。
  • [a-z]:查找任何从小写 a 到小写 z 的字符。
  • [abc]:查找方括号之间的任何字符。

限定符

限定符用来指定正则表达式中的一部分,必须要出现多少次才能满足匹配。有下面几种:

字符 描述
* 匹配前面的子表达式零次或多次。例如,zo* 能匹配 "z" 以及 "zoo"。* 等价于{0,}。
匹配前面的子表达式一次或多次。例如,'zo ' 能匹配 "zo" 以及 "zoo",但不能匹配 "z"。 等价于 {1,}。
? 匹配前面的子表达式零次或一次。例如,"do(es)?" 可以匹配 "do" 、 "does" 中的 "does" 、 "doxy" 中的 "do" 。? 等价于 {0,1}。
{n} n 是一个非负整数。匹配确定的 n 次。例如,'o{2}' 不能匹配 "Bob" 中的 'o',但是能匹配 "food" 中的两个 o。
{n,} n 是一个非负整数。至少匹配n 次。例如,'o{2,}' 不能匹配 "Bob" 中的 'o',但能匹配 "foooood" 中的所有 o。'o{1,}' 等价于 'o '。'o{0,}' 则等价于 'o*'。
{n,m} m 和 n 均为非负整数,其中n <= m。最少匹配 n 次且最多匹配 m 次。例如,"o{1,3}" 将匹配 "fooooood" 中的前三个 o。'o{0,1}' 等价于 'o?'。请注意在逗号和两个数之间不能有空格。

定位符

定位符用来描述字符串或单词的边界,能够将正则表达式固定到行首或行尾。

字符 描述
^ 匹配输入字符串开始的位置。如果设置了 RegExp 对象的 Multiline 属性,^ 还会与 \n 或 \r 之后的位置匹配。
$ 匹配输入字符串结尾的位置。如果设置了 RegExp 对象的 Multiline 属性,$ 还会与 \n 或 \r 之前的位置匹配。
\b 匹配一个单词边界,即字与空格间的位置。
\B 非单词边界匹配。

注意:不能将限定符与定位符一起使用。由于在紧靠换行或者单词边界的前面或后面,不能有一个以上位置,因此不允许诸如 ^* 之类的表达式。

flags

修饰符用于执行区分大小写和全局匹配:

修饰符 描述
i 执行对大小写不敏感的匹配。
g 执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
m 执行多行匹配。

从上面含义可以知道,如果不设置修饰符,正则表达式匹配的时候遵循的规是:区分大小写、只找到匹配的第一个、不执行多行匹配。

方法

RegExp 对象方法

主要有:exec()test()toString()

exec()

  • 功能:用于检索字符串中的正则表达式的匹配。
  • 参数:必需,接受一个参数,即要应用的模式的字符串。
  • 结果:返回包含第一个匹配项信息的数组,若没有匹配到,则返回 null。

返回的数组,较一般的数组数组多了两个额外的属性:index、input。

  • index:表示匹配项在字符串中的位置。
  • input:表示应用正则表达式的字符串。
var testString = "cat and dog and bird";
var pattern = /dog (and bird)?/g;
var patternNo = /ball (and foot)?/g;
var result = pattern.exec(testString);
console.info(result); // ["dog and bird", "and bird", index: 8, input: "cat and dog and bird",groups: undefined]
var resultNo = patternNo.exec(testString);
console.info(resultNo); // null

test()

  • 功能:用于检测一个字符串是否匹配某个模式。
  • 参数:必需,接受一个字符串。
  • 结果:能匹配到时,返回 true,否则,返回 false。
var testString = "cat and dog and bird";
var pattern = /dog/g;
var patternNo = /ball/g;
var result = pattern.test(testString);
console.info(result); // true
var resultNo = patternNo.test(testString);
console.info(resultNo); // false

toString() 和 toLocaleString()

返回真正表达式的字面量。

var pattern = /dog/g;
var result1 = pattern.toString();
console.info(result1); // /dog/g
var result2 = pattern.toLocaleString();
console.info(result2); // /dog/g

支持正则表达式的 String 对象的方法

主要有:search()match()replace()split()

search()

  • 功能:用于检索字符串中指定的子字符串,或检索与正则表达式相匹配的子字符串。
  • 参数:必需,接受一个参数,为字符串、正则表达式或 RegExp 对象。
  • 结果:返回字符串中第一个匹配项的索引;如果没有,就返回 -1。

这个方法始终从字符串开头向后查找。

var testString = "cat and dog and bird";
var pattern = /and/g;
var patternNo = /ball/g;
var result = testString.search(pattern);
console.info(result); // 4
var resultNo = testString.search(patternNo);
console.info(resultNo); // -1

match()

  • 功能:该方法可在字符串内检索指定的值,找到一个或多个正则表达式的匹配。
  • 参数:必需,接受一个参数,为正则表达式或 RegExp 对象。
  • 结果:返回一个数组。没有匹配到则返回 null。
var testString = "cat and dog and bird";
var pattern = /and/g;
var patternNo = /ball/g;
var result = testString.match(pattern);
console.info(result); // ["and","and"]
var resultNo = testString.match(patternNo);
console.info(resultNo); // null

replace()

  • 功能:用于在字符串中用一些字符替换另一些字符,或替换一个与正则表达式匹配的子串。
  • 参数:两个参数,都必需。第一个参数可以是字符串或 RegExp,第二个参数可以是字符串或函数。如果第一个参数为空,那么只会替换第一个子字符串。
  • 结果:返回一个新的字符串。
var testString = "cat and dog and bird";
var pattern = /and/g;
var result = testString.replace(pattern,function(){
    return 'or';
});
console.info(result); // cat or dog or bird
var resultEmpty = testString.replace('',function(){
    return 'or';
});
console.info(resultEmpty); // orcat and dog and bird

split()

  • 功能:用于把一个字符串分割成字符串数组。
  • 参数:两个参数,都可选。第一个参数可以是字符串或 RegExp,第二个参数可指定返回的数组的最大长度。如果设置了该参数,返回的子串不会多于这个参数指定的数组。如果没有设置该参数,整个字符串都会被分割,不考虑它的长度。
  • 结果:返回一个数组。
var testString = "cat and dog and bird";
var pattern = /and/g;
var result = testString.split(pattern);
console.info(result); // ["cat ", " dog ", " bird"]

RegExp 实例属性

每个实例都又如下的属性:

属性 描述
constructor 返回一个函数,该函数是一个创建 RegExp 对象的原型。
global 布尔值,判断是否设置了 "g" 修饰符
ignoreCase 布尔值,判断是否设置了 "i" 修饰符
lastIndex 整数,用于规定下次匹配的起始位置
multiline 布尔值,判断是否设置了 "m" 修饰符
source 返回正则表达式的匹配模式

如何写一个正则

最近跟人交流的时候,意识到一个问题:虽然收集了资料,知道有这些,看教程好像是这么回事,但要临时写一个的话,不知道如何下手。

自己想到的解决方法:先分析常见的正则表达式,从中发现适合自己理解的方法,并进行总结。

来看看去空的正则。

去空正则分析

let str = '  fd af  dsa  ';
let filterAllSpace = str.replace(/\s/g,'');

观察正则并进行拆分:

  • / /,是创建正则的形式。
  • \s,是表示空字符串的元字符,属于普通字符。
  • g,是修饰符,表示执行全局匹配。

思考

上面是基于写好的正则进行拆分,发现功能语义跟正则的表达式是一一对应。其实一开始就是这样的,之所以自己碰到正则就感觉头蒙,是因为自己想一步到位,但自己基本都没有掌握很熟练,当然就会不知如何下手。由此,个人想到写正则的思路是:

  1. 将功能需求描述清楚,进行语义拆分,提取关键点。
  2. 将关键点与正则语法规则,一一对应,进行替换。
  3. 尝试写完整的正则,进行测试验证,不断修正,记录遗漏点。

熟悉基本正则模式,不需要全部记住,知道大概几类就可以,使用的时候可以再查,逐渐强化记忆。

举个例子:匹配手机号。

第 1 步

“匹配手机号”这个描述还要细化,手机号都是数字,共 11 位,国内号码段有:13、14、15、16、17、18、19。

提取关键点:

  • 都是数字。
  • 11 位。
  • 号段:13、14、15、16、17、18、19,也就是说数字前 2 位有限制,剩下 9 位无限制。

第 2 步

  • 都是数字,可能使用到的信息有:元字符 \d,表示数字范围的方括号 [0-9],全局匹配 g
  • 11 位,可能使用到的信息有:多次匹配限定符 {}
  • 号段,可能使用到的信息有:边界定位符 ^,匹配多个选项中的一个 |

第 3 步

根据以上信息尝试写出的正则:

let reg = /^1[3-9]\d{9}/g;
let example = 18278989877;
console.info(reg.test(example));

经过尝试,发现超过 11 位的数字也符合匹配。原因是没有匹配结尾的位置。修正后正则为:

let reg = /^1[3-9]\d{9}$/g;

正则收集

校验数字

- 数字:^[0-9]*$
- n位的数字:^\d{n}$
- 至少n位的数字:^\d{n,}$
- m-n位的数字:^\d{m,n}$
- 零和非零开头的数字:^(0|[1-9][0-9]*)$
- 非零开头的最多带两位小数的数字:^([1-9][0-9]*) (\.[0-9]{1,2})?$
- 带1-2位小数的正数或负数:^(\-)?\d (\.\d{1,2})$
- 正数、负数、和小数:^(\-|\ )?\d (\.\d )?$
- 有两位小数的正实数:^[0-9] (\.[0-9]{2})?$
- 有1~3位小数的正实数:^[0-9] (\.[0-9]{1,3})?$
- 非零的正整数:^[1-9]\d*$ 或 ^([1-9][0-9]*){1,3}$ 或 ^\ ?[1-9][0-9]*$
- 非零的负整数:^\-[1-9][]0-9"*$ 或 ^-[1-9]\d*$
- 非负整数:^\d $ 或 ^[1-9]\d*|0$
- 非正整数:^-[1-9]\d*|0$ 或 ^((-\d )|(0 ))$
- 非负浮点数:^\d (\.\d )?$ 或 ^[1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0 |0$
- 非正浮点数:^((-\d (\.\d )?)|(0 (\.0 )?))$ 或 ^(-([1-9]\d*\.\d*|0\.\d*[1-9]\d*))|0?\.0 |0$
- 正浮点数:^[1-9]\d*\.\d*|0\.\d*[1-9]\d*$ 或 ^(([0-9] \.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9] )|([0-9]*[1-9][0-9]*))$
- 负浮点数:^-([1-9]\d*\.\d*|0\.\d*[1-9]\d*)$ 或 ^(-(([0-9] \.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9] )|([0-9]*[1-9][0-9]*)))$
- 浮点数:^(-?\d )(\.\d )?$ 或 ^-?([1-9]\d*\.\d*|0\.\d*[1-9]\d*|0?\.0 |0)$

校验字符

- 汉字:^[\u4e00-\u9fa5]{0,}$
- 英文和数字:^[A-Za-z0-9] $ 或 ^[A-Za-z0-9]{4,40}$
- 长度为3-20的所有字符:^.{3,20}$
- 由26个英文字母组成的字符串:^[A-Za-z] $
- 由26个大写英文字母组成的字符串:^[A-Z] $
- 由26个小写英文字母组成的字符串:^[a-z] $
- 由数字和26个英文字母组成的字符串:^[A-Za-z0-9] $
- 由数字、26个英文字母或者下划线组成的字符串:^\w $ 或 ^\w{3,20}$
- 中文、英文、数字包括下划线:^[\u4E00-\u9FA5A-Za-z0-9_] $
- 中文、英文、数字但不包括下划线等符号:^[\u4E00-\u9FA5A-Za-z0-9] $ 或 ^[\u4E00-\u9FA5A-Za-z0-9]{2,20}$
- 可以输入含有^%&',;=?$\"等字符:[^%&',;=?$\x22] 
- 禁止输入含有~的字符:[^~\x22] 

其它

- Email地址:^\w ([- .]\w )*@\w ([-.]\w )*\.\w ([-.]\w )*$
- 域名:[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(/.[a-zA-Z0-9][-a-zA-Z0-9]{0,62}) /.?
- InternetURL:[a-zA-z] ://[^\s]* 或 ^http://([\w-] \.) [\w-] (/[\w-./?%&=]*)?$
- 手机号码:^(13[0-9]|14[5|7]|15[0|1|2|3|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$
- 电话号码("XXX-XXXXXXX"、"XXXX-XXXXXXXX"、"XXX-XXXXXXX"、"XXX-XXXXXXXX"、"XXXXXXX"和"XXXXXXXX):^(\(\d{3,4}-)|\d{3.4}-)?\d{7,8}$
- 国内电话号码(0511-4405222、021-87888822):\d{3}-\d{8}|\d{4}-\d{7}
- 电话号码正则表达式(支持手机号码,3-4位区号,7-8位直播号码,1-4位分机号): ((\d{11})|^((\d{7,8})|(\d{4}|\d{3})-(\d{7,8})|(\d{4}|\d{3})-(\d{7,8})-(\d{4}|\d{3}|\d{2}|\d{1})|(\d{7,8})-(\d-{4}|\d{3}|\d{2}|\d{1}))$)
- 身份证号(15位、18位数字),最后一位是校验位,可能为数字或字符X:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)
- 帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$
- 密码(以字母开头,长度在6~18之间,只能包含字母、数字和下划线):^[a-zA-Z]\w{5,17}$
- 强密码(必须包含大小写字母和数字的组合,不能使用特殊字符,长度在 8-10 之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])[a-zA-Z0-9]{8,10}$
- 强密码(必须包含大小写字母和数字的组合,可以使用特殊字符,长度在8-10之间):^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8,10}$
- 日期格式:^\d{4}-\d{1,2}-\d{1,2}
- 一年的12个月(01~09和1~12):^(0?[1-9]|1[0-2])$
- 一个月的31天(01~09和1~31):^((0?[1-9])|((1|2)[0-9])|30|31)$
- 钱的输入格式:
    1. 有四种钱的表示形式我们可以接受:"10000.00" 和 "10,000.00", 和没有 "分" 的 "10000" 和 "10,000":^[1-9][0-9]*$
    2. 这表示任意一个不以0开头的数字,但是,这也意味着一个字符"0"不通过,所以我们采用下面的形式:^(0|[1-9][0-9]*)$
    3. 一个0或者一个不以0开头的数字.我们还可以允许开头有一个负号:^(0|-?[1-9][0-9]*)$
    4. 这表示一个0或者一个可能为负的开头不为0的数字.让用户以0开头好了.把负号的也去掉,因为钱总不能是负的吧。下面我们要加的是说明可能的小数部分:^[0-9] (.[0-9] )?$
    5. 必须说明的是,小数点后面至少应该有1位数,所以"10."是不通过的,但是 "10" 和 "10.2" 是通过的:^[0-9] (.[0-9]{2})?$
    6. 这样我们规定小数点后面必须有两位,如果你认为太苛刻了,可以这样:^[0-9] (.[0-9]{1,2})?$
    7. 这样就允许用户只写一位小数.下面我们该考虑数字中的逗号了,我们可以这样:^[0-9]{1,3}(,[0-9]{3})*(.[0-9]{1,2})?$
    8. 1到3个数字,后面跟着任意个 逗号 3个数字,逗号成为可选,而不是必须:^([0-9] |[0-9]{1,3}(,[0-9]{3})*)(.[0-9]{1,2})?$
    9. 备注:这就是最终结果了,别忘了" "可以用"*"替代如果你觉得空字符串也可以接受的话(奇怪,为什么?)最后,别忘了在用函数时去掉去掉那个反斜杠,一般的错误都在这里
- xml文件:^([a-zA-Z] -?) [a-zA-Z0-9] \\.[x|X][m|M][l|L]$
- 中文字符的正则表达式:[\u4e00-\u9fa5]
- 双字节字符:[^\x00-\xff] (包括汉字在内,可以用来计算字符串的长度(一个双字节字符长度计2,ASCII字符计1))
- 空白行的正则表达式:\n\s*\r (可以用来删除空白行)
- HTML标记的正则表达式:<(\S*?)[^>]*>.*?|<.*? /> ( 首尾空白字符的正则表达式:^\s*|\s*$或(^\s*)|(\s*$) (可以用来删除行首行尾的空白字符(包括空格、制表符、换页符等等),非常有用的表达式)
- 腾讯QQ号:[1-9][0-9]{4,} (腾讯QQ号从10000开始)
- 中国邮政编码:[1-9]\d{5}(?!\d) (中国邮政编码为6位数字)
- IP地址:((?:(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d?\\d))

参考资料

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant