正则表达式介绍
介绍正则表达式 (RE) 的理论及其在实践中的应用。
本文档介绍了正则表达式 (RE) 的理论及其在实践中的应用。
目录
什么是正则表达式?
正则表达式是一种在字符串中搜索子字符串(“匹配”)的方法。这是通过使用“模式”在字符串中进行搜索来完成的。
示例
您可能知道在 DOS 命令行中 `dir` 命令使用的“*”和“?” 字符。“*”字符表示“零个或多个任意字符”,而“?”表示“一个任意字符”。
使用类似“text?.*
”的模式时,它将找到类似以下的 文件:
textf.txt
text1.asp
text9.html
但它不会找到类似以下的 文件:
text.txt
text.asp
text.html
这正是 RE 的工作方式。虽然“*”和“?” 是非常有限的模式匹配方式,但 RE 提供了更广泛的模式描述范围。
为什么要使用正则表达式?
示例用法可能包括:
- 从 HTML 文件中删除特定标签的所有出现
- 检查电子邮件地址是否格式正确
标准正则表达式操作
基本上,您可以使用 RE 对字符串执行以下操作:
- 测试模式
即,在字符串中搜索并检查模式是否匹配子字符串,返回 true 或 false。 - 提取子字符串
即,搜索子字符串并返回该子字符串。 - 替换子字符串
即,搜索匹配模式的子字符串并将其替换为另一个字符串。
在哪里使用正则表达式?
RE 是 Perl 编程语言的基础之一,因此内置于编译器本身。许多其他语言可以通过使用第三方库或插件来使用 RE。
以下是一些存在 RE 库的其他语言:
- VBScript (5.x 及以上版本),通过 `RegExp` 对象。
- JScript (5.x 及以上版本),同样通过 `RegExp` 对象。
- C++,通过 `Regex++` 库和 `PCRE` (Perl Compatible Regular Expression) 库。
- Java,使用 Apache 团队的 `ORO` (Perl 5 兼容)、`RegExp`、`Rex` 或 `gnu.regexp`。
- Microsoft 的 **.NET** 框架 (包括 **C#**),通过 `System.Text.RegularExpression` 命名空间内置 RE 支持。
- PHP,使用其内置的 Perl 兼容 RE 函数或 POSIX 扩展 RE 函数。
尽管使用方式略有不同(由于语言的设计),但它们都与 Perl 的 RE 实现非常相似。因此,本文档使用 Perl 代码片段来描述示例。
RE 语法尚未完全标准化。据我所知,有一个 POSIX 版本定义了完整的语法。Perl 的 RE 实现比 POSIX 的更灵活,因此通常需要一个尽可能与 Perl 兼容的库。
语法本身在不同语言之间可能有所不同。例如,一个库只实现 POSIX RE 语法的子集,而另一个库则实现几乎所有的 Perl RE 语法。
如何在 Perl 中使用正则表达式
如前所述,我将在 Perl 中进行所有示例。因此,这里是关于如何在 Perl 中执行正则表达式最常见方法的快速概述。
搜索字符串中的模式
expression =~ m/pattern/[switches]
搜索字符串 `expression` 中匹配“`pattern`”的子字符串的出现,并返回识别出的子表达式(`$1`、`$2`、`$3` 等)。“`m`”代表“match”。
例如:
$test = "this is just one test";
$test =~ m/(o.e)/
将返回“`one`”在 `$1` 中。
替换子字符串
expression =~ s/pattern/new text/[switches]
搜索字符串“`expression`”中匹配“`pattern`”的子字符串的出现,并将找到的子字符串替换为“`new text`”。“`s`”代表“substitute”。
例如:
$test = "this is just one test";
$test =~ s/one/my/
将把“`one`”替换为“`my`”,结果字符串为“`this is just my test`”,存储在 `$test` 中。
正则表达式的语法基础
本章无意成为 RE 模式中所有可用字符的参考。有 其他文档 做得很好。取而代之的是,将展示和解释基本的 *元字符*。
需要字面使用的元字符必须用反斜杠转义,就像 C++ 字符串一样。例如,要字面使用方括号 `[`,请写 `\[`。(请记住,这适用于 Perl 语言,对于其他语言可能有所不同)。
重要元字符
以下是 MSDN 中“正则表达式语法”一章中列出的最重要元字符。
字符 | 描述 |
---|---|
\ |
将下一个字符标记为特殊字符、字面字符、反向引用或八进制转义。例如,“`n`”匹配字符“`n`”。“`\n`”匹配换行符。序列“`\\`”匹配“`\`”,而“`\(”匹配“`(`”。 |
. |
匹配除“`\n`”之外的任何单个字符。要匹配包括“`\n`”在内的任何字符,请使用类似“`[.\n]”的模式。 |
字符类
字符类是包含一个或多个字符的组。这些字符用方括号“`[`...`]`”括起来。例如,构造“`B[iu]rma”匹配“`Birma”或“`Burma”,即“`B”后跟“`i”或“`u”,再后跟“`rma”。
换句话说,字符类表示“匹配该类中的任何单个字符”。
还有与字符类相对立的 *否定字符类*。这意味着“匹配不在该类中的任何单个字符”。例如,“`[^1-6]`”识别除数字“`1`”到“`6`”之外的任何字符。
在 MSDN 的“字符匹配”部分可以找到更多示例。
量词
如果您不确定有多少个字符,可以使用量词来指定字符可以出现的次数。例如,您可以说“`Hel+o”,这意味着“`He”后跟一个或多个“`l”,再后跟一个“`o”。
更多量词,来自 MSDN 中“量词”一章:
字符 | 描述 |
---|---|
* |
匹配前一个子表达式零次或多次。 例如,“`zo*”匹配“`z”和“`zoo”。“`*”等同于“`{0,}”。 |
+ |
匹配前一个子表达式一次或多次。 例如,“`zo+”匹配“`zo”和“`zoo”,但不匹配“`z”。“`+”等同于“`{1,}”。 |
? |
匹配前一个子表达式零次或一次。 例如,“`do(es)?”匹配“`does”中的“`do”或“`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?”。请注意,您不能在逗号和数字之间添加空格。 |
贪婪
关于量词的一个重要事实是,“`*”和“`+”是“贪婪”的。也就是说,它们尽可能多地匹配,而不是尽可能少。例如:
$test = "hello out there, how are you";
$test =~ m/h.*o/
表示“找到一个‘`h’,后面跟着任意数量的任意字符,后面跟着一个‘`o’”。作者可能认为它匹配“`hello”,但实际上它匹配“`hello out there, how are yo”,因为 RE 是贪婪的,会搜索直到 *最后一个*“`o”,也就是“`you”中的“`o”。
您可以通过附加“`?”来显式声明量词应该是“非贪婪”的。例如:
$test = "hello out there, how are you";
$test =~ m/h.*?o/
实际上会找到“`hello”,正如预期的那样,因为它现在表示“找到一个‘`h’,后面跟着任意数量的任意字符,后面跟着 *第一个出现* 的‘`o’”。
锚点
行首和行尾
要检查行(或字符串)的开头或结尾,请使用元字符 `^` 和 `$`。例如,“`^thing”匹配以“`thing”开头的行。“`thing$
”匹配以“`thing”结尾的行。
单词边界
元字符“`\b”和“`\B”用于测试单词边界和非单词边界。例如:
$test =~ m/out/
不仅会匹配“`speak out loud”中的“`out”,还会匹配“`please don't shout at me”中的“`out”。为避免这种情况,可以在模式前加上单词边界锚点:
$test =~ m/\bout/
现在,它只会在“`out”出现在单词边界处时才匹配,而不是在单词内部。
选择和分组
选择允许使用“`|”字符在两个或多个选项之间进行选择。将其与括号“`(...`|...`|...`)”结合使用,允许您对选项进行分组。
括号本身用于“捕获”子字符串以供以后使用,并将它们存储在 Perl 内置变量 `$1`、`$2`、...、`$9` 中。(参见下面的 反向引用)。
例如:
$test = "I like apples a lot";
$test =~ m/like (apples|pines|bananas)/
将匹配,因为“`apples”是三个匹配项之一,因此找到“`like apples”。括号还将“捕获”`apples` 作为 `$1` 中的反向引用。
反向引用、先行断言和后行断言
反向引用
RE 最重要的特性之一是能够存储(“捕获”)匹配子字符串的一部分以供以后重用。这是通过将子字符串放在括号 `(`...`)` 中实现的。这些被存储在 Perl 内置变量 `$1`、`$2`、...、`$9` 中。
如果您不想捕获子字符串,但需要括号来分组子字符串,请使用“`?:”运算符来避免捕获。
例如:
$test = "Today is monday the 18th."; $test =~ m/([0-9]+)th/
将把“`18”存储在 `$1` 中,而
$test = "Today is monday the 18th."; $test =~ m/[0-9]+th/
将不存储任何内容在 `$1` 中,因为没有出现括号。
$test = "Today is monday the 18th."; $test =~ m/(?:[0-9]+)th/
也将不存储任何内容在 `$1` 中,因为括号与“`?:”运算符一起使用。另一个在替换操作中直接使用的示例:
$test = "Today is monday the 18th."; $test =~ s/ the ([0-9]+)th/, and the day is $1/
将导致 `$test` 为“`Today is monday, and the day is 18.”。
您还可以使用 `\1`、`\2`、...、`\9` 在查询中反向引用先前找到的子字符串。例如,以下 RE 将删除重复的单词:
$test = "the house is is big"; $test =~ s/\b(\S+)\b(\s+\1\b)+/$1/
将导致 `$test` 为“`the house is big”。
先行断言和后行断言
有时需要说“匹配此,但前提是它不 *前导* 于那个”或“匹配此,但前提是它不 *跟随* 于那个”。当只涉及单个字符时,您可以使用否定字符类 `[^...`]`。
但是,当涉及的不仅仅是单个字符时,您需要使用所谓的 *先行断言* 或 *后行断言*。有四种可能的类型:
- 正向先行断言 '`(?=re)`'
仅当后面跟着 RE `re` 时才匹配。 - 负向先行断言 '`(?!re)`'
仅当后面不跟着 RE `re` 时才匹配。 - 正向后行断言 '`(?<=re)`'
仅当前面是 RE `re` 时才匹配。 - 负向后行断言 '`(?re)`'
仅当前面不是 RE `re` 时才匹配。
示例
$test = "HTML is a document description-language and not a programming-language";
$test =~ m/(?<=description-)language/
将匹配第一个“`language”(“`description-language”),因为它前面是“`description-”,而
$test = "HTML is a document description-language and not a programming-language";
$test =~ m/(?<!description-)language/
将匹配第二个“`language”(“`description-language”),因为它前面不是“`description-”。
更多示例
这里有一些来自 RE 部分 [3] 最后一章的更实际的示例。这些更高级的 RE 可以作为您自己 RE 的起点,或者仅仅是您可以更详细查看的详细示例。
交换前两个单词
s/(\S+)(\s+)(\S+)/$3$2$1/
查找 *name=value* 对
m/(\w+)\s*=\s*(.*?)\s*$/
现在 *name* 在 `$1` 中,*value* 在 `$2` 中。
读取格式为 *YYYY-MM-DD* 的日期
m/(\d{4})-(\d\d)-(\d\d)/
现在 *YYYY* 在 `$1` 中,*MM* 在 `$2` 中,*DD* 在 `$3` 中。
从文件名中删除前导路径
s/^.*\///
摘要
本文档试图为您提供 RE 是什么、在哪里以及如何使用它们的简要介绍。
尽管开始使用 RE 可能很简单,但在“现实生活”中可能会遇到许多陷阱和错误。强烈建议参考其他文献和示例来理解和使用 RE 的全部功能。特别是 [4] 是一个非常有价值(但有些挑剔)的资源,您应该阅读。
本文档未涵盖的主题包括:
- RE 的修饰符(也称为“开关”)
这些可用于设置区分大小写、单行和多行模式、扩展模式以便更好地查看等。 - RE 引擎的内部原理
不同类型的 RE 引擎(即 NFA 和 DFA)行为不同。 - 在 Perl 以外的语言中使用 RE
存在与 Perl 的 RE 实现不同的特定于语言的细节。 - 优化
编写 RE 的方法不止一种。有些更快,有些更容易阅读。
有关这些以及更多内容,请参阅下面的资源。
参考资料
- 学习 Perl (第二版)
Randal L. Schwartz, Tom Christiansen, Larry Wall (序) - 编程 Perl (第三版)
Larry Wall, Tom Christiansen, Jon Orwant - Perl 实用秘籍
Tom Christiansen, Nathan Torkington, Larry Wall - 精通正则表达式:Perl 及其他工具的强大技术(O'Reilly Nutshell)
Jeffrey E. Friedl (编辑), Andy Oram (编辑) - 正则表达式简介
Microsoft Developer Network (MSDN), Microsoft Corporation - Perl 5 Pocket Reference, 3rd Edition: Programming Tools (O'Reilly Perl)
Johan Vromans, Larry Wall, Linda Mui