正则表达式必知必会 - 常见问题的正则表达式解决方案

2023-10-14 09:53:11 浏览数 (2)


        与正则表达式有关的问题很少会有什么终极答案。更常见的情况是取决于对不确定性的容忍程度。同时存在着多种解决方法,在正则表达式性能与其所能够处理的场景之间总是存在着权衡。记住,不仅要匹配符合条件的号码,还要排除不符合条件的号码,这也是该正则表达式看起来比较复杂的原因。

一、北美电话号码

        North American Numbering Plan(北美编号方案)对北美地区的电话号码格式做出了定义。根据这一方案,北美地区(美国、加拿大、加勒比海地区大部以及其他几个地区)的电话号码由一个 3 位数的区号和一个 7 位数的号码构成。这 7 位数字又分成一个 3 位数的局号和一个 4 位数的线路号,局号和线路号之间用连字符分隔。每位电话号码可以是任意数字,但区号和局号的第一位数字不能是 0 或 1。往往把区号放在括号里,而且还会在区号与实际电话号码之间加上一个连字符来分隔它们。假设只有以下 4 种格式合法: (555) 555-5555 (555)555-5555 555-555-5555 555.555.5555

代码语言:javascript复制
mysql> set @s:='J. Doe: 248-555-1234
    '> B. Smith: (313) 555-1234
    '> A. Lee: (810)555-1234
    '> M. Jones: 734.555.9999';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='(\([2-9]\d{2}\)\s?\d{3}-\d{4})|([2-9]\d{2}(?<z>[.-]))\d{3}\k<z>\d{4}';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ -------------------------------------------------------- ------------ 
| c    | s                                                      | i          |
 ------ -------------------------------------------------------- ------------ 
|    4 | 248-555-1234,(313) 555-1234,(810)555-1234,734.555.9999 | 9,32,55,79 |
 ------ -------------------------------------------------------- ------------ 
1 row in set (0.01 sec)

二、美国 ZIP 编码

        美国于 1963 年开始使用 ZIP 编码(ZIP,Zone Improvement Plan 的首字母缩写)。美国目前有 4 万多个 ZIP 编码,它们全都由数字构成(第一位数字代表从美国东部到西部的一个地域,0 代表东海岸地区,9 代表西海岸地区)。在 1983 年,美国邮政总局开始使用扩展 ZIP 编码,简称 ZIP 4 编码。新增加的 4 位数字对信件投送区域做了更细致的划分(细化到某个特定的城市街区或某幢特定的建筑物),这大大提高了信件的投送效率和准确性。不过,ZIP 4 编码的使用是可选的,所以对 ZIP 编码进行检查通常必须同时照顾到 5 位数字的 ZIP 编码和 9 位数字的 ZIP 4 编码,ZIP 4 编码中的后 4 位数字与前 5 位数字之间要用一个连字符隔开。

代码语言:javascript复制
mysql> set @S:='999 1st Avenue, Bigtown, NY, 11222
    '> 123 High Street, Any City, MI 48034-1234';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='\d{5}(-\d{4})?';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ ------------------ ------- 
| c    | s                | i     |
 ------ ------------------ ------- 
|    2 | 11222,48034-1234 | 30,66 |
 ------ ------------------ ------- 
1 row in set (0.00 sec)

        d{5} 匹配任意 5 位数字,-d{4} 匹配一个连字符和后 4 位数字。因为后 4 位数字是可选的,所以要把 -d{4} 用括号括起来,使它成为了一个子表达式,再用一个 ? 来表明这个子表达式最多只允许出现一次。

三、加拿大邮政编码

        加拿大邮政编码由 6 个交替出现的字母和数字字符构成。每个编码分成两部分:前 3 个字符给出了 FSA(forward sortation area,转发分拣区)代码,后 3 个字符给出了 LDU(local delivery unit,本地投递单位)代码。FSA 代码的第一个字符用来表明省、市或地区。这个字符有 18 种有效的选择,比如 A 代表纽芬兰地区,B 代表新斯科舍地区,K、L、N和P代表安大略省,M 代表多伦多市,等等。模式应该对此作出验证,确保这个字符的有效性。在书写加拿大邮政编码的时候,FSA 代码和 LDU 代码之间通常要用一个空格隔开。

代码语言:javascript复制
mysql> set @s:='123 4th Street, Toronto, Ontario, M1A 1A1
    '> 567 8th Avenue, Montreal, Quebec, H9Z 9Z9';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='[ABCEGHJKLMNPRSTVXY]\d[A-Z] \d[A-Z]\d';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ ----------------- ------- 
| c    | s               | i     |
 ------ ----------------- ------- 
|    2 | M1A 1A1,H9Z 9Z9 | 35,77 |
 ------ ----------------- ------- 
1 row in set (0.00 sec)

        [ABCEGHJKLMNPRSTVXY] 匹配 18 个有效字符中的任意一个,d[A-Z] 匹配单个数字和紧随其后的任意字母,二者加在一起就能够匹配 FSA 代码。d[A-Z]d 匹配 LDU 代码,也就是任意两个数字字符之间夹着任意一个字母。这个匹配加拿大邮政编码的正则表达式不用区分字母大小写。

四、英国邮政编码

        英国邮政编码由 5~7 个字母和数字构成,这些编码是由英国皇家邮政局(royal mail)定义的。英国邮政编码分为两部分:外部邮政编码[或称外码(outcode)]和内部邮政编码[或称内码(incode)]。外码是一到两个字母后面跟着一到两位数字,或者是一到两个字母后面跟着一个数字和一个字母。内码永远是一位数字后面跟着两个字母(除 C、I、K、M、O 和 V 以外的任意字母,这 6 个字母不会在邮政编码中出现)。内码和外码之间要用一个空格隔开。

代码语言:javascript复制
mysql> set @s:='171 Kyverdale Road, London N16 6PS
    '> 33 Main Street, Portsmouth, P01 3AX
    '> 18 High Street, London NW11 8AB';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='[A-Z]{1,2}\d[A-Z\d]? \d[ABD-HJLNP-UW-Z]{2}';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ -------------------------- ---------- 
| c    | s                        | i        |
 ------ -------------------------- ---------- 
|    3 | N16 6PS,P01 3AX,NW11 8AB | 28,64,95 |
 ------ -------------------------- ---------- 
1 row in set (0.00 sec)

        在该模式中,[A-Z]{1,2}d 匹配一到两个字母以及紧跟着的一位数字,随后的 [A-Zd]? 匹配一个可选的字母或数字字符。于是,[A-Z]{1,2}d[A-Zd]? 可以匹配任何一种有效的外码组合。内码部分由 d[ABD-HJLNP-UW-Z]{2} 负责匹配,它可以匹配任意一位数字和紧随其后的两个允许出现在内码里的字母(A、B、D~H、J、L、N、P~U、W~Z)。这个匹配英国邮政编码的正则表达式不用区分字母大小写。

五、美国社会安全号码

        美国社会安全号码(social security number,简称 SSN)由 3 组数字组成,彼此之间以连字符分隔:第一组包含 3 位数字,第二组包含 2 位数字,第三组包含 4 位数字。从1972年起,美国政府开始根据 SSN 申请人提供的住址来分配第一组里的 3 位数字。

代码语言:javascript复制
mysql> set @s:='John Smith: 123-45-6789';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='\d{3}-\d{2}-\d{4}';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ ------------- ------ 
| c    | s           | i    |
 ------ ------------- ------ 
|    1 | 123-45-6789 | 13   |
 ------ ------------- ------ 
1 row in set (0.00 sec)

        d{3}-d{2}-d{4} 将依次匹配:任意3位数字、一个连字符、任意2位数字、一个连字符、任意4位数字。大多数数字组合都是有效的 SSN,但在实际中,还是要满足几项要求。首先,有效的 SSN 中不能出现全 0 字段;其次,第一组数字(到目前为止)不得大于 728,因为 SSN 还没分配过这么大的数字,但以后也许会有。但是,这样将会是一个非常复杂的模式,所以通常使用的还是比较简单的 d{3}-d{2}-d{4}。

六、IP地址

        IP 地址由 4 个字节构成(这 4 个字节的取值范围都是 0~255)。IP 地址通常被写成 4 组以 . 字符分隔的整数,每个整数由 1~3 位数字构成。

代码语言:javascript复制
mysql> set @s:='localhost is 127.0.0.1.';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='(((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))\.){3}((\d{1,2})|(1\d{2})|(2[0-4]\d)|(25[0-5]))';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ ----------- ------ 
| c    | s         | i    |
 ------ ----------- ------ 
|    1 | 127.0.0.1 | 14   |
 ------ ----------- ------ 
1 row in set (0.00 sec)

        该模式使用了一系列嵌套子表达式。(((d{1,2})|(1d{2})|(2[0-4]d)|(25[0-5]).) 由 4 个嵌套子表达式组成:(d{1,2}) 匹配任意 1 位或 2 位数字(0~99),(1d{2}) 匹配以 1 开头的任意 3 位数字(100~199),(2[0-4]d) 匹配整数 200~249;(25[0-5]) 匹配整数250~255。这 4 部分通过 | 操作符(其含义是只需匹配其中一部分即可)形成了一个子表达式。随后的 . 用来匹配 . 字符,它与前面又形成了一个更大的子表达式,接下来的 {3} 表明需要重复 3 次。最后,取值区间又出现了 1 次(这次省略了尾部的 .),以匹配最后一组数字。通过把 4 组数字全都限制在 0 到 255 之间,这个模式准确无误地做到了只匹配有效的 IP 地址,排除无效的 IP 地址。

七、URL

        匹配 URL 是一件相当有难度的任务,其复杂性取决于想获得多么精确的匹配结果。URL 匹配模式至少应该匹配到协议(http或https)、主机名、可选的端口号和路径。

代码语言:javascript复制
mysql> set @s:='http://www.forta.com/blog
    '> https://www.forta.com:80/blog/index.cfm
    '> http://www.forta.com
    '> http://ben:password@www.forta.com/
    '> http://localhost/index.php?ab=1&c=2
    '> http://localhost:8500/';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='https?:\/\/[-\w.] (:\d )?(\/([\w\/_.]*)?)?';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ ----------------------------------------------------------------------------------------------------------------------------------------------------- -------------------- 
| c    | s                                                                                                                                                   | i                  |
 ------ ----------------------------------------------------------------------------------------------------------------------------------------------------- -------------------- 
|    6 | http://www.forta.com/blog,https://www.forta.com:80/blog/index.cfm,http://www.forta.com,http://ben,http://localhost/index.php,http://localhost:8500/ | 1,27,67,88,123,159 |
 ------ ----------------------------------------------------------------------------------------------------------------------------------------------------- -------------------- 
1 row in set (0.01 sec)

        https?://匹配http://或https://,?使得字符s成为可选项。[-w.] 匹配主机名。(:d )? 匹配一个可选的端口号。(/([w/_.]*)?)? 匹配路径:外层的子表达式匹配 /(如果存在的话),内层的子表达式匹配路径本身。正如所见,这个模式无法处理查询字符串,也不能正确解读嵌在 URL 之中的“username:password”(用户名:密码)。不过,它已经足以处理绝大多数的 URL 了(匹配主机名、端口号和路径)。这个匹配URL的正则表达式不用区分字母大小写。

        如果还想匹配使用了 ftp 协议的 URL,把 https? 替换为 (http|https|ftp) 即可。对于使用了其他协议的 URL 也可以按照类似的思路来匹配。

八、完整的URL

        下面是一个更完整(也更慢)的 URL 匹配模式,它还可以匹配 URL 查询字符串(嵌在 URL 之中的变量信息,这部分与 URL 中的地址之间要用一个 ? 隔开)以及可选的用户登录信息。

代码语言:javascript复制
mysql> set @s:='http://www.forta.com/blog
    '> https://www.forta.com:80/blog/index.cfm
    '> http://www.forta.com
    '> http://ben:password@www.forta.com/
    '> http://localhost/index.php?ab=1&c=2
    '> http://localhost:8500/';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='https?:\/\/(\w*:\w*@)?[-\w.] (:\d )?(\/([\w\/_.]*(\?\S )?)?)?';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------- 
| c    | s                                                                                                                                                                                    | i                  |
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------- 
|    6 | http://www.forta.com/blog,https://www.forta.com:80/blog/index.cfm,http://www.forta.com,http://ben:password@www.forta.com/,http://localhost/index.php?ab=1&c=2,http://localhost:8500/ | 1,27,67,88,123,159 |
 ------ -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------- 
1 row in set (0.02 sec)

        该模式是在前一个例子的基础上改进而来的。这次紧跟在 https?: // 后面的是 (w*:w*@)?,它匹配嵌入在 URL 之中的用户名和密码(用户名和密码要用 : 隔开,后面还要跟上一个 @ 字符),参见这个例子中的第 4 行。另外,路径之后的 (?S )? 负责匹配查询字符串,出现在 ? 后面的文本是可选的,这可以使用 ? 来表示。这个匹配URL的正则表达式不用区分字母大小写。为什么不使用这个模式代替上一个模式呢?就性能来说,越复杂的模式,执行速度越慢。如果不需要额外的功能,还是不使用它比较好。

九、电子邮件地址

        正则表达式经常用于验证电子邮件地址,不过,即便是一个简单的电子邮件地址,验证起来也绝非易事。

代码语言:javascript复制
mysql> set @s:='My name is Ben Forta, and my
    '> email address is ben@forta.com.';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='(\w \.)*\w @(\w \.) [A-Za-z] ';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ --------------- ------ 
| c    | s             | i    |
 ------ --------------- ------ 
|    1 | ben@forta.com | 47   |
 ------ --------------- ------ 
1 row in set (0.00 sec)

        (w .)*w 负责匹配电子邮件地址里的用户名部分(@之前的所有内容):(w .)* 匹配零次或多次出现的文本以及之后的 .,w 匹配必需的文本(例如,这种组合能够匹配 ben 和 ben.forta)。接下来,@ 匹配 @ 字符本身。(w .) 至少匹配一个以 . 结束的字符串,[A-Za-z] 匹配顶层域名(com、edu、us、uk等)。决定电子邮件地址格式有效性的规则极其复杂。该模式无法验证所有可能的电子邮件地址。比如说,这个模式会认为 ben..forta@forta.com 是有效的(显然无效),也不允许主机名部分使用 IP 地址(这种形式是可以的)。还是那句话,它足以验证大部分的电子邮件地址,所以还是可以拿来一用的。这个匹配电子邮件地址的正则表达式不用区分字母大小写。

十、HTML注释

        HTML 页面里的注释必须位于 <!-- 和 --> 标签之间。这两个标签必须至少包含两个连字符,多于两个也没有关系。在浏览(或调试)Web 页面的时候,找出所有的注释是有用的。

代码语言:javascript复制
mysql> set @s:='<!-- Start of page -->
    '> <html>
    '> <!-- Start of head -->
    '> <head>
    '> <title>My Title</title> <!-- Page title -->
    '> </head>
    '> <!-- Body -->
    '> <body>';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='<!(-{2,}).*?[^-]\1>';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ --------------------------------------------------------------------------------- ------------- 
| c    | s                                                                               | i           |
 ------ --------------------------------------------------------------------------------- ------------- 
|    4 | <!-- Start of page -->,<!-- Start of head -->,<!-- Page title -->,<!-- Body --> | 1,31,85,113 |
 ------ --------------------------------------------------------------------------------- ------------- 
1 row in set (0.00 sec)

        <!-{2,} 匹配 HTML 注释的开始标签,也就是 <! 后面紧跟着两个或更多个连字符的情况。.*? 匹配 HTML 注释的文字部分,这里用的是懒惰型量词。-{2,}> 匹配 HTML 注释的结束标签。该模式匹配两个或更多个连字符,所以还可以用来查找 CFML 注释(这种注释的开始/结束标签里包含 3 个连字符)。这个模式还检查 HTML 注释的开始标签和结束标签中的连字符的个数是否配对(可以用来检查 HTML 注释的格式是否有误)。

十一、JavaScript 注释

        JavaScript,以及包括 ActionScript 和 ECMAScript 变体在内的其他脚本语言,代码里的注释均以 // 开头。正如上一个例子中所示,找出给定页面里的所有注释还是挺实用的。

代码语言:javascript复制
mysql> set @s:='<script language="JavaScript">
    '> // Turn off fields used only by replace
    '> function hideReplaceFields() {
    '>   document.getElementById('RegExReplace').disabled=true;
    '>   document.getElementById('replaceheader').disabled=true;
    '> }
    '> // Turn on fields used only by replace
    '> function showReplaceFields() {
    '>   document.getElementById('RegExReplace').disabled=false;
    '>   document.getElementById('replaceheader').disabled=false;
    '> }';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='\/\/.*';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ -------------------------------------------------------------------------------- -------- 
| c    | s                                                                              | i      |
 ------ -------------------------------------------------------------------------------- -------- 
|    2 | // Turn off fields used only by replace,// Turn on fields used only by replace | 32,220 |
 ------ -------------------------------------------------------------------------------- -------- 
1 row in set (0.00 sec)

        该模式很简单://.*匹配 // 和紧随其后的注释内容。

十二、信用卡号码

        正则表达式无法验证信用卡号码是否真正有效,最终的结论要由信用卡的发行机构做出。但是,正则表达式可用于在对信用卡号码做进一步处理之前,把有输入错误的信用卡号码,比如多输入一位数字或少输入一位数字等情况排除在外。

        这里使用的模式都假设信用卡号码里的空格和连字符已提前被去掉。一般来说,在使用正则表达式对信用卡号码进行匹配处理之前,先把其中的非数字字符去掉是一种不错的做法。所有的信用卡都遵守着同一种基本的编号方案:以特定的数字序列开头,号码的总位数是一个固定的值。先来看看 MasterCard(万事达)卡的情况。

代码语言:javascript复制
mysql> set @s:='MasterCard: 5212345678901234
    '> Visa 1: 4123456789012
    '> Visa 2: 4123456789012345
    '> Amex: 371234567890123
    '> Discover: 601112345678901234
    '> Diners Club: 38812345678901';
Query OK, 0 rows affected (0.00 sec)

mysql> set @r:='5[1-5]\d{14}';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ ------------------ ------ 
| c    | s                | i    |
 ------ ------------------ ------ 
|    1 | 5212345678901234 | 13   |
 ------ ------------------ ------ 
1 row in set (0.00 sec)

        MasterCard 卡的号码总长度是 16 位数字,第一位数字永远是 5,第二位数字是 1~5。5[1-5] 匹配前 2 位数字,d{14} 匹配随后的 14 位数字。Visa 卡的情况稍微复杂一些。

代码语言:javascript复制
mysql> set @r:='4\d{12}(\d{3})?';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ -------------------------------- ------- 
| c    | s                              | i     |
 ------ -------------------------------- ------- 
|    2 | 4123456789012,4123456789012345 | 38,60 |
 ------ -------------------------------- ------- 
1 row in set (0.00 sec)

        Visa 卡的第一位号码永远是 4,总长度是 13 位或 16 位数字(不存在 14 位或 15 位,所以这里不能使用数字区间)。4 匹配数字 4 本身,d{12} 匹配接下来的 12 位数字,(d{3})? 匹配可选的最后 3 位数字。匹配American Express(美国运通)卡号的模式就简单多了。

代码语言:javascript复制
mysql> set @r:='3[47]\d{13}';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ ----------------- ------ 
| c    | s               | i    |
 ------ ----------------- ------ 
|    1 | 371234567890123 | 83   |
 ------ ----------------- ------ 
1 row in set (0.01 sec)

        American Express 卡的号码总长度是 15 位,前 2 位号码必须是 34 或 37 。3[47] 匹配前 2 位数字,d{13} 匹配剩余的 13 位数字。匹配Discover卡号的模式也不难。

代码语言:javascript复制
mysql> set @r:='6011\d{14}';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ -------------------- ------ 
| c    | s                  | i    |
 ------ -------------------- ------ 
|    1 | 601112345678901234 | 109  |
 ------ -------------------- ------ 
1 row in set (0.00 sec)

        Discover 卡的号码总长度是 16 位,前 4 位号码必须是 6011,所以用 6011d{14} 就行了。Diners Club卡的情况稍微复杂一些。

代码语言:javascript复制
mysql> set @r:='(30[0-5]|36\d|38\d)\d{11}';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ ---------------- ------ 
| c    | s              | i    |
 ------ ---------------- ------ 
|    1 | 38812345678901 | 141  |
 ------ ---------------- ------ 
1 row in set (0.00 sec)

        Diners Club 卡的号码总长度是 14 位,必须以 300~305、36 或 38 开头。如果前 3 位号码是 300~305,后面必须再有 11 位数字;如果前 2 位号码是 36 或 38,则后面必须再有 12 位数字。这里采用了一个比较简单的办法:不管具体是什么,先匹配前 3 位数字。(30[0-5]|36d|38d) 包含 3 个子表达式,只要其中之一得到匹配即可;其中 30[0-5] 匹配 300~305,36d 匹配以36 开头的任意 3 位数字,38d 匹配以 38 开头的任意 3 位数字。最后,d{11} 匹配剩余的 11 位数字。现在,只要把上述 5 种信用卡号码的匹配模式组合在一起即可。

代码语言:javascript复制
mysql> set @r:='(5[1-5]\d{14})|(4\d{12}(\d{3})?)|(3[47]\d{13})|(6011\d{14})|((30[0-5]|36\d|38\d)\d{11})';
Query OK, 0 rows affected (0.00 sec)

mysql> select regexp_count(@s, @r, '') c, regexp_extract(@s, @r, '') s, regexp_extract_index(@s, @r, 0, '') i;
 ------ --------------------------------------------------------------------------------------------------- --------------------- 
| c    | s                                                                                                 | i                   |
 ------ --------------------------------------------------------------------------------------------------- --------------------- 
|    6 | 5212345678901234,4123456789012,4123456789012345,371234567890123,601112345678901234,38812345678901 | 13,38,60,83,109,141 |
 ------ --------------------------------------------------------------------------------------------------- --------------------- 
1 row in set (0.00 sec)

        该模式用 | 操作符(提供了多选分支)把前面得到的 5 个模式组合到了一起。有了它就可以一次性验证 5 种常见信用卡的号码了。这里使用的模式只能检查信用卡号码起始的数字序列和数字总长度是否正确。不过,并非所有以 4 开头的 13 位数字都是有效的 Visa 卡号。还要使用一种叫作 Mod 10 的数学公式对信用卡号码(上面提及过的所有信用卡类型)进行计算,以确定号码是否真正有效。在处理信用卡的时候,Mod 10 算法是一个必不可少的重要环节,但它不属于正则表达式的工作,因为其涉及数学运算。

0 人点赞