暗色模式

Linux 正则表达式:从基础元字符到 grep/sed/awk 应用

技术教程
2026-08-24
8
0
本文要点
  • 正则表达式(Regular Expression)是一套用元字符描述文本模式的语法,贯穿 grepsedawk 三大文本处理工具。
  • 字符类 [abc]、量词 * + ? {n}、锚点 ^ $、分组 () 与转义 \. 是必须掌握的核心元字符。
  • grep 默认用基本正则(BRE),加 -E 切换到扩展正则(ERE),+ ? | () 不再需要转义。
  • grep -o 只打印实际匹配的片段,grep -c 统计匹配行数,用于从日志中精确抽取 IP、端口等字段。
  • awk 既可按正则筛选整行(/pattern/{...}),也能按正则匹配单个字段($n ~ /re/)。

什么是正则表达式

正则表达式(Regular Expression,简称 regex)是一套用来描述文本模式的迷你语言。它把"含有数字的 IP"、"以 ERROR 开头的行"这类模糊需求,翻译成机器可执行的模式串。Linux 命令行中三个最常用的文本工具——grepsedawk——都以正则表达式为匹配核心,因此掌握正则就是掌握三剑客的共同基础。

本文以一份模拟的服务器日志为例,在 Ubuntu 24.04 上真实执行每条命令,带你系统过一遍元字符、定位与分组,以及它们在 grepsedawk 中的实际应用。

准备演示数据

先在服务器上创建一份 7 行的日志文件 /root/net.log,模拟包含不同级别(INFO/WARN/ERROR)、IP、端口与服务的应用日志:

cat > /root/net.log <<'LOG'
2026-08-20 10:11:22 ERROR Failed to connect to db.srv:5432
2026-08-20 10:12:03 INFO  User astarry logged in from 192.168.1.10
2026-08-20 10:12:45 WARN  Slow query 1.2s on orders table
2026-08-20 10:15:18 ERROR Timeout connecting to cache.srv:6379
2026-08-20 10:16:02 INFO  User root ran sudo apt update
2026-08-20 10:20:33 ERROR Connection refused on 10.0.0.5:8080
2026-08-20 10:22:11 INFO  Backup completed in 45s
LOG

观察文件结构:每行由时间戳(日期 + 时分秒)级别消息三部分组成,其中 INFO 是普通信息、WARN 是警告、ERROR 是错误。后面所有演示都基于这份数据。

核心元字符

正则表达式的核心是十几个元字符,它们各自代表一类匹配规则。先记住最常用的几组。

字符类 [...]

方括号表示"匹配括号内任意一个字符"。例如:

  • [0-9] 匹配任意一个数字,等价于 [0123456789]
  • [a-z] 匹配任意一个小写字母
  • [A-Za-z] 匹配任意一个字母(大小写均可)
  • [^0-9] 中的 ^ 在方括号内表示"取反",匹配任意一个数字字符

量词 * + ? {n}

量词修饰它前面的字符或分组,表示"出现多少次":

量词含义示例
*前一项出现 0 次或多次a* 匹配空、aaaa
+前一项出现 1 次或多次a+ 匹配 aaaa,不匹配空
?前一项出现 0 次或 1 次colou?r 同时匹配 colorcolour
{n}前一项恰好 n 次[0-9]{4} 匹配 4 位数字
{n,}前一项至少 n 次[0-9]{2,} 匹配 2 位及以上数字
{n,m}前一项 n 到 m 次[0-9]{2,4} 匹配 2 到 4 位数字

锚点 ^$

^ 匹配行首$ 匹配行尾(在非多行模式下分别匹配字符串开头与结尾):

  • ^ERROR 匹配 ERROR 开头的行
  • ERROR$ 匹配 ERROR 结尾的行
  • ^$ 匹配空行

分组 () 与分支 |

  • (pattern) 把一部分模式括成分组,可整体被量词修饰、被捕获引用、或用于替换
  • A|B 表示,匹配 AB

转义 \

. 在正则里是一个元字符,代表"任意一个字符"。要匹配字面上的点号(如 IP 中的小数点 192.168.1.10),必须写成 \.。类似地,( ) { } [ ] * + ? ^ $ | 这些元字符要匹配字面值时,都需用 \ 转义。

grep:最常用的匹配工具

grep 按正则逐行扫描文件,把匹配的行打印出来。

普通匹配与行号

最简单的用法是把普通字符串当模式,匹配包含该字符串的行,-n 显示行号:

grep -n "ERROR" /root/net.log
1:2026-08-20 10:11:22 ERROR Failed to connect to db.srv:5432
4:2026-08-20 10:15:18 ERROR Timeout connecting to cache.srv:6379
6:2026-08-20 10:20:33 ERROR Connection refused on 10.0.0.5:8080

基本正则与扩展正则:-E 开关

grep 默认使用基本正则(BRE),此时 + ? | ( ) 这些元字符需要加 \ 才有特殊含义(如 a\+)。加上 -E 切换到扩展正则(ERE),这些字符直接生效,更直观。日常使用几乎都用 -E。请看下面对比——扩展正则下 | 直接表示"或":

多分支匹配 ERROR 或 WARN(| 分支 + 行号)

grep -nE "ERROR|WARN" /root/net.log
1:2026-08-20 10:11:22 ERROR Failed to connect to db.srv:5432
3:2026-08-20 10:12:45 WARN  Slow query 1.2s on orders table
4:2026-08-20 10:15:18 ERROR Timeout connecting to cache.srv:6379
6:2026-08-20 10:20:33 ERROR Connection refused on 10.0.0.5:8080

统计与抽取:-c-o

  • grep -c "模式" 文件 统计匹配的行数,而不是匹配次数:
grep -c "ERROR" /root/net.log
3
  • grep -o "模式" 文件 只打印实际匹配到的片段(而不是整行)。这对从日志里抽取 IP、端口等字段非常有用。用一个字符类 + 量词 + 转义点的正则来抽取全部 IP 地址:

抽取日志中的 IP 地址(字符类 + 量词 + 转义点)

grep -oE "[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+" /root/net.log
192.168.1.10
10.0.0.5

这里 [0-9]+ 匹配 1 到多位数字,\. 匹配字面点号,四组数字用三个点连接就是 IPv4 地址的模式。文件中只有第 2、6 行出现了完整 IPv4,因此抽取到两条。

同样的思路可以抽取端口号。端口是冒号后接 4 位数字,用 :[0-9]{4}

grep -oE ":[0-9]{4}" /root/net.log
:5432
:6379
:8080

以及连接的服务名(db.srvcache.srv):

grep -oE "[a-z]+\.srv" /root/net.log
db.srv
cache.srv

单词边界 \b

\b 匹配单词边界,用于精确匹配而不是前缀匹配。例如要匹配独立的单词 connect,而不是 connecting

grep -nE "\bconnect\b" /root/net.log
1:2026-08-20 10:11:22 ERROR Failed to connect to db.srv:5432

第 4 行虽然也有 connecting,但因为它不是独立的 connect 单词,未被匹配到。

锚点的一个易错点

^ERROR 只匹配 ERROR 位于行首的行。在本日志里,级别字段前面还有时间戳,所以 ^ERROR 不会有任何输出:

grep -n "^ERROR" /root/net.log

(无输出)这说明:模式的锚点必须与文本实际位置吻合,否则匹配不上。

sed:按正则做流式替换与筛选

sed 是流编辑器,能逐行读入、按正则替换删除/保留内容。

替换:s/模式/替换/

s/模式/替换/ 替换匹配部分。例如删掉每行开头的时间戳(日期 时分秒 + 空格,即 ^[0-9-]* [0-9:]* ):

sed -n "s/^[0-9-]* [0-9:]* //p" /root/net.log
ERROR Failed to connect to db.srv:5432
INFO  User astarry logged in from 192.168.1.10
WARN  Slow query 1.2s on orders table
ERROR Timeout connecting to cache.srv:6379
INFO  User root ran sudo apt update
ERROR Connection refused on 10.0.0.5:8080
INFO  Backup completed in 45s

这里的 ^[0-9-]* 从行首匹配数字和连字符(日期),[0-9:]* 继续匹配 10:11:22(时分秒),整体被替换为空。-n 关闭默认打印,p 只在发生替换时打印该行。

sed 替换中还支持分组引用。正则里用 \( \)(基本正则)或 ( )(扩展正则配合 -E)分组,替换串里用 \1\2 引用。例如只保留"日期 时间"两段:

sed -nE "s/^([0-9-]+) ([0-9:]+).*/\1 \2/p" /root/net.log
2026-08-20 10:11:22
2026-08-20 10:12:03
2026-08-20 10:12:45
2026-08-20 10:15:18
2026-08-20 10:16:02
2026-08-20 10:20:33
2026-08-20 10:22:11

([0-9-]+) 是第一组(日期)、([0-9:]+) 是第二组(时间),.* 吃掉剩余部分,替换为 \1 \2 就只剩日期和时间。

按正则删除:/模式/d

/模式/d 表示删除匹配该正则的行。例如删除所有 ERROR 行,只留下 INFO/WARN

sed -n "/ERROR/d; p" /root/net.log
2026-08-20 10:12:03 INFO  User astarry logged in from 192.168.1.10
2026-08-20 10:12:45 WARN  Slow query 1.2s on orders table
2026-08-20 10:16:02 INFO  User root ran sudo apt update
2026-08-20 10:22:11 INFO  Backup completed in 45s

awk:正则筛选 + 字段处理

awk处理文本,默认用空白分隔字段,$1$2……分别代表第 1、2……列,$0 代表整行,$NF 代表最后一列。awk 把正则用得最灵活——既能筛选整行,也能匹配单个字段。

按整行正则筛选

/正则/ {动作} 表示"匹配该正则的行执行动作"。日志里第 3 列是级别,筛出 ERRORWARN 的行,并打印第 2 列(时间)和第 3 列(级别):

awk 正则筛选并格式化输出

awk '/ERROR|WARN/ {print $2, $3}' /root/net.log
10:11:22 ERROR
10:12:45 WARN
10:15:18 ERROR
10:20:33 ERROR

正则表达式用在两个斜杠 / 之间,写法与 grep 一致。

按字段正则匹配:$n ~ /re/

awk 的一个强大之处是可以只对某一列做正则匹配。$n ~ /正则/ 表示"第 n 列匹配该正则"。例如找出 User 后面是 root 的行($4User$5 是用户名),打印日期、时间、用户:

awk '$4 ~ /User/ && $5=="root" {print $1, $2, $4, $5}' /root/net.log
2026-08-20 10:16:02 User root

~ 是"匹配"运算符,!~ 表示"不匹配"。字段值判断(==)配合正则(~),让 awk 能精确处理结构化文本。

正则统计思路

用 awk 结合正则与排序,可以快速统计各级别日志的条数:

awk '{print $3}' /root/net.log | sort | uniq -c
   3 ERROR
   3 INFO
   1 WARN

awk 抽出第 3 列级别,sort 排序,再用 uniq -c 计数,一条管道就得出 ERROR 3 条、INFO 3 条、WARN 1 条——这与日志里三类级别的实际行数完全吻合。这正是日志分析的常见套路。

BRE 与 ERE:理解 cronsed 等处的差异

最后澄清一个关键概念:正则有两种方言。

  • 基本正则(BRE)grep(不带 -E)、sed(不带 -E)、grep 脚本、老的 vi 默认使用。在 BRE 中,+?|(){} 只是普通字符,要当元字符必须写成 \+\?\|\(\)\{\}
  • 扩展正则(ERE)grep -Eegrepsed -Eawk 使用。+?|(){} 直接是元字符,不需要转义。

awk 一出生就用 ERE,所以前面的 awk 例子直接写了 ERROR|WARN[0-9]+ 而没有转义。而 sedgrep 默认是 BRE,需要 -E 才能用同样的写法(分组的转义写法也因此在两者间不同)。搞清这个差异,很多"同样的正则为什么在不同工具里表现不同"的困惑就解开了。

常用正则速查表

元字符含义示例
.任意一个字符a.c 匹配 abca1c
[...]括号内任意一个字符[0-9] 数字、[a-z] 小写
[^...]除括号内外的任意字符[^0-9] 非数字
*前项 0 次或多次ab*c 匹配 acabbc
+前项 1 次或多次(ERE)ab+c 匹配 abcabbc
?前项 0 次或 1 次(ERE)colou?r 匹配 color/colour
{n,m}前项 n~m 次[0-9]{2,4}
^行首(方括号内取反)^INFO
$行尾INFO$
\b单词边界\bconnect\b
()分组(可供替换引用)(ab)+
``或(ERE)`ERRORWARN`(ERE)
\. \(转义元字符的字面值\.srv 匹配 .srv

小结

正则表达式是 Linux 文本处理的地基。记住四组核心元字符——字符类、量词、锚点、分组与分支,再用 grep -o/-csed s///awk 字段匹配这几个高频组合去练习,就能覆盖绝大多数日志分析与配置处理场景。三剑客虽然命令不同,但正则是它们共用的"语言",会了正则,grep/sed/awk 的多数功能都可触类旁通。

如果你想进一步深入,推荐阅读 Regular-Expressions.info 的教程(英文权威参考)或系统自带的 man grepman sedman awk 手册页(直接 man grep 在终端查看全部基础正则与扩展正则语法说明)。

发表评论

暂无评论,快来抢沙发吧!