迟早每个命令行会话都会遇到纯文本搜索无法解决的匹配问题:找出以数字开头的行、从日志中提取每一个邮箱地址,或者把一种格式的日期替换成另一种。正则表达式(regex)就是解决这些问题的模式语言,而且同一套核心语法在 grep、sed、awk、文本编辑器以及大多数编程语言中都能通用。
本指南将讲解正则表达式的基本构件——锚点、字符类、量词、分组与选择(alternation),并演示同一个模式如何分别从 grep 延续到 sed 与 awk。
什么是正则表达式
正则表达式是一种描述字符串集合的模式。它并非只匹配某个固定的单词,例如模式 ^error [0-9]+ 能匹配任意“以 error 开头、后接一个空格和一个或多个数字”的行。
本指南中的示例都使用 GNU grep -E(扩展正则表达式),因此所有模式都可原样生效。你可以通过把文本管道传给 grep 来测试其中任意一个:
echo "error 404" | grep -E '^error [0-9]+'
error 404
该行之所以被打印,是因为它与模式匹配。不匹配时,grep 不输出任何内容并返回一个非零的退出状态。务必用单引号包裹模式,以免 shell 在 grep 看到这些字符之前先解释 $ 和 * 等符号。
字面字符与元字符
正则表达式中,大多数字符匹配自身:模式 cat 会匹配一行中任意位置的字符串 “cat”,包括 “concatenate” 内部。但有一小部分字符具有特殊含义,而非按字面匹配:
. ^ $ * + ? ( ) [ ] { } | \
这些就是元字符,本指南剩余部分将介绍它们各自的作用。若想按字面匹配其中某个字符,用反斜杠转义即可:\. 匹配真正的点号,\$ 匹配真正的美元符号。
锚点与单词边界
锚点与单词边界并不匹配字符,而是匹配行中的某个位置。
- ^:匹配行首。
- $:匹配行尾。
- \b:匹配单词边界,即单词字符与非单词字符之间的位置。
\b 边界是 GNU grep 的扩展,并不属于 POSIX 扩展正则表达式语法的一部分。
这种差异在真实输入下最容易看清。下面这段输入包含三行相似的内容,但模式只打印出恰好由单词 “root” 构成的那个行:
printf 'root\nroot:x:0:0\nchroot\n' | grep -E '^root$'
root
不加锚点时,模式 root 还会匹配 “chroot”,或匹配 “root” 出现在中间的任意行。单词边界能解决子串问题,又不必把匹配限定在整个行上:
echo "the cat scattered" | grep -E -o '\bcat\b'
cat
-o 标志只打印匹配到的文本。注意 “scattered” 并未产生匹配,因为它内部的 “cat” 并没有被单词边界包围。
字符类:匹配字符集合
方括号用于匹配字符集合中的一个字符:
- [abc]:匹配 a、b 或 c 中的任意一个字符。
- [a-z]:匹配一个小写字母;范围也适用于 [0-9] 与 [A-Z]。
- [^abc]:取反,匹配不是 a、b 或 c 的任意一个字符。
例如,要用一个模式同时匹配 “gray” 和 “grey”:
printf 'gray\ngrey\ngroy\n' | grep -E 'gr[ae]y'
gray grey
第三行不匹配,因为其中的 “o” 不在集合内。在方括号内部,大多数元字符会失去特殊含义;[.] 匹配的就是字面点号。
POSIX 字符类是在方括号内可用的具名简写:[[:digit:]] 等价于 [0-9],[[:alpha:]] 匹配字母,[[:space:]] 匹配空白符。许多工具在支持 PCRE(grep -P)时也支持 Perl 风格简写 \d,但方括号形式是 shell 操作中最具可移植性的选择。
点号与量词:匹配重复
点号 . 匹配除换行符外的任意单个字符。量词作用于它前面的元素,控制该元素可以重复的次数:
- *:零次或多次。
- +:一次或多次。
- ?:零次或一次(可选)。
- {n}:恰好 n 次。
- {n,m}:n 到 m 次之间。
将点号与量词结合得到 .*,它可以匹配任意内容(包括空内容)。下面这个更精确的示例要求每组为一到三位数字,从而匹配类似 IPv4 的地址:
echo "server at 192.168.1.10 is up" | grep -E -o '[0-9]{1,3}(\.[0-9]{1,3}){3}'
192.168.1.10
逐段解读:[0-9]{1,3} 匹配第一组数字,\. 匹配字面点号,而带 {3} 的括号把“点号加数字”这个序列重复三次。
初学者常犯的错误是本来想用 + 却用了 *。模式 [0-9]* 会心安理得地匹配空字符串,因此它对每一行都能匹配成功;而 [0-9]+ 才真正要求至少有一个数字。
分组与选择(或)
圆括号用于对模式的某部分进行分组,而竖线 | 表示选择(或):
printf 'error: disk full\nwarning: low memory\ninfo: started\n' | grep -E '^(error|warning):'
error: disk full warning: low memory
分组把“选择”限定在冒号前的两个单词上;若没有圆括号,^error|warning: 的含义会变成“以 error 开头,或在任意位置包含 warning:”,这通常不是你想要的。
分组还会捕获所匹配的内容,捕获到的文本可以重复使用。在 sed 中,\1 指代第一个分组,从而能够对文本重新排序:
echo "2026-01-15" | sed -E 's/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3.\2.\1/'
15.01.2026
三个分组分别捕获年、月、日,替换部分则将它们按逆序写回。
基础正则与扩展正则
POSIX 定义了两种正则方言,而它们的差异几乎会让每个人都踩坑。基础正则表达式(BRE)是普通 grep 与 sed 所用的方言,其中 +、?、|、{} 与 () 都按字面匹配,必须用反斜杠转义(\+、\( … \))才能获得特殊行为。而在扩展正则表达式(ERE)中,它们默认就是特殊字符。
| 构造 | ERE(grep -E、sed -E、awk) | BRE(普通 grep、sed) |
|---|---|---|
| 一次或多次 | + | \+ |
| 可选 | ? | \? |
| 重复 | {n,m} | \{n,m\} |
| 分组 | (...) | \(...\) |
| 选择 | a|b | a|b(仅 GNU 扩展) |
最后一行最值得注意。选择(alternation)根本不属于 POSIX BRE,因此 \| 在 GNU grep 与 GNU sed 中可用,但在 macOS 自带的 BSD sed 上会静默失败。
实际操作中,最简单的规则就是:给 grep 和 sed 都加上 -E,并采用扩展方言来书写,本指南的所有示例都是如此。awk 原生就使用扩展语法:
printf 'alice 92\nbob 47\ncarol 78\n' | awk '/^[ab]/ {print $1}'
alice bob
这段 awk 模式选中以 “a” 或 “b” 开头的行并打印第一个字段。grep -P 启用第三种方言——Perl 兼容正则表达式(PCRE),它新增了 \d 与环视(lookaround)等特性;当 POSIX 方言不够用时就可以用它。
同一模式,三种工具
语法是共通的,但每种工具的包装方式不同,这往往就是从一条命令把能用的模式搬到另一条命令时让人困惑的地方。先创建一个小日志文件方便跟着操作:
printf 'error: disk full\nwarning: low memory\ninfo: started\n' > app.log
因为 grep 是一个过滤器,模式本身就是全部工作,无需任何外围包裹:
grep -E '^(error|warning):' app.log
sed 默认会打印每一行输入,除非你用 -n 把它抑制掉,因此选择行需要一个地址,后接一个显式的 p 命令:
sed -E -n '/^(error|warning):/p' app.log
awk 把不带动作块的裸模式理解为“打印匹配的行”:
awk '/^(error|warning):/' app.log
error: disk full warning: low memory
三种工具都会打印相同的两行并跳过 “info” 行。它们的分野在于匹配之后发生什么:grep 报告匹配的行后便止步,sed 可以重写匹配到的文本,而 awk 会把每个匹配行拆分成可供处理的字段:
awk -F ': ' '/^(error|warning):/ {print $2}' app.log
disk full low memory
将字段分隔符设为 ': ' 后,$2 就成了消息正文,于是模式负责选中行、动作块负责提取你真正想要的部分。这正是把正则作为一门独立主题来学习的价值所在:你今天为 grep 写的模式,明天会原样粘贴进 sed、awk、文本编辑器或 Python 脚本中。
快速参考
| 模式 | 匹配 |
|---|---|
| . | 任意单个字符 |
| ^ / $ | 行首 / 行尾 |
| \b | GNU grep 中的单词边界 |
| [abc] / [^abc] | 集合中的一个字符 / 不在集合中的字符 |
| [0-9]、[[:digit:]] | 一个数字 |
| * / + / ? | 零次或多次 / 一次或多次 / 可选 |
| {n,m} | n 到 m 次重复 |
| (foo|bar) | foo 或 bar |
| \1 | 第一个分组捕获的文本 |
| \. | 字面点号(转义的元字符) |
总结
锚点、字符类、量词与分组组合在一起,就能构成应对 Linux 日常大部分匹配需求的模式,而且这套词汇也能沿用至编辑器与编程语言。若要在具体工具中实践这些语法,请参阅我们关于 grep 中的正则表达式以及使用 sed 查找替换的指南。