正则表达式,又称规则表达式**,**(Regular Expression,在代码中常简写为regex、regexp或RE),它是一种文本模式,同时也是计算机科学的一个概念,其中包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为"元字符")。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串,通常被用来检索、替换那些符合某个模式(规则)的文本。
许多程序设计语言都支持利用正则表达式进行字符串操作。例如,在Perl中就内建了一个功能强大的正则表达式引擎。正则表达式这个概念最初是由Unix中的工具软件(例如sed和grep)普及开来的,后来才逐渐被广泛运用于Scala 、PHP、C# 、Java、C++ 、Objective-c、Perl 、Swift、VBScript 、Javascript、Ruby 以及Python等等。正则表达式通常缩写成“regex”,单数有regexp、regex,复数有regexps、regexes、regexen。
基本概念
正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
简介
正则表达式(Regex)是一种强大的文本模式匹配工具,用于对字符串进行搜索、替换和验证。它利用一组特定的字符和“元字符”,构成规则字符串,从而定义如何识别和操作文本。
它的主要功能包括:模式匹配,可以识别特定格式的字符串,例如电子邮件地址、电话号码等;字符串搜索,在大量文本中快速找到符合特定模式的内容;文本替换,可以根据匹配的模式,对字符串进行替换操作;验证输入,检查用户输入是否符合预定格式(如密码复杂度、日期格式等),等等。
主要应用方面包括:数据清洗,在数据处理和分析中,去除无效或不必要的文本;Web开发,验证表单输入、解析URL等;文本编辑器,搜索和替换功能;编程语言,在代码中处理字符串的复杂需求,等等。
总的来说,正则表达式是一种文本模式,该模式描述在搜索文本时要匹配的一个或多个字符串。1
起源与现状
1 正则表达式的起源
正则表达式的“鼻祖”或许可一直追溯到科学家对人类神经系统工作原理的早期研究。美国新泽西州的Warren McCulloch和出生在美国底特律的Walter Pitts这两位神经生理方面的科学家,研究出了一种用数学方式来描述神经网络的新方法,他们创造性地将神经系统中的神经元描述成了小而简单的自动控制元,从而作出了一项伟大的工作革新。
2 数学模型的提出
在1951 年,一位名叫Stephen Kleene的数学科学家,他在Warren McCulloch和Walter Pitts早期工作的基础之上,发表了一篇题目是《神经网事件的表示法》的论文,利用称之为正则集合的数学符号来描述此模型,引入了正则表达式的概念。正则表达式被作为用来描述其称之为“正则集的代数”的一种表达式,因而采用了“正则表达式”这个术语。
3 技术落地
之后一段时间,人们发现可以将这一工作成果应用于其他方面。1968年,Unix之父Ken Thompson就把这一成果应用于计算搜索算法的一些早期研究。5Ken Thompson将此符号系统引入编辑器QED,然后是Unix上的编辑器ed,并最终引入grep。Jeffrey Friedl 在其著作《Mastering Regular Expressions (2nd edition)》(中文版译作:精通正则表达式,已出到第三版)中对此作了进一步阐述讲解,如果你希望更多了解正则表达式理论和历史,推荐你看看这本书。
自此以后,正则表达式被广泛地应用到各种UNIX或类似于UNIX的工具中,如大家熟知的Perl。Perl的正则表达式源自于Henry Spencer编写的regex,之后已演化成了pcre(Perl兼容正则表达式Perl Compatible Regular Expressions),pcre是一个由Philip Hazel开发的、为很多现代工具所使用的库。正则表达式的第一个实用应用程序即为Unix中的 qed 编辑器。
4 发展现状
近六十年间,正则表达式经历了从模糊而深奥的数学概念到现代计算机工具和软件包中核心功能的转变。它不仅受到众多UNIX工具的支持,在过去二十年中,正则表达式的理念和应用也逐渐被广泛融入Windows开发者工具包。正则表达式在Microsoft Visual Basic 6、VBScript及.NET Framework中的发展,使得Windows系列产品在这一领域的支持达到了前所未有的高度,几乎所有Microsoft开发者及.NET语言用户均可利用这一强大功能。
作为接触计算机语言的专业人士,会发现正则表达式在主流操作系统(如Linux、Unix、Windows、HP、BeOS等)和多种开发语言(包括Delphi、Scala、PHP、C#、Java、C++、Objective-C、Swift、VB、JavaScript、Ruby及Python等)中无处不在。这一强大工具在数以亿计的应用软件中展现出其独特而优雅的能力,极大地促进了文本处理和数据验证的效率与准确性。1
目的
给定一个正则表达式和另一个字符串,我们可以达到如下的目的:
1. 判断给定的字符串是否符合正则表达式的过滤逻辑(称作“匹配”):
2. 可以通过正则表达式,从字符串中获取我们想要的特定部分。
特点
正则表达式的特点包括:
1. 模式匹配:用于查找和匹配字符串中符合特定模式的部分。
2. 灵活性:支持复杂的匹配规则,如字符类、量词和分组。
3. 可读性:虽然复杂的正则表达式可能难以理解,但基本模式相对简单。
4. 多种语法:不同编程语言和工具可能有细微差别,但基本概念大致相同。
5. 性能考虑:在处理大文本时,复杂的正则表达式可能导致性能下降。
6. 替换功能:不仅可以匹配,还可以进行字符串替换。
7. 支持回溯:允许在匹配失败时回退,尝试不同的匹配路径。
8. 字符转义:使用反斜杠来转义特殊字符。
这些特点使得正则表达式在文本处理和数据验证等领域非常有用。由于正则表达式主要应用对象是文本,因此它在各种文本编辑器场合都有应用,小到著名编辑器EditPlus,大到Microsoft Word、Visual Studio等大型编辑器,都可以使用正则表达式来处理文本内容。
正则引擎
按照确定性来划分,正则引擎主要可以分为两大类:一种是确定型有穷自动机(Deterministic finite automaton, DFA),一种是非确定型有穷自动机(Non-deterministic finite automaton,NFA)。这两种引擎都有漫长的历史,在这过程中他们产生了很多变体。于是就有了POSIX的出台,这种措施规避了不必要变体的继续产生。这样一来,现在主流的正则引擎就进一步分为了3类:一、DFA,二、传统型NFA,三、POSIX NFA。
DFA 引擎在线性时状态下执行,因为它们不要求回溯(并因此它们永远不测试相同的字符两次)。DFA 引擎还可以确保匹配最长的可能的字符串。但是,因为 DFA 引擎只包含有限的状态,所以它不能匹配具有反向引用的模式;并且因为它不构造显示扩展,所以它不可以捕获子表达式。
传统的 NFA 引擎运行所谓的“贪婪的”匹配回溯算法,以指定顺序测试正则表达式的所有可能的扩展并接受第一个匹配项。因为传统的 NFA 构造正则表达式的特定扩展以获得成功的匹配,所以它可以捕获子表达式匹配和匹配的反向引用。但是,因为传统的 NFA 回溯,所以它可以访问完全相同的状态多次(如果通过不同的路径到达该状态)。因此,在最坏情况下,它的执行速度可能非常慢。因为传统的 NFA 接受它找到的第一个匹配,所以它还可能会导致其他(可能更长)匹配未被发现。
POSIX NFA 引擎与传统的 NFA 引擎类似,不同的一点在于:在它们可以确保已找到了可能的最长的匹配之前,它们将继续回溯。因此,POSIX NFA 引擎的速度慢于传统的 NFA 引擎;并且在使用 POSIX NFA 时,您恐怕不会愿意在更改回溯搜索的顺序的情况下来支持较短的匹配搜索,而非较长的匹配搜索。
使用DFA引擎的程序主要有:awk,egrep,flex,lex,MySQL,Procmail等;
使用传统型NFA引擎的程序主要有:GNU Emacs,Java,ergp,less,more,.NET语言,PCRE library,Perl,PHP,Python,Ruby,sed,vi;
使用POSIX NFA引擎的程序主要有:mawk,Mortice Kern Systems’ utilities,GNU Emacs(使用时可以明确指定);
也有使用DFA/NFA混合的引擎:GNU awk,GNU grep/egrep,Tcl。
举例简单说明NFA与DFA工作的区别:
NFA(非确定性有限自动机)和DFA(确定性有限自动机)是两种用于识别正则语言的计算模型。它们的工作原理和特性有所不同,以下是简单说明及示例。
- NFA(非确定性有限自动机)
- 特性:
- 允许多个状态同时进行转换。
- 可以有 ε-转移(不消耗输入字符的转移)。
- 不必对每个输入字符都有确定的下一个状态。
- 示例:
假设我们有一个 NFA 用于识别字符串 "ab"。状态转换可以如下所示:
状态 0 --a--> 状态 1
状态 1 --b--> 状态 2
状态 0 --ε--> 状态 2 (ε-转移)
在这个 NFA 中,输入字符串 "ab" 会从状态 0 经过状态 1 到达状态 2。而在状态 0,系统可以选择 ε-转移直接到状态 2。
- DFA(确定性有限自动机)
- 特性:
- 每个状态对于每个输入字符只有一个确定的下一个状态。
- 不允许 ε-转移。
- 运行时总是处于一个确定的状态。
- 示例:
假设我们有一个 DFA 识别同样的字符串 "ab"。状态转换如下:
状态 0 --a--> 状态 1
状态 1 --b--> 状态 2
对于输入字符串 "ab",DFA 从状态 0 开始,接收字符 'a' 转移到状态 1,然后接收字符 'b' 转移到状态 2。每一步都是确定的,没有其他选择。
主要区别
- 确定性:
- NFA 可以有多个下一状态,而 DFA 只有一个。
- 转换方式:
- NFA 允许 ε-转移,而 DFA 不允许。
- 实现复杂度:
- NFA 更灵活但实现复杂,DFA 简单但可能需要更多的状态。
NFA 允许更多的灵活性和简化的设计,但在运行时可能需要同时跟踪多个状态。DFA 通过明确的状态转移提高了运行效率,但可能需要更多状态来实现同样的功能。
由此可以看出来,两种引擎的工作方式完全不同,一个(NFA)以表达式为主导,一个(DFA)以文本为主导。一般而论,DFA引擎则搜索更快一些。但是NFA以表达式为主导,反而更容易操纵,因此一般程序员更偏爱NFA引擎。 两种引擎各有所长,而真正的引用则取决与你的需要以及所使用的语言。2
解决实际问题
在文本解析、数据验证和字符串替换等领域,正则表达式提供了一种简洁而高效的方法。与其他字符串处理方法相比,在处理实际问题中,正则表达式在处理复杂模式时具有显著优势。
1 正则表达式的优势
正则表达式能够以非常紧凑的方式表达复杂的模式匹配。例如,相比使用多个字符串操作函数,正则表达式可以在一行代码中实现相同的功能。
1. 灵活性
正则表达式支持多种字符类、重复、选择和分组等高级特性,使得它可以处理各种复杂的匹配需求,而传统的方法通常需要更多的代码和条件判断。
2. 一致性
使用正则表达式可以确保相同的模式在不同字符串中的一致匹配。这种一致性在处理大量文本数据时尤为重要。
3. 性能
对于大规模文本的匹配和替换,正则表达式通常比传统的循环和条件语句更高效,尤其是在处理复杂模式时。
2 实例简示
#### 1. 数据验证
**传统方法:**
使用字符串操作和条件语句来验证数据格式通常会显得冗长且容易出错。例如,验证电子邮件格式需要多个步骤来检查不同的条件。
**正则表达式示例:**
import re
\# 定义一个电子邮件正则表达式
email\_pattern = r'^\[a-zA-Z0-9.\_%+-\]+@\[a-zA-Z0-9.-\]+\\.\[a-zA-Z\]{2,}$'
def validate\_email(email):
return bool(re.match(email\_pattern, email))
\# 测试验证函数
print(validate\_email("example@example.com")) # 输出: True
print(validate\_email("invalid-email@.com")) # 输出: False
相比之下,使用正则表达式可以在一行代码中完成复杂的验证,大大简化了代码的复杂性。
#### 2. 文本搜索和替换
**传统方法:**
使用字符串的 .replace()
方法进行替换通常只能替换精确的字符串,无法处理模式匹配。
**正则表达式示例:**
import re
\# 示例文本
text = "请联系我:电话是123-456-7890,或者拨打987-654-3210。"
\# 定义电话号码的正则表达式
phone\_pattern = r'\\d{3}-\\d{3}-\\d{4}'
\# 替换电话号码为"\[电话号码\]"
modified\_text = re.sub(phone\_pattern, "\[电话号码\]", text)
print(modified\_text) # 输出: 请联系我:电话是\[电话号码\],或者拨打\[电话号码\]。
正则表达式可以灵活地处理复杂的模式,进行批量替换,而无需编写多个替换逻辑。
#### 3. 从文本中提取信息
**传统方法:**
使用字符串的 .find()
或 .split()
方法来提取特定信息通常需要多次操作,并且难以处理复杂的结构。
**正则表达式示例:**
import re
\# 示例日志文本
log\_data = """
2024-09-27 10:00:00 INFO User logged in from 192.168.1.1
2024-09-27 10:05:00 ERROR Connection failed from 10.0.0.1
"""
\# 定义IP地址的正则表达式
ip\_pattern = r'(\\d{1,3}\\.){3}\\d{1,3}'
\# 提取所有IP地址
ip\_addresses = re.findall(ip\_pattern, log\_data)
print(ip\_addresses) # 输出: \['192.168.1.1', '10.0.0.1'\]
正则表达式可以一行代码提取出所有匹配的项,而传统方法则需要复杂的逻辑和多个步骤。
#### 4. 数据清洗
**传统方法:**
如果使用字符串的 .replace()
和其他函数去除特殊字符,通常需要多次调用,代码也显得繁琐。
**正则表达式示例:**
import re
\# 示例数据
data = "Hello, World! @2024 #Python #Regex"
\# 定义去除特殊字符的正则表达式
clean\_pattern = r'\[^a-zA-Z0-9\\s\]'
\# 清洗数据
cleaned\_data = re.sub(clean\_pattern, '', data)
print(cleaned\_data) # 输出: Hello World 2024 Python Regex
3 具体案例与代码解析
以下是一个问题背景以及解决方案的详细分析。
### 问题背景
假设我们有一个文本文件,其中包含了许多电子邮件地址。我们的目标是从这些文本中提取所有有效的电子邮件地址,以便进行后续的处理(如发送通知、进行数据分析等)。
### 电子邮件地址的基本格式
电子邮件地址通常由以下部分组成:
- 用户名:可以包含字母、数字、下划线(_)、点(.)和短横线(-)。
- @符号。
- 域名:可以包含字母、数字、点(.)和短横线(-),并且必须以一个字母结尾。
### 正则表达式设计
为了匹配上述电子邮件地址的格式,我们可以设计如下的正则表达式:
\[a-zA-Z0-9.\_%+-\]+@\[a-zA-Z0-9.-\]+\\.\[a-zA-Z\]{2,}
- \[a-zA-Z0-9.\_%+-\]+
:匹配一个或多个字母、数字、下划线、点、百分号、加号和短横线(用户名部分)。
- @
:匹配@符号。
- \[a-zA-Z0-9.-\]+
:匹配一个或多个字母、数字、点和短横线(域名部分)。
- \\.
:匹配点(.)符号。
- \[a-zA-Z\]{2,}
:匹配两个或更多字母(域的后缀)。
### 代码示例
以下是一个 Python 的代码示例,展示如何使用正则表达式来提取电子邮件地址:
<pre data-lang="python">```pythonimport re# 假设这是我们要处理的文本text = """ Hello, you can reach me at john.doe@example.com. My backup email is johndoe123@gmail.co.uk. Please don't contact invalid-email@.com or just@wrong, thanks! """ # 定义正则表达式 email_pattern = r'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}' # 使用 re.findall() 提取所有匹配的电子邮件地址 emails = re.findall(email_pattern, text) # 输出结果 print("提取的电子邮件地址:") for email in emails: print(email)```
### 代码分析
1. **导入模块**:我们首先导入 re
模块,这是 Python 中用于正则表达式操作的标准库。
2. **定义文本**:我们定义一个多行字符串 text
,其中包含我们想要提取的电子邮件地址。
3. **定义正则表达式**:我们将之前设计的正则表达式存储在 email\_pattern
变量中。
4. **提取电子邮件**:使用 re.findall()
方法,该方法会返回所有匹配的结果列表。
5. **输出结果**:我们循环遍历提取出的电子邮件地址并打印它们。
### 输出示例
运行上述代码后,输出结果如下所示:
提取的电子邮件地址:
john.doe@example.com
johndoe123@gmail.co.uk
正则表达式可以在一个简单的表达式中完成复杂的清洗操作,提高了代码的可读性和可维护性。
正则表达式是处理字符串的强大工具,其优势在于简洁性、灵活性、一致性和性能。无论是在数据验证、文本搜索与替换、信息提取还是数据清洗方面,正则表达式都能够提供高效且优雅的解决方案。掌握正则表达式的使用,能够极大地提高开发者在文本数据处理中的效率和准确性。
组成符号
(摘自《正则表达式之道》)
正则表达式由一些普通字符和一些元字符(metacharacters)组成。普通字符包括大小写的字母和数字,而元字符则具有特殊的含义,我们下面会给予解释。
在最简单的情况下,一个正则表达式看上去就是一个普通的查找串。例如,正则表达式"testing"中没有包含任何元字符,它可以匹配"testing"和"testing123"等字符串,但是不能匹配"Testing"。
要想真正的用好正则表达式,正确的理解元字符是最重要的事情。下表列出了所有的元字符和对它们的一个简短的描述。
|| ||
来源: 百度百科
内容资源由项目单位提供