[原创] 正则表达式从入门到老鸟

一、什么是通配符

  通配符是一种特殊语句。在查找文件或文字内容时,如果不知道真正的字符或者不想提供完整的查询字符时,常常使用一些特殊符号来代替一个或多个实字符,这些符号就是“通配符”。基本通配符包括星号(*)和问号(?)。

“*” 表示多个实字符,“?” 表示一个实字符;使用通配符可以进行“模糊查询”。

二、什么是正则表达式

  正则表达式就是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。
  就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个表示某种规律或逻辑的“表达式”,使用这个“表达式”对字符串进行匹配或过滤。

按照约定的符号规则来表示具有一定规律的文字。可以认为是“特高级”的通配符。

三、正则表达式的作用

匹配、查找、替换字符,她可以代替累赘的运算。

例[1]: 判断一组字符串是不是合法的手机号码

  1. 分析手机号码特点
  • 11位数字
  • 第一个数字是1
  • 第二个数字是3、5、8
  • 前面允许是“0”(就变成12位)
  1. 使用正则表达式验证
    ^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除了数字、字母、下划线以外的字符例如:, = ~
\d0~9任意数字

4.数词

  数词不表示实际字符,用来表示在他前面的字符或字符组合数量。

字符说明备注/例子
+表示匹配≥1个例如:A+,表示匹配1个以上的“A”
*表示匹配≥0个例如:A*,表示匹配多个“A”,但是也允许没有“A”
?表示匹配0个或1个例如:A?,表示匹配一个“A”,但是也允许没有“A”
{}表示精确匹配的个数:
{n}匹配n个
{n,}匹配n个以上
{m,n}匹配m到n个(m≤n)
例如:
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”后windows 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接口都提供有“匹配”方法,使用正则表达式进行匹配,可以实现对数据格式验证。

语言方法/函数
javascriptRegExp.test
phppreg_match
javaPattern.matches
pythonre._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个字符。
  如果正则表达式中含有这些普通字符,必须在前面加上转义符,将其回归为普通字符!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值