Regex 字符”$”并不一定表示 “字符串结尾”
本文讲述的是我最近在为 CPython 开发 SBOM 工具时,使用 Python 的 regex 模块 (re
) 发现的一些令人惊讶的行为。
以前使用过正则表达式的人可能知道 ^
的意思是 “字符串的起始端”,相应地,$
的意思是 “字符串的结束端”。因此,模式 cat$
可以匹配字符串 “lolcat”,但不能匹配 “internet cat video”。
^
的行为让我觉得 $
也与之类似,但它们并不总是对称的,其行为也与平台有关。特别是对于禁用了多行模式的 Python,$
字符既可以匹配字符串的结尾,也可以匹配字符串结尾前的换行符。
因此,如果要匹配一个结尾没有换行的字符串,在 Python 中就不能只使用 $!
我本以为禁用多行模式就不会出现这种换行匹配行为,但事实并非如此。
下一个合乎逻辑的问题是,在 Python 中如何匹配没有换行的字符串结尾?
在对 Python 和其他正则表达式语法做了更多研究后,我还发现 \z
和 \Z
可以作为 “字符串结尾 “字符。
在 Python 中使用 re.MULTILINE
可以启用多行模式,文档中有如下说明:
指定
re.MULTILINE
时,模式字符”$
“会在字符串末尾和每行末尾(紧接着每个换行符之前)匹配。默认情况下,”$
“只与字符串末尾和字符串末尾换行符(如果有)前的字符匹配。
让我们看看这些功能如何在多个平台上协同工作:
Pattern matches "cat\n" ? |
"cat$" multiline |
"cat$" no multiline |
"cat\z" |
"cat\Z" |
---|---|---|---|---|
PHP | ✅ | ✅ | ❌ | ✅ |
ECMAScript | ✅ | ❌ | ⚠️ | ⚠️ |
Python | ✅ | ✅ | ⚠️ | ❌ |
Golang | ✅ | ❌ | ❌ | ⚠️ |
Java 8 | ✅ | ✅ | ❌ | ✅ |
.NET 7.0 | ✅ | ✅ | ❌ | ✅ |
Rust | ✅ | ❌ | ❌ | ⚠️ |
- ✅: 模式匹配字符串
"cat\n"
- ❌: 模式不匹配字符串
"cat\n"
- ⚠️: 模式无效或非法字符
综合上表,如果匹配尾部换行是可以接受的,那么使用多行模式的 $
就可以在所有平台上一致运行,但如果我们不想匹配尾部换行,事情就变得复杂了。
要不匹配尾部换行,可以在所有平台上使用 \z
,但 Python 和 ECMAScript 除外,在这两种平台上分别需要使用 \Z
或不带多行模式的 $
。希望你今天学到了一些关于正则表达式的知识!
注:数据表来自 regex101.com,我没有使用实际运行时进行测试。
本文文字及图片出自 Regex character “$” doesn't mean “end-of-string”