跳至主要内容

R 正则表达式指南

探索 R 中的正则表达式、其重要性、相关工具与函数、常见模式,以及如何使用它们。
· 12分钟 阅读

使用 AI 探索

正则表达式(regular expressions,通常简称 regex)这一概念存在于许多编程语言中,如 R、Python、C、C++、Perl、Java 和 JavaScript。您可以在这些语言的基础版本或通过库来使用正则表达式功能。对大多数编程语言而言,regex 模式的语法大体相似。

在本教程中,我们将探讨 R 中的正则表达式是什么、为什么重要、有哪些工具与函数可以使用、哪些是最常见的 regex 模式,以及如何使用它们。最后,我们还将概览一些 R 正则表达式的进阶应用。

什么是 R 正则表达式,为什么要使用它们?

在 R 中,正则表达式(regex)是一串字符(甚至可以是单个字符),用于描述文本中出现的某种特定模式。Regex 模式可以像‘a’一样短,也可以像这条StackOverflow 讨论中提到的那样长。

广义上讲,上述对正则表达式的定义不仅适用于 R,也适用于任何支持正则表达式的编程语言。

Regex 是一种非常灵活而强大的工具,被广泛用于处理与挖掘非结构化文本数据。例如,它们被应用于搜索引擎、词法分析、垃圾邮件过滤以及文本编辑器中。

用于处理 R 正则表达式的工具与函数

尽管多数编程语言中的 regex 模式类似,但用于处理它们的函数各不相同。

在 R 中,我们可以使用 base R 的函数来检测、匹配、定位、提取和替换 regex。下面是一些主要函数:它们在字符向量中搜索 regex 匹配,然后执行相应操作:

  • grep()、grepl() —— 返回包含匹配项的字符串下标(grep()),或返回逻辑向量指示哪些字符串包含匹配(grepl())。

  • regexpr()、gregexpr() —— 返回每个字符串中匹配开始的位置索引及匹配长度。regexpr()仅给出第一个匹配(从左起),gregexpr()则给出所有匹配。

  • sub()、gsub() —— 将每个字符串中检测到的匹配替换为指定字符串(sub()仅替换第一个匹配,gsub()替换所有匹配)。

  • regexec() —— 类似于 regexpr(),但也会返回匹配中指定子表达式的信息。

  • regmatches() —— 类似于 regexec(),但返回整体匹配和指定子表达式所检测到的精确字符串。

不过,与其使用原生 R 函数,一种更方便且一致的方式是使用 stringr 包(属于 tidyverse 生态)。该库构建在 stringi 之上。在 stringr 中,所有函数都以 str_ 开头,且名称(以及可选参数的名称)比 base R 更直观。

要安装并加载 stringr 包,运行:

install.packages('stringr')
library(stringr)

下表展示了我们在本节先前讨论过的 base R 函数与 stringr 函数之间的对应关系:

stringr

Base R

str_subset()

grep()

str_detect()

grepl()

str_extract()

regexpr()、regmatches()、grep()

str_match()

regexec()

str_locate()

regexpr()

str_locate_all()

gregexpr()

str_replace()

sub()

str_replace_all()

gsub()

您可以在这些速查表中找到 stringr 函数与正则表达式的完整列表,我们也会在本教程后续部分继续讨论其中一些。

注意:在 stringr 函数中,我们首先传入数据,然后是 regex;而在 base R 函数中——顺序正好相反。

另注:本教程使用的是 stringr 1.5.1,即截至 2026 年的当前版本。如果您使用较旧版本,部分行为可能不同——尤其是 str_view(),它在 1.5.0 中进行了重大更新。

R 正则表达式模式

现在,我们将概览最常见的 R 正则表达式模式及其用法,同时练习一些 stringr 函数。

在此之前,让我们先看一个非常基础的例子。也就是,检查一下 unicorn 是否至少有一个 corn 😉

str_detect('unicorn', 'corn')

输出:

TRUE

在这个例子中,我们使用了 stringr 的 str_detect() 函数来检查字符串 unicorn 中是否包含字符串 corn。

不过,通常我们寻找的并不是文本中的某个字面字符串,而是某种模式——即正则表达式。下面就来深入了解这些模式。

字符转义

在 R 正则表达式中,有一些字符具有特殊含义。更准确地说,它们不像字母和数字那样匹配自身,而是执行不同的功能:

str_extract_all('unicorn', '.')

输出:

1. 'u' 'n' 'i' 'c' 'o' 'r' 'n'

很明显,我们的 unicorn 中并没有点号。然而,str_extract_all() 却提取了该字符串中的每一个字符。这正是 . 的使命——匹配除换行符外的任意单个字符。

如果我们想匹配一个字面的点号怎么办?为此,我们需要在点号前使用一个正则转义字符——反斜杠(\\)。不过这里有个需要注意的坑:反斜杠在字符串本身中也用作转义字符。这意味着我们首先需要“转义转义字符”,即使用双反斜杠。来看一下:

str_extract_all('Eat. Pray. Love.', '\\.')

输出:

1. '.' '.' '.'

因此,反斜杠既可以帮助取消某些符号在 R 正则表达式中的特殊含义,使其按字面解释;也可以起到相反作用:赋予某些本会被按字面解释的字符以特殊含义。下面的表格给出了最常用的字符转义序列:

R regex

匹配内容

\\b

单词边界(\\w 与 \\W 之间的边界)

\\B

非单词边界(\\w-\\w 或 \\W-\\W)

\\n

换行符

\\t

制表符

\\v

垂直制表符

来看一些示例,牢记在这些情况下也需要使用双反斜杠。同时,我们引入另一个 stringr 函数:str_view(),用于以 HTML 形式查看首个或全部匹配:

str_view('Unicorns are so cute!', 's\\\b')
str_view('Unicorns are so cute!', 's\\\B')

输出:

Unicorns are so cute!
Unicorns are so cute!

在字符串 Unicorns are so cute! 中,字母 s 出现了两次。上面第一个 R regex 模式高亮了第一次出现的 s(因为后面跟着空格),而第二个则高亮了第二次出现(因为后面跟着另一个字母,而非单词边界)。

再看两个示例:

cat('Unicorns are\nso cute!')
str_view('Unicorns are\nso cute!', '\\\\n')

输出:

Unicorns are
so cute!

Unicorns are_so cute!

cat('Unicorns are\tso cute!')
str_view('Unicorns are\tso cute!', '\\\\t')

输出:

Unicorns are so    cute!

Unicorns are_so cute!

字符类

字符类用于在一组预定义字符中匹配任意一个字符。内置字符类的语法与上一节看到的字符转义序列相同:一个反斜杠后跟一个字母,使其具有特殊含义而非字面含义。最常用的写法如下:

R regex

匹配内容

\\w

任意“单词字符”(任意字母、数字或下划线)

\\W

任意非“单词字符”

\\d

任意数字

\\D

任意非数字

\\s

任意空白字符(空格、制表符、换行等)

\\S

任意非空白字符

来看几个不言自明的示例:

str_view('Unicorns are so cute!', '\\\\w')
str_view('Unicorns are so cute!', '\\\\W')

输出:

[1] │ <U><n><i><c><o><r><n><s>< ><a><r><e>< ><s><o>< ><c><u><t><e>!
[1] │ Unicorns< >are< >so< >cute<!>
str_view('Unicorns are\nso cute!', '\\\\s')
str_view('Unicorns are\nso cute!', '\\\\S')

输出:

Unicorns are<\n>so cute!
str_detect('Unicorns are so cute!', '\\\\d')

输出:

FALSE

内置字符类也可以出现为另一种形式——[:character_class_name:]。其中一些与带反斜杠的等价,另一些则没有。最常见的如下:

R regex

匹配内容

[:alpha:]

任意字母

[:lower:]

任意小写字母

[:upper:]

任意大写字母

[:digit:]

任意数字(等价于 \\d)

[:alnum:]

任意字母或数字

[:xdigit:]

任意十六进制数字

[:punct:]

任意标点符号

[:graph:]

任意字母、数字或标点符号

[:space:]

空格、制表符、换行等(等价于 \\s)

我们来做些示例,注意需要将上述任意 R regex 模式放入方括号中:

str_view('Unicorns are so cute!', '[[:upper:]]')
str_view('Unicorns are so cute!', '[[:lower:]]')

输出:

Unicorns are so cute!

Unicorns are so cute!

str_detect('Unicorns are so cute!', '[[:digit:]]')

输出:

FALSE
str_extract_all('Unicorns are so cute!', '[[:punct:]]')

输出:

1. '!'
str_view('Unicorns are so cute!', '[[:space:]]')

输出:

Unicorns_are_so_cute!

我们也可以自定义字符类:在方括号中放入希望匹配的任意字符集合,以匹配其中任一个字符。可以在方括号内写入某个字母或数字范围(按 Unicode 顺序)、多个不同范围,或任意连续/不连续的字符或字符组。

例如,[A-D] 匹配 A 到 D(含)之间的任意大写字母,[k-r] 匹配 k 到 r(含)之间的任意小写字母,[0-7] 匹配 0 到 7(含)之间的任意数字,[aou14%9] 则匹配方括号内给定的任一字符。如果将脱字符(^)放在方括号内首位,则该 R regex 模式将匹配除给定字符之外的任意字符。注意上述匹配机制区分大小写。

str_view('Unicorns Are SOOO Cute!', '[O-V]')
str_view('Unicorns Are SOOO Cute!', '[^O-V]')

输出:

Unicorns Are SOOO Cute!

Unicorns Are SOOO Cute!

str_view('3.14159265359', '[0-2]')

输出:

3.14159265359

str_view('The number pi is equal to 3.14159265359', '[n2e9&]')

输出:

The number pi is equal to 3.14159265359

量词

我们经常需要重复匹配某个 R regex 模式,而不是只匹配一次。为此可使用量词。量词总是位于其所作用的 regex 模式之后。最常见的量词如下表:

R regex

重复次数

*

0 次或更多

+

至少 1 次

?

至多 1 次

{n}

恰好 n 次

{n,}

至少 n 次

{n,m}

至少 n 次且至多 m 次

我们逐一尝试:

str_extract('dog', 'dog\\\\d*')

输出:

'dog'

我们得到了原字符串 dog:该字符串末尾没有数字,但这没关系(允许 0 次或更多次数字)。

str_extract('12345', '\\\\d+')

输出:

'12345'
str_extract('12345', '\\\\d?')

输出:

'1'
str_extract('12345', '\\\\d{3}')

输出:

'123'
str_extract('12345', '\\\\d{7,}')

输出:

NA

得到 NA,因为字符串中没有至少 7 个连续数字,只有 5 个。

str_extract('12345', '\\\\d{2,4}')

输出:

'1234'

锚点

默认情况下,R 正则表达式会匹配给定字符串中的任意部分。我们可以通过指定 R regex 模式在字符串中的特定位置来改变这一行为。最常见的是从字符串的开头或末尾进行限定。为此使用 R 正则表达式中的两个主要锚点:

  • ^ —— 从字符串开头匹配(多行字符串则为每行的开头)

  • $ —— 从字符串末尾匹配(多行字符串则为每行的末尾)

我们用回文 stella won no wallets 来看看它们的工作方式:

str_view('stella won no wallets', '^s')
str_view('stella won no wallets', 's$')

输出:

<s>tella won no wallets
stella won no wallet<s>

如果我们想匹配字符 ^ 或 $ 本身,需要在其前面添加反斜杠(需双重转义):

str_view('Do not have 100$, have 100 friends', '\\\\$')

输出:

Do not have 100$, have 100 friends

我们也可以将匹配锚定到字符串中的单词边界或非单词边界(分别为 \\b 和 \\B):

str_view('road cocoa oasis oak boa coach', '\\\\boa')
str_view('road cocoa oasis oak boa coach', 'oa\\\\b')
str_view('road cocoa oasis oak boa coach', 'oa\\\\B')

输出:

road cocoa oasis oak boa coach

road cocoa oasis oak boa coach

road cocoa oasis oak boa coach

上面我们匹配了字母组合 oa:

  • 第 1 个示例——在单词开头
  • 第 2 个示例——在单词结尾
  • 第 3 个示例——当其后跟随一个单词字符时(此处为字母)

或运算(交替)

通过交替运算符(|),我们可以在同一字符串中匹配多个 R regex 模式。注意,如果在用户自定义的字符类中使用该运算符,它会被按字面解释,因此不会执行交替。

str_view('coach koala board oak cocoa road boa load coat oasis boat', 'boa|coa')

输出:

coach koala board oak cocoa road boa load coat oasis boat

在上述示例中,我们匹配了所有 boa 或 coa 的出现。

分组

R 的 regex 模式遵循一定的优先级规则。例如,重复(量词)优先级高于锚点,而锚点又高于交替。若要覆盖这些规则并提升某个操作的优先级,应使用分组。方法是用圆括号将感兴趣的子表达式括起来。

分组通常与交替运算符配合效果最佳。下面的示例清晰展示了这种组合的效果:

str_view('code rat coat cot cat', 'co|at')
str_view('code rat coat cot cat', 'c(o|a)t')

输出:

code rat coat cot cat

code rat coat cot cat

R 正则表达式的进阶应用

到目前为止的内容,已经为您开始使用 R 正则表达式打下了良好基础。不过,这个强大工具还能做更多事。在不展开细节的前提下,我们提及一些可以用 R regex 实现的进阶操作:

  • 覆盖 stringr 函数的默认行为
  • 匹配字素簇(grapheme clusters)
  • 分组回溯引用
  • 匹配 Unicode 属性
  • 应用更高级的字符转义
  • 在不将模式包含进输出的前提下验证其存在(即所谓 lookaround)
  • 将重复机制由贪婪变为惰性
  • 使用原子分组

在我们的课程 Intermediate Regular Expressions in R 中了解更多。

R 正则表达式练习

现在轮到您来练习本教程中介绍的 R regex 模式了。请使用我们的数据集:互联网新闻与消费者参与,尝试完成以下任务:从所有 URL 中提取顶级域名(TLD)。TLD 的示例包括 com、net、uk 等。

完成该任务的方法不止一种,其中包括优雅而简洁的一行解法(提示:您可以进一步了解上一节提到的 R regex 中的 lookaround,并用它来解决该问题)。以下是对相对“新手”思路的一些松散指引:

  • 检查数据集中的若干 URL,注意在 TLD 之前 哪些模式总是出现,哪些是可选的
  • 观察在 TLD 之后 的 URL 中是否有任何必需模式(若有,是哪些),以及哪些是可选的
  • 去除 TLD 之前的所有内容
  • 去除 TLD 之后的所有内容

若按上述算法求解,您无需额外学习或操作。只需回顾本教程讨论的内容并加以实践。尤其需要用到 stringr 函数、字符转义、字符类、量词与分组。

加分题:挑战 #2

如果您还想进一步练习,这次不提供任何提示:仍然使用上一题的数据集,从所有 URL 中提取域名。例如,在 URL www.google.com 中,域名是 google。

结语

总而言之,在本教程中,我们系统学习了 R 正则表达式的诸多内容。具体包括:

  • R 正则表达式是什么
  • 它们的重要性及应用场景
  • 用于处理 R 正则表达式的函数(包含原生 R 与专业库)
  • 最常见的 R regex 模式及其适用范围、细节与注意点
  • 何时以及如何在 R regex 中使用字符转义、字符类、量词、锚点、或运算与分组
  • R regex 与相关函数的多种应用示例
  • R 正则表达式的一些进阶操作

这些技能与概念不仅有助于您在 R 中开展工作,在许多其他编程语言中同样适用,因为正则表达式的概念在各语言中一致,而多数语言的 regex 语法也相当相似。

如果您希望进一步精通 R,建议探索 DataCamp 的以下课程、技能路线与文章:

Elena Kosourova

作者

Elena Kosourova

IBM 认证数据科学家(2020)。此前曾在全球油气田担任石油地质学家/地质建模师,拥有 12 年以上国际工作经验。精通 Python、R 和 SQL。专长领域包括:数据清洗、数据处理、数据可视化、数据分析、数据建模、统计、数据叙事、机器学习。具备丰富的数据科学社区管理经验,并长期撰写与审校数据科学及职业发展相关的文章与教程。

FAQs

如何查看我安装的 stringr 版本,并进行更新?

在 R 控制台中运行:

packageVersion('stringr')

如果版本号低于 1.5.0,请更新:

install.packages('stringr')
 
str_view_all() 会被彻底移除吗?现在继续使用是否安全?

在当前版本的 stringr 中它仍然可用,并将在一段时间内继续可用——R 中的弃用并不意味着立即移除。不过,每次调用都会产生警告信息,噪声会很大。更重要的是,被弃用的函数最终会在未来的大版本中移除,因此任何依赖 str_view_all() 的代码最终会失效。修复只需约 30 秒,现在处理比以后更省心。

为什么在 R 的正则里需要用两个反斜杠(\\),而其他语言只用一个?

答:因为在传给正则引擎之前,R 会先处理字符串。单个 \ 会被 R 的字符串解析器消费掉,所以您需要使用 \\ 来确保有一个 \ 真正传递到正则引擎。写 \\d,R 会把它转换成传给正则的 \d;如果写 \d,在正则看到它之前解析就已经出错了。

base R 中 grep() 与 grepl() 有何区别,应如何选择?

grep() 返回匹配元素的索引;grepl() 返回与输入等长的 TRUE/FALSE 向量。需要位置时用 grep(),要过滤或测试时用 grepl()。实践中,stringr 的 str_subset() 与 str_detect() 在大多数用例下更清晰。

何时应使用 [aeiou] 这样的字符类,而不是 a|e|i|o|u 这样的交替?

对于单个字符,请始终使用字符类——更快也更清晰。当某些选项包含多个字符时再用交替 |,如 cat|dog,因为多字符选项无法放入 [] 中。

量词 *, + 和 ? 有何区别?

* = 零次或多次(模式可选且可重复)。+ = 一次或多次(至少需要一次)。? = 零次或一次(可选,不会重复)。记忆小窍门:+ 表示“加”(至少来一个),? 表示“问”(也许有也许没有),* 表示“都行”。

sub() 与 gsub() 有何区别?stringr 中的对应函数是什么?

sub() 只替换首个匹配;gsub() 会替换所有匹配。它们在 stringr 中的等价函数是 str_replace() 与 str_replace_all()。推荐使用 stringr 版本,因为它们遵循 tidyverse 的参数顺序——数据在前、模式第二、替换项第三。

在 base R 中,何时使用 regexpr(),何时使用 gregexpr()?

regexpr() 查找每个字符串中首个匹配的位置;gregexpr() 则查找所有匹配。多数 R 用户更喜欢 stringr 的 str_locate() 与 str_locate_all()——它们返回带有 start 与 end 列的整洁数据框,而不是 base R 那种不太友好的命名整数格式。

方括号内外的 ^ 分别代表什么?

在 [] 外,它将模式锚定到字符串开头:^Hello 匹配以 "Hello" 开头的字符串。在 [] 内且作为首字符时,它会对集合取反:[^aeiou] 匹配任何非元音的字符。在 [] 的其他位置时,作为字面量处理。

何时应使用 \b 单词边界,而不是 ^ 和 $ 锚点?

当您想在字符串任意位置匹配整个单词而非仅仅开头或结尾时,用 \b。直接搜索 cat 也会匹配到“concatenated”——而 \bcat\b 只会匹配独立成词的“cat”。在文本分析中尤为有用,因为部分匹配可能会干扰结果。

主题