65.9K
CodeProject 正在变化。 阅读更多。
Home

正则表达式介绍

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (16投票s)

2001年1月26日

CPOL
viewsIcon

286597

介绍正则表达式 (RE) 的理论及其在实践中的应用。

本文档介绍了正则表达式 (RE) 的理论及其在实践中的应用。

目录

  1. 什么是正则表达式?
  2. 为什么要使用正则表达式?
  3. 在哪里使用正则表达式?
  4. 如何使用正则表达式语法基础?
  5. 正则表达式的语法基础
  6. 更多示例
  7. 摘要
  8. 参考资料

什么是正则表达式?

正则表达式是一种在字符串中搜索子字符串(“匹配”)的方法。这是通过使用“模式”在字符串中进行搜索来完成的。

示例

您可能知道在 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} mn 是非负整数,其中 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 的方法不止一种。有些更快,有些更容易阅读。

有关这些以及更多内容,请参阅下面的资源。

参考资料

  1. 学习 Perl (第二版)
    Randal L. Schwartz, Tom Christiansen, Larry Wall (序)
  2. 编程 Perl (第三版)
    Larry Wall, Tom Christiansen, Jon Orwant
  3. Perl 实用秘籍
    Tom Christiansen, Nathan Torkington, Larry Wall
  4. 精通正则表达式:Perl 及其他工具的强大技术(O'Reilly Nutshell)
    Jeffrey E. Friedl (编辑), Andy Oram (编辑)
  5. 正则表达式简介
    Microsoft Developer Network (MSDN), Microsoft Corporation
  6. Perl 5 Pocket Reference, 3rd Edition: Programming Tools (O'Reilly Perl)
    Johan Vromans, Larry Wall, Linda Mui
© . All rights reserved.