介绍正则表达式

正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。换句话说,正则表达式就是记录文本规则的代码。正则表达式(Regular Expression)是一种文本模式,包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。

正则表达式的工作机制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
    +--------+
    |  编译  |
    +--------+
         |
         ↓
+----------------+
|  设置开始位置    |←---------+
+----------------+          ↑
         |                  |
         ↓               其 |
+----------------+       他 |
|  匹配 & 回溯    |       路 |
+----------------+       径 |
         |                  |
         ↓                  |
+----------------+          |
|  成功 or 失败   |---------→+
+----------------+

常用的元字符

代码 说明
. 匹配除换行符以外的任意字符
\w 匹配字母或数字或下划线或汉字
\s 匹配任意的空白符
\d 匹配数字
\b 匹配单词的开始或结束
^ 匹配字符串的开始
$ 匹配字符串的结束
元字符 描述
. 句号匹配任意单个字符除了换行符.
[ ] 字符种类. 匹配方括号内的任意字符.
[^ ] 否定的字符种类. 匹配除了方括号里的任意字符
* 匹配>=0个重复的在*号之前的字符.

|+|匹配>=1个重复的+号前的字符. |?|标记?之前的字符为可选.| |{n,m}|匹配num个中括号之前的字符 (n <= num <= m).| |(xyz)|字符集, 匹配与 xyz 完全相等的字符串.| |||或运算符,匹配符号前或后的字符.| |\|转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ || |^|从开始行开始匹配.| |$|从末端开始匹配.|

锚点

在正则表达式中, 想要匹配指定开头或结尾的字符串就要使用到锚点. ^ 指定开头, $ 指定结尾.

^ 用来检查匹配的字符串是否在所匹配字符串的开头. 例如, 在 abc 中使用表达式 ^a 会得到结果 a. 但如果使用 ^b 将匹配不到任何结果. 因为在字符串 abc 中并不是以 b 开头. 例如, ^(T|t)he 匹配以 Thethe 开头的字符串. 同理于 ^ 号, $ 号用来匹配字符是否是最后一个. 例如, (at\.)$ 匹配以 at. 结尾的字符串.

重复

代码 说明
* 重复零次或更多次
+ 重复一次或更多次
? 重复零次或一次
{n} 重复n次
{n,} 重复n次或更多次
{n,m} 重复n到m次

反义 有时需要查找不属于某个能简单定义的字符类的字符。比如想查找除了数字以外,其它任意字符都行的情况,这时需要用到反义:

代码 说明
\W 匹配任意不是字母,数字,下划线,汉字的字符
\S 匹配任意不是空白符的字符
\D 匹配任意非数字的字符
\B 匹配不是单词开头或结束的位置
[^x] 匹配除了x以外的任意字符
[^aeiou] 匹配除了aeiou这几个字母以外的任意字符

简写字符集

正则表达式提供一些常用的字符集简写. 如下:

简写 描述
. 除换行符外的所有字符
\w 匹配所有字母数字, 等同于 [a-zA-Z0-9_]
\W 匹配所有非字母数字, 即符号, 等同于: [^\w]
\d 匹配数字: [0-9]
\D 匹配非数字: [^\d]
\s 匹配所有空格字符, 等同于: [\t\n\f\r\p{Z}]
\S 匹配所有非空格字符: [^\s]
\f 匹配一个换页符
\n 匹配一个换行符
\r 匹配一个回车符
\t 匹配一个制表符
\v 匹配一个垂直制表符
\p 匹配 CR/LF (等同于 \r\n),用来匹配 DOS 行终止符

回车"(carriage return)和"换行"(line feed) 的区别

回车每行后面加两个表示结束的字符。一个叫做"回车",告诉打字机把打印头定位在左边界;另一个叫做"换行",告诉打字机把纸向下移一行。从英文单词上也是能get 到意思的。

‘\n’ 10 换行(newline) ‘\r’ 13 回车(return)

回车 \r 本义是光标重新回到本行开头,r的英文return,控制字符可以写成CR,即Carriage Return 换行 \n 本义是光标往下一行(不一定到下一行行首),n的英文newline,控制字符可以写成LF,即Line Feed

不同操作系统下的含义:

\n: UNIX 系统行末结束符 \r\n: window 系统行末结束符 \r: MAC OS 系统行末结束符

软回车和硬回车

硬回车就是普通我们按回车产生的,它在换行的同时也起着段落分隔的作用。 软回车是用 Shift + Enter 产生的,它换行,但是并不换段,即前后两段文字在 Word 中属于同一“段”。在应用格式时你会体会到这一点。软回车能使前后两行的行间距大幅度缩小,因为它不是段落标记,要和法定的段落标记——硬回车区别出来。硬回车的html代码是 <p>..</p>,段落的内容就夹在里面,而软回车的代码很精悍 <br>。网页的文字如果复制到word中,则硬回车变为弯曲的箭头,软回车变为向下的箭头。

标志

标志也叫修饰语, 因为它可以用来修改表达式的搜索结果. 这些标志可以任意的组合使用, 它也是整个正则表达式的一部分.

标志 描述
i 忽略大小写.
g 全局搜索.
m 多行的: 锚点元字符 ^ $ 工作范围在每行的起始.

修饰语 i 用于忽略大小写。例如, 表达式 /The/gi 表示在全局搜索 The, 在后面的 i 将其条件修改为忽略大小写, 则变成搜索 theThe, g 表示全局搜索.

修饰符 g 常用语执行一个全局搜索匹配, 即(不仅仅返回第一个匹配的, 而是返回全部). 例如, 表达式 /.(at)/g 表示搜索 任意字符(除了换行) + at, 并返回全部结果.

多行修饰符 m 常用语执行一个多行匹配. 像之前介绍的 (^,$) 用于检查格式是否是在待检测字符串的开头或结尾. 但我们如果想要它在每行的开头和结尾生效, 我们需要用到多行修饰符 m. 例如, 表达式 /at(.)?$/gm 表示在待检测字符串每行的末尾搜索 at后跟一个或多个 . 的字符串, 并返回全部结果.

分枝条件 (定义了几种不同的匹配规则) 不幸的是,刚才那个表达式也能匹配(010)12345678或(022-87654321这样的“不正确”的格式。要解决这个问题,我们需要用到分枝条件。正则表达式里的分枝条件指的是有几种规则,如果满足其中任意一种规则都应该当成匹配,具体方法是用|把不同的规则分隔开。听不明白?没关系,看例子:

0\d{2}-\d{8}|0\d{3}-\d{7}这个表达式能匹配两种以连字号分隔的电话号码:一种是三位区号,8位本地号(如010-12345678),一种是4位区号,7位本地号(0376-2233445)。

(?0\d{2})?[- ]?\d{8}|0\d{2}[- ]?\d{8}这个表达式匹配3位区号的电话号码,其中区号可以用小括号括起来,也可以不用,区号与本地号间可以用连字号或空格间隔,也可以没有间隔。你可以试试用分枝条件把这个表达式扩展成也支持4位区号的。

\d{5}-\d{4}|\d{5}这个表达式用于匹配美国的邮政编码。美国邮编的规则是5位数字,或者用连字号间隔的9位数字。之所以要给出这个例子是因为它能说明一个问题:使用分枝条件时,要注意各个条件的顺序。如果你把它改成\d{5}|\d{5}-\d{4}的话,那么就只会匹配5位的邮编(以及9位邮编的前5位)。原因是匹配分枝条件时,将会从左到右地测试每个条件,如果满足了某个分枝的话,就不会去再管其它的条件了。

分组(使用index 访问使用很广泛)

我们已经提到了怎么重复单个字符(直接在字符后面加上限定符就行了);但如果想要重复多个字符又该怎么办?你可以用小括号来指定子表达式(也叫做分组),然后你就可以指定这个子表达式的重复次数了,你也可以对子表达式进行其它一些操作(后面会有介绍)。

(\d{1,3}.){3}\d{1,3}是一个简单的IP地址匹配表达式。要理解这个表达式,请按下列顺序分析它:\d{1,3}匹配1到3位的数字,(\d{1,3}.){3}匹配三位数字加上一个英文句号(这个整体也就是这个分组)重复3次,最后再加上一个一到三位的数字(\d{1,3})。

不幸的是,它也将匹配256.300.888.999这种不可能存在的IP地址。如果能使用算术比较的话,或许能简单地解决这个问题,但是正则表达式中并不提供关于数学的任何功能,所以只能使用冗长的分组,选择,字符类来描述一个正确的IP地址:((2[0-4]\d|25[0-5]|[01]?\d\d?).){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)。

常见的例子

除了使用[]表示或逻辑,()也是可以的。用法是(a|b)表示a或者b

  • 匹配邮箱
1
2
3
gaoyaqi411@126.com  
dyumc@google.net 
sam@sjtu.edu

步骤

  1. 任何一个以words开头的,一个或更多 \w+
  2. 紧接着是一个@符号 \w+@
  3. 接着有一个或者更多的words \w+@\w+
  4. 接着一个.标点 \w+@\w+.
  5. 接着一个com net 或 edu \w+@\w+.(com|net|edu)

手机号正则

1
/^1[34578][0-9]{9}$/

单词边界

1
/\bis\b/

URL分组替换

1
/http:(\/\/.+\.jpg)/

正则表达式使用小括号用来分组,这个时候我们可以通过用 $1来获取 group#1的内容。 获取内容是为了 replace

正则表达式由两种基本字符组成:

  • 原义字符
  • 非打印字符
  • 元字符 (* + ? $ ^ . | ( ) { } )

非打印字符包括换行符、回车符号, 分页符等等

字符类取反 [^] 范围类 [-] 预定义类 (这个是为了方便使用,在上面也被称为 简写类)

边界类

^ 表示开头, $ 表示结尾, \b 表示单词边界, \B 表示非单词边界

量词

正则表达式默认会匹配贪婪模式,什么是贪婪模式呢?如其名尽可能多的匹配。我们看个例子 与贪婪对应就是懒惰模式,懒惰对应的就是匹配的尽可能少的情况。如何开启懒惰模式? 在量词后面加?。继续上面的例子

1
/\d{3,6}?/

邮箱正则表达式实例

分析邮件名称部分:

26个大小写英文字母表示为a-zA-Z

  • 数字表示为0-9
  • 下划线表示为_
  • 中划线表示为-
  • 由于名称是由若干个字母、数字、下划线和中划线组成,所以需要用到+表示多次出现

根据以上条件得出邮件名称表达式:[a-zA-Z0-9_-]+

分析域名部分:

常用正则表达式—邮箱(Email)

分析邮件名称部分:

汉字在正则表示为 [\u4e00-\u9fa5] 字母和数字表示为 A-Za-z0-9  通过分析得出邮件名称部分表达式为 [A-Za-z0-9\u4e00-\u9fa5]+

C++ regex函数有3个:regex_match、 regex_search 、regex_replace

match是全文匹配,即要求整个字符串符合匹配规则。

1
2
cout << regex_match("123", regex("\\d")) << endl;		//结果为0
cout << regex_match("123", regex("\\d+")) << endl;		//结果为1

search是搜索匹配,即搜索字符串中存在符合规则的子字符串。

1
2
cout << regex_match("123", regex("\\d")) << endl;		//结果为0
cout << regex_search("123", regex("\\d")) << endl;		//结果为1

regex_search和regex_match的主要区别是:regex_match是全词匹配,而regex_search是搜索其中匹配的字符串

regex_replace是替换正则表达式匹配内容的函数 replace是替换匹配,即可以将符合匹配规则的子字符串替换为其他字符串。

1
2
3
4
string str = "Hello_2018!";
regex pattern("Hello");	
cout << regex_replace(str, pattern, "") << endl;	//输出:_2018,将Hello替换为""
cout << regex_replace(str, pattern, "Hi") << endl;	//输出:Hi_2018,将Hello替换为Hi

有时我们希望能够匹配的时候忽略大小写,这时候就要用到Regex的语法选项了。

1
2
cout << regex_match("aaaAAA", regex("a*", regex::icase)) << endl;	//结果为1
cout << regex_match("aaaAAA", regex("a*")) << endl;		

针对python 的正则表达式的练习题

1
2
3
4
5
6
7
8
1search(pattern, string, flags=0)      在一个字符串中查找匹配

2findall(pattern, string ,flags=0)     找到匹配,返回所有匹配部分的列表

3sub(pattern, repl, string , count=0, flags=0)    将字符串中匹配正则表达式的部分替换为其他值

4split(pattern, string ,maxsplit=0, flags=0)  根据匹配分割字符串,返回分隔符串组成的列表

开始总结 python 版本的正则表达式

正则表达式 (Regular Expression) 又称 RegEx, 是用来匹配字符的一种工具. 在一大串字符中寻找你需要的内容. 它常被用在很多方面, 比如网页爬虫, 文稿整理, 数据筛选等等. 现在都是比较广泛学习,需要知道这里面都是有什么,等到用的时候,再好好琢磨,因为内容还是很多的呀。

要注意的是,正则表达式并不是一个程序,而是用于处理字符串的一种模式,如果你想用它来处理字符串,就必须使用支持正则表达式的工具,比如 Linux 中的 awk, sed, grep,或者编程语言 Perl, Python, Java 等等。

这个是一个引子, 关键字 in 表示字符串的匹配关系

1
2
3
4
5
6
# matching string
pattern1 = "cat"
pattern2 = "bird"
string = "dog runs to cat"
print(pattern1 in string)    # True
print(pattern2 in string)    # False

如果想要使用更加强大的功能,那么使用 re 模块, 于是就是我们今天的主角- 正则匹配。 re 模块中提供了不少有用的函数, 比如

  • compile 函数
  • match 函数
  • search 函数
  • findall 函数
  • finditer 函数
  • split 函数
  • sub 函数
  • subn 函数

re 模块的一般使用的步骤

  • 使用 compile 函数将正则表达式的字符串形式编译为一个 Pattern 对象
  • 通过 Pattern 对象提供的一系列方法对文本进行匹配查找,获得匹配结果(一个 Match 对象)
  • 最后使用 Match 对象提供的属性和方法获得信息,根据需要进行其他的操作

compile 函数 compile 函数用于编译正则表达式,生成一个 Pattern 对象,它的一般使用形式如下:

1
re.compile(pattern[, flag])

其中,pattern 是一个字符串形式的正则表达式,flag 是一个可选参数,表示匹配模式,比如忽略大小写,多行模式等。

match 方法

match 方法用于查找字符串的头部(也可以指定起始位置),它是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果。

search 方法 search 方法用于查找字符串的任何位置,它也是一次匹配,只要找到了一个匹配的结果就返回,而不是查找所有匹配的结果

findall 方法 上面的 match 和 search 方法都是一次匹配,只要找到了一个匹配的结果就返回。然而,在大多数时候,我们需要搜索整个字符串,获得所有匹配的结果。

finditer 方法 finditer 方法的行为跟 findall 的行为类似,也是搜索整个字符串,获得所有匹配的结果。但它返回一个顺序访问每一个匹配结果(Match 对象)的迭代器。

split 方法 split 方法按照能够匹配的子串将字符串分割后返回列表

sub 方法 sub 方法用于替换。

匹配中文

在某些情况下,我们想匹配文本中的汉字,有一点需要注意的是,中文的 unicode 编码范围 主要在 [\u4e00-\u9fa5],这里说主要是因为这个范围并不完整,比如没有包括全角(中文)标点,不过,在大部分情况下,应该是够用的。

1
2
3
4
5
6
# -*- coding: utf-8 -*-
import re
title = u'你好,hello,世界'
pattern = re.compile(ur'[\u4e00-\u9fa5]+')
result = pattern.findall(title)
print result

注意到,我们在正则表达式前面加上了两个前缀 ur,其中 r 表示使用原始字符串,u 表示是 unicode 字符串。

执行结果:

1
[u'\u4f60\u597d', u'\u4e16\u754c']

总结

re 模块的一般使用步骤如下:

  • 使用 compile 函数将正则表达式的字符串形式编译为一个 Pattern 对象;
  • 通过 Pattern 对象提供的一系列方法对文本进行匹配查找,获得匹配结果(一个 Match 对象);
  • 最后使用 Match 对象提供的属性和方法获得信息,根据需要进行其他的操作;

Python 的正则匹配默认是贪婪匹配。

总结的小抄,出处

在爬虫中的实践

使用requests 得到内容之后,然后使用 re 进行解析。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import requests
import re
content = requests.get('https://movie.douban.com/chart').text
# 豆瓣电影排行榜
pattern = re.compile('class="pl2".*?<.*?="(.*?)".*?>(.*?)<span.*?>(.*?)</span>.*?"rating_nums">(.*?)</span>.*?"pl">(.*?)</span>', re.S)
# compile可以在多次使用中提高效率,这里影响不大
results = re.findall(pattern, content)
for result in results:
    url, name1, name2, nums, pl = result
    print(url, name1.replace("/","").strip(), name2.replace("/","").strip(), nums, pl)

linux 中常见的正则匹配实例

(1)How to find files recursively by file type and copy them to a directory while in ssh? 在一个文件中迭代查找所有某类的文件

1
find . -name "*.pdf" -type f -exec cp {} ./pdfsfolder \;

正则匹配帮助文档

在线正则表达式测试

在线正则测试

细说python正则表达式 ,写的非常全 Python 正则表达式 re 模块 也是比较好的博客

这个是实战,写的很容易理解 https://segmentfault.com/a/1190000013075245 https://blog.csdn.net/make164492212/article/details/51656638

python中的正则表达

字符串是编程中涉及最多的一种数据结构。正则表达式是一种从来匹配字符串的强有力的武器,它的设计思想是用一种描述性的语言来给字符串顶一个规则,凡是符合规则的字符串,就认为其匹配了,否则字符串不合法。

正则匹配 的准备知识

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
\d 表示1个数字
\w 表示1个字母或数字
. 表示任意1个字符
* 表示任意个字符(包括0个)
+ 表示至少1个字符
? 表示0个或1个字符
{n} 表示n 个字符
{n,m} 表示 n-m 个字符

# 给出一些例子
\d{3} 表示3个数字
\s  表示1个空格(也包括Tab空白符)
\d{3,8} 表示3-8个数字
如果要匹配特殊字符 `-`,需要使用转移字符 `\`

[0-9a-zA-Z\-] 表示一个数字、字母或者下划线
[0-9a-zA-Z\-]+ 表示至少由一个数字、字母和下划线组成的字符串
[a-zA-Z\_][0-9a-zA-Z\_]{0,19} 匹配有字母或者下划线开头,后接一个由数字、字母或者下划线组成的字符串,是python中合法的变量。

A|B 可以匹配A或B, 所以 (p|P)ython 可以匹配'python'或者 'Python'
^ 表示行的开头,^\d 表示必须以数字开头
$ 表示行的结束,\d$ 表示必须以数字结束

在python 中建议使用 r前缀,这样就不用考虑转义问题。

1
2
3
4
# 可以写成这样
s = r'ABC\-001'
# 而不用写成这样
s = 'ABC\\-001'
  1. 判断是否匹配
1
2
3
4
5
6
7
import re

test =""
if re.match(r'', test):
    print("ok")
else:
    print("failed")
  1. 切分字符串
1
2
3
4
5
6
7
8
# 普通的split无法识别连续的空格
'a b   c'.split(' ')
# 正则表达式的 split() 可以划分连续的空格
re.split(r'\s+', 'a b    c')
['a', 'b', 'c']
# 更加复杂的切分方式
re.split(r'[\s\,\;]+', 'a,b;;c   d')
['a', 'b', 'c', 'd']
  1. 分组

正则表达式有提取子串的功能。用 () 表示提取的分组(group)

1
2
3
4
m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
m.group(0) # 原字符串
m.group(1) 
m.group(2)

group extraction

The “group” feature of a regular expression allows you to pick out parts of the matching text. 主要用于提取想要的子串

1
2
3
4
5
6
7
8
str = 'purple alice-b@google.com monkey dishwasher'
match = re.search(r'([\w.-]+)@([\w.-]+)', str)

if match:
    print( match.group()) # the whole match
    print(match.group(1)) # alice-b, (the username, group 1)
    print(match.group(2)) # google.com (the host, group 2)
 
  1. 贪婪匹配

正则表达式默认是贪婪匹配(尽可能多的匹配字符串)

1
2
re.match(r'^(\d+)(0*)$', '102300').groups()
# ('1023', '00')

由于\d+ 默认采用的是贪婪匹配,直接把后面的0给全部匹配了,所以 0* 只能匹配空字符串。必须让 +d+ 次用非贪婪匹配(尽可能少的匹配) 才能把后面的 0匹配出来。只需要加上一个? 就可以

1
re.match(r'^(\d+?)(0*)$', '102300').groups()

对于贪婪模式产生的解释:因为有 + 和* 的出现

First the search finds the leftmost match for the pattern, and second it tries to use up as much of the string as possible – i.e. + and * go as far as possible (the + and * are said to be “greedy”).

  1. re.findall()

If you want to find all the substrings in a string, We can just use the findall method of the re module (No need to loop) findall returns all non-overlapping matches of pattern in string, as a list of strings. The string is scanned left-to-right, and matches are returned in the order in which they are found.

If one or more groups are present in the pattern, findall returns a list of groups. 如果有多个pattern,那么返回的 returns a list of groups

使用 | 表示 alternations,比如以下的例子:

1
2
3
4
import re
str ="Course location is London or Paris!"
mo =re.search(r"location.*(London|Paris|Beijing)", str)
if mo: print(mo.group())

大小写问题

1
2
import re
re.findall(pattern, text, re.IGNORECASE)

使用参数,忽略大小写

findall() is probably the single most powerful function in the re module re.search()re.findall() 区别:前者只是找到 first match for a pattern, findall 是找到了全部的match,returns them as a list of strings

1
2
3
4
5
str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'

emails =re.findall(r'[\w\.-]+@[\w\.-]+', str)
for email in emails:
    print(email)

findall with files (这个功能是比较实用的)

1
2
f =open('test.txt', 'r')
strings =re.findall(r'some pattern', f.read())

findall and groups (使用的是上面涉及到的 () 的用法)给个的简单的例子

1
2
3
4
5
6
7
  str = 'purple alice@google.com, blah monkey bob@abc.com blah dishwasher'
  tuples = re.findall(r'([\w\.-]+)@([\w\.-]+)', str)
  print tuples  ## [('alice', 'google.com'), ('bob', 'abc.com')]
  for tuple in tuples:
    print tuple[0]  ## username
    print tuple[1]  ## host

  1. compile mode

python 中使用正则表达式的时候,re模块内部会做两件事情:(1)编译正则表达式 (2)用编译之后的表达式去匹配字符串

If you want to use the same regexp more than once in a script, it might be a good idea to use a regular expression object.

如果是多次使用,从效率的角度考虑,应该使用 compile mode

1
2
3
4
5
6
import re
regex = r"[A-z]{1,2}[0-9R][0-9A-Z]? [0-9][ABD-HJLNP-UW-Z]{2}"
address = "BBC News Centre, London, W12 7RJ"
compiled_re = re.compile(regex)
res = compiled_re.search(address)
print(res)
  1. re.search
1
2
3
4
# ^, matches the start of string
match =re.rearch(r'^b\w+', 'foobar')
# this failed
match =re.search(r'b\w+', 'foobar') 
  1. square brackets

Square brackets can be used to indicate a set of chars, so [abc] matches ‘a’ or ‘b’ or ‘c’. 这个是用来配置可选项的。

比如用以下的pattern 用来match 邮箱地址

1
2
3
match  =re.search(r'[\w.-]+@[\w.-]+', str)
if match:
    print(match.group()) ##