Linux 文本处理三剑客进阶实战:grep、sed、awk 从入门到精通
为什么你需要精通这三把利器
在 Linux 世界里,有一组被无数运维和开发人员奉为经典的文本处理工具——grep 搜索、sed 替换、awk 编程。它们就像瑞士军刀的三个刀片,各自擅长的领域互补而不重叠:
| 工具 | 核心定位 | 最佳场景 | 一句话理解 |
|---|---|---|---|
| grep | 文本搜索与过滤 | 查找、过滤、统计匹配行 | "我帮你找" |
| sed | 流编辑器 | 批量替换、行级编辑、文本变换 | "我帮你改" |
| awk | 模式驱动编程语言 | 字段提取、报表生成、结构化数据处理 | "我帮你算" |
很多开发者只会基础用法(grep xxx file、sed 's/old/new/'、awk '{print $1}'),遇到稍复杂的需求就写 Python 脚本——其实 90% 的日常文本处理任务,这三把利器组合起来就够了,而且比 Python 脚本快 10 倍以上。本文从正则表达式精通到实战案例,帮你彻底掌握它们。
一、grep:文本搜索的瑞士军刀
1.1 基础与常用选项速查
grep 的核心功能是在文本流中搜索匹配模式的行。以下是日常高频使用的选项:
# 基础搜索
grep "error" app.log # 在日志中查找包含 error 的行
# 常用选项组合
grep -i "error" app.log # 忽略大小写
grep -n "error" app.log # 显示行号
grep -c "error" app.log # 只输出匹配行数(计数)
grep -v "debug" app.log # 反向过滤:排除包含 debug 的行
grep -r "TODO" src/ # 递归搜索目录
grep -l "password" *.conf # 只输出包含匹配的文件名
grep -w "fail" app.log # 全词匹配(fail 不匹配 failure)
grep -A3 "exception" app.log # 匹配行 + 后 3 行(After)
grep -B2 "exception" app.log # 匹配行 + 前 2 行(Before)
grep -C2 "exception" app.log # 匹配行 + 前/后各 2 行(Context)
grep -E "err|fail|warn" app.log # 扩展正则(相当于 egrep)
grep -P "\d{4}-\d{2}-\d{2}" log # Perl 正则(支持 \d 等简写)
1.2 正则表达式精通:从 BRE 到 ERE 到 PCRE
grep 支持三种正则引擎,理解它们的差异是写出精准匹配的前提:
| 引擎 | 触发选项 | 特点 | 典型差异 |
|---|---|---|---|
| BRE(基础正则) | 默认 | +/?/|/() 需转义 | \+ 表示"一个或多个" |
| ERE(扩展正则) | -E | +/?/|/() 直接用 | + 表示"一个或多个" |
| PCRE(Perl 正则) | -P | 支持 \d/\w、 lookahead 等 | \d+ 匹配连续数字 |
实战推荐:日常使用优先选 -E(ERE),语法最直观;需要 \d 等简写或零宽断言时用 -P。
# ERE 实战:匹配 HTTP 状态码 4xx 或 5xx
grep -E "HTTP/[0-9.]+ [45][0-9]{2}" access.log
# PCRE 实战:提取 IPv4 地址
grep -Po "\b(\d{1,3}\.){3}\d{1,3}\b" access.log
# -o 只输出匹配部分(而非整行),配合 PCRE 精准提取
# 零宽断言:提取 JSON 字段值(lookbehind + lookahead)
grep -Po '(?<="api_key": ")[^"]+' config.json
1.3 高级用法:多文件、上下文与二进制
# 多模式 OR 搜索(两种写法等效)
grep -E "error|warn|fatal" app.log
grep -e "error" -e "warn" -e "fatal" app.log
# 从标准输入搜索(管道组合)
cat access.log | grep -E "5[0-9]{2}" | grep -v "500"
# 排除二进制文件 + 递归搜索
grep -rI "secret" /etc/ # -I 忽略二进制文件
# 统计每个文件中匹配行数 + 排序
grep -rc "TODO" src/ | sort -t: -k2 -rn | head -10
# fgrep(固定字符串搜索,不做正则解析,更快)
fgrep "exact_string_no_regex" bigfile.log
1.4 常见陷阱
陷阱 1:BRE 下 | 不是"或",() 不是分组——需要 \| 和 \(。初学者最常踩坑。
陷阱 2:grep "pattern" *.log 如果没有 .log 文件,grep 会报错。用 grep -r "pattern" . --include="*.log" 更安全。
陷阱 3:-P 不是所有系统都支持(macOS 的 grep 不支持 PCRE,需 brew install grep 安装 GNU grep)。
二、sed:流编辑器的替换艺术
2.1 核心概念:地址 + 命令
sed 的工作模型是逐行读取 → 匹配地址 → 执行命令 → 输出。"地址"决定哪些行被处理,"命令"决定如何处理:
# 地址语法速查
sed '3d' file # 删除第 3 行
sed '1,5d' file # 删除第 1-5 行
sed '/^#/d' file # 删除以 # 开头的行(注释)
sed '/error/,/warn/d' log # 删除从 error 到 warn 之间的所有行
# 命令速查
sed 's/old/new/' file # 替换每行第一个 old
sed 's/old/new/g' file # 替换每行所有 old(g = global)
sed 's/old/new/2' file # 只替换每行第 2 个匹配
sed 's/old/new/i' file # 忽略大小写替换
sed 'y/abc/xyz/' file # 字符映射(a→x, b→y, c→z)
2.2 替换实战:从简单到进阶
# 基础:修改配置文件端口
sed -i 's/Port 22/Port 2222/' /etc/ssh/sshd_config
# -i 直接修改文件(原地编辑),不加 -i 只输出到 stdout
# 安全原地编辑:自动备份
sed -i.bak 's/old/new/g' config.yaml
# 生成 config.yaml.bak 备份文件
# 引用匹配组(& 和 \1)
sed 's/[0-9]\+/(&)/g' file # 用 & 引用整个匹配:123 → (123)
sed -E 's/(\w+)@(\w+)/\1 at \2/g' emails.txt
# \1 引用第 1 组,\2 引用第 2 组
# bob@example → bob at example
# 删除行首行尾空白
sed 's/^ \+//' file # 删除行首空格
sed 's/ \+$//' file # 删除行尾空格
sed 's/^ \+//;s/ \+$//' # 组合:同时删首尾空格
# 多命令用分号连接或 -e 选项
sed -e 's/foo/bar/' -e 's/baz/qux/' file
2.3 高级技巧:多行模式与 Hold Space
sed 有两个缓冲区:Pattern Space(当前处理的行)和 Hold Space(临时存储区)。掌握它们的交换命令(h/H/g/G/x),就能处理跨行操作:
# 合并用 \ 续行的配置项(多行变单行)
sed ':a;N;/\\\n$/ba;s/\\\n//g' nginx.conf
# :a 设置标签,N 读取下一行到 Pattern Space
# 如果续行符号 \ 在行尾则跳回标签 a 继续读
# 最后把 \n 替换掉
# 反转文件所有行(类似 tac)
sed '1!G;h;$!d' file
# 1!G:非首行时把 Hold Space 追加到 Pattern Space
# h:把 Pattern Space 存到 Hold Space
# $!d:非末行时删除 Pattern Space(不输出)
# 到最后一行时自然输出:效果就是逆序
# 删除空行但保留一个(压缩连续空行)
sed '/^$/d' file # 删除所有空行
sed '/^$/{N;/^\n$/d}' file # 只删除连续空行,保留单个空行
2.4 常见陷阱
陷阱 1:sed -i 在 macOS 和 Linux 行为不同。macOS 需要 sed -i '' 's/old/new/' file(空字符串备份后缀),Linux 用 sed -i 's/old/new/' file。跨平台脚本建议用 sed -i.bak。
陷阱 2:替换含路径分隔符 / 的内容时,可以用其他分隔符:sed 's|/old/path|/new/path|g'。
陷阱 3:-i 会原地修改文件,没有撤销机制。务必先测试不加 -i 的版本,确认无误再加 -i。
三、awk:模式驱动的编程语言
3.1 核心模型:BEGIN + 模式-动作 + END
awk 不是简单的"打印字段"工具——它是完整的编程语言,有变量、条件、循环、函数、数组。其执行模型:
awk '
BEGIN { 初始化代码 } # 执行一次,在读取任何行之前
/模式/ { 动作代码 } # 每行匹配模式时执行
{ 默认动作代码 } # 无模式则每行都执行
END { 收尾代码 } # 执行一次,在所有行处理完毕后
' file
3.2 字段处理实战
# 基础字段操作(默认空格/tab 分隔)
awk '{print $1, $3}' data.txt # 打印第 1、3 列
awk '{print NR, $0}' data.txt # NR=行号,$0=整行
awk '{print NF}' data.txt # NF=当前行字段数
# 自定义分隔符
awk -F',' '{print $1, $2}' csv.csv # 逗号分隔
awk -F':' '{print $1}' /etc/passwd # 冒号分隔
awk -F'\t' '{print $1}' tsv.tsv # tab 分隔
# 多分隔符(正则分隔符)
awk -F'[,;|]' '{print $1}' mixed.txt # 逗号、分号、竖线都当分隔符
# 重组字段输出
awk -F',' '{print "Name:" $1, "Age:" $2, "City:" $3}' users.csv
3.3 条件过滤与计算
# 条件过滤
awk '$3 > 100' data.txt # 第 3 列 > 100 的行
awk '$1 == "ERROR"' app.log # 第 1 列等于 ERROR
awk '/5[0-9]{2}/' access.log # 行中包含 5xx 状态码
# 列求和与统计
awk '{sum += $3} END {print "Total:", sum}' sales.csv
awk '{sum += $3; count++} END {print "Avg:", sum/count}' sales.csv
# 按条件分组统计
awk -F',' '$3 == "Beijing" {sum += $2} END {print "Beijing total:", sum}' data.csv
# 计数与频次统计
awk '{count[$1]++} END {for(k in count) print k, count[k]}' access.log
# 按第 1 列(如 IP)分组计数,输出 IP + 访问次数
3.4 内置函数与字符串处理
# 字符串函数
awk '{print length($0)}' file # 每行长度
awk '{print toupper($1)}' file # 转大写
awk '{print substr($1, 1, 3)}' file # 截取子串
awk '{print index($0, "error")}' log # 查找子串位置
awk -F':' '{gsub(/^[ \t]+/, "", $1); print $1}' file # 去除字段首空格
# 数学函数
awk '{print sqrt($2), int($2), log($2)}' data.txt
# 格式化输出(printf)
awk -F',' '{printf "%-20s %8d %10.2f\n", $1, $2, $3}' report.csv
# %-20s 左对齐 20 字符宽,%8d 右对齐 8 位整数,%10.2f 10 字符 2 位小数
3.5 高级实战:报表生成与日志分析
# ========== 案例 1:Nginx 日志分析 ==========
# 统计各 HTTP 状态码出现次数 + 占比
awk '{status[$9]++} END {
total = 0;
for (s in status) total += status[s];
for (s in status) printf "%s: %d (%.1f%%)\n", s, status[s], status[s]*100/total;
}' access.log
# 提取 TOP 10 访问 IP
awk '{ip[$1]++} END {
for (k in ip) print ip[k], k;
}' access.log | sort -rn | head -10
# ========== 案例 2:CSV 报表生成 ==========
# 生成带合计行的销售报表
awk -F',' '
BEGIN {
printf "%-15s %10s %10s\n", "Region", "Sales", "Orders";
printf "%-15s %10s %10s\n", "------", "------", "-------";
}
{
region = $1; sales = $2; orders = $3;
total_sales += sales; total_orders += orders;
printf "%-15s %10.0f %10d\n", region, sales, orders;
}
END {
printf "%-15s %10s %10s\n", "------", "------", "-------";
printf "%-15s %10.0f %10d\n", "TOTAL", total_sales, total_orders;
}
' sales.csv
3.6 awk 数组与多维统计
# 二维统计:按 IP + 状态码交叉统计
awk '{matrix[$1][$9]++} END {
for (ip in matrix) {
for (status in matrix[ip]) {
print ip, status, matrix[ip][status];
}
}
}' access.log
# 利用 SUBSEP 模拟多维数组(传统 awk 不支持 [][] 语法)
awk '{key = $1 SUBSEP $9; count[key]++} END {
for (k in count) {
split(k, parts, SUBSEP);
print parts[1], parts[2], count[k];
}
}' access.log
四、三剑客组合实战:管道串联的威力
真正的战斗力在于组合。下面是几个高频组合案例:
4.1 日志分析全链路
# 提取 5xx 错误 → 统计 IP → TOP 10 → 生成报告
grep -E "HTTP/[0-9.]+ [5][0-9]{2}" access.log \
| awk '{print $1}' \
| sort \
| uniq -c \
| sort -rn \
| head -10
# 提取错误日志中的时间分布(按小时统计)
grep "ERROR" app.log \
| awk '{print substr($1, 1, 2)}' \
| sort \
| uniq -c \
| awk '{printf "%02d:00 - %d errors\n", $2, $1}'
# 过滤 + 替换 + 格式化
grep "exception" app.log \
| sed 's/exception/EXCEPTION/g' \
| awk '{printf "[%s] %s\n", $1, $0}'
4.2 配置文件批量处理
# 删除所有注释行 + 空行,输出干净配置
sed '/^#/d; /^$/d' nginx.conf
# 替换所有 IP 为新 IP + 验证
sed -i 's/192\.168\.1\.100/10\.0\.0\.50/g' *.conf
grep -r "10.0.0.50" . --include="*.conf"
# 提取所有 key=value 对,输出为 CSV
awk -F'=' '/^[^#]/ && NF==2 {
gsub(/^[ \t]+|[ \t]+$/, "", $1);
gsub(/^[ \t]+|[ \t]+$/, "", $2);
print $1 "," $2;
}' config.ini
4.3 数据清洗与转换
# JSON 日志 → CSV(简易提取)
grep "api_call" events.json \
| sed 's/.*"user":"\([^"]*\)".*"path":"\([^"]*\)".*"time":\([0-9]*\).*/\1,\2,\3/' \
| awk -F',' 'BEGIN{print "user,path,time"} {print}' > events.csv
# TSV → JSON(awk 生成)
awk -F'\t' 'NR>1 {
printf "{\"name\":\"%s\",\"age\":%d,\"city\":\"%s\"}", $1, $2, $3;
if(NR<FNR) printf ",";
printf "\n";
}' data.tsv > data.json
五、性能优化与选型决策
5.1 性能对比实测
在 100 万行日志文件上实测各工具处理速度:
| 操作 | 命令 | 耗时 | 备注 |
|---|---|---|---|
| 简单搜索 | grep "error" big.log | 0.3s | grep 最快,专精搜索 |
| 正则搜索 | grep -E "err|fail" big.log | 0.5s | ERE 略慢于固定字符串 |
| 行级替换 | sed 's/old/new/g' big.log | 1.2s | sed 单行替换极快 |
| 字段提取 | awk '{print $1,$3}' big.log | 1.8s | awk 需解析字段,稍慢 |
| 分组统计 | awk '{c[$1]++} END{...}' | 3.5s | 涉及哈希表,更慢但仍秒级 |
| Python 等效 | python script.py | 15s+ | 启动+解释开销,慢 10 倍+ |
5.2 选型决策流程
面对文本处理需求,如何快速决定用哪个工具?
# 决策流程(伪代码)
if 只需要查找/过滤:
→ grep(最快最简单)
elif 只需要替换/删除行:
→ sed(流编辑,一行搞定)
elif 需要字段操作/计算/报表:
→ awk(编程语言,灵活强大)
elif 需要搜索+替换+计算:
→ grep | sed | awk 管道组合
elif 数据量大(GB级)或逻辑极复杂:
→ 考虑专用工具(ripgrep、mlr、jq)
5.3 现代替代品推荐
经典三剑客之外,一些场景有更优选择:
| 工具 | 替代品 | 优势 |
|---|---|---|
| grep | ripgrep (rg) | 递归搜索速度快 5-10 倍,自动忽略 .git |
| grep -P | rg | 原生支持 PCRE,无需 GNU grep |
| sed | sd | 语法更直觉,内存安全,默认原地编辑有备份 |
| awk | Miller (mlr) | CSV/TSV/JSON 处理更专业,支持多格式转换 |
| awk | jq | JSON 处理专精(已有专题文章) |
建议:日常仍然优先学三剑客(所有 Linux 都预装),特殊场景用替代品。
六、最佳实践 Checklist
# ✓ grep 最佳实践
1. 优先用 -E (ERE) 而非默认 BRE,语法更直觉
2. 大文件搜索用 -F (固定字符串) 或 fgrep,更快
3. 递归搜索用 rg 或 grep -rI --include="*.ext"
4. 提取匹配内容用 -Po (PCRE + 只输出匹配)
5. 组合过滤用管道:grep ... | grep -v ...
# ✓ sed 最佳实践
1. 先不加 -i 测试,确认后再加 -i.bak(带备份)
2. macOS 用 -i '',Linux 用 -i,跨平台用 -i.bak
3. 含 / 的替换用 | 或 # 当分隔符
4. 多行操作用 N/H/G,先在简单文件上验证逻辑
5. 复杂 sed 脚本写进文件用 -f 调用
# ✓ awk 最佳实践
1. BEGIN 块放初始化、FS 设置、表头
2. END 块放汇总输出、统计结果
3. 大文件统计用 awk 而非 sort|uniq(避免排序开销)
4. printf 比 print 更可控(对齐、精度)
5. 复杂 awk 程序写进文件用 -f 调用
七、常见陷阱汇总
| 陷阱 | 描述 | 解法 |
|---|---|---|
| grep BRE 语法 | | 不是"或",( 不是分组 | 统一用 -E 或 -P |
| sed -i 跨平台 | macOS 和 Linux 参数不同 | 用 -i.bak 统一 |
| sed 无撤销 | -i 原地修改不可逆 | 先不加 -i 测试,或用 -i.bak |
| awk 分隔符默认 | 默认连续空格视为一个分隔符 | 用 -F 明确指定 |
| awk 数字比较 | "100" 是字符串,"100" > "99" 结果错误 | 强制数值:$2+0 > 99 |
| 管道性能 | 多次管道各启动一个进程 | 尽量在 awk 内完成多步逻辑 |
| 编码问题 | 处理 UTF-8 中文字符可能乱码 | 设置 LC_ALL=C 或用 rg |
结语
grep、sed、awk 三剑客是 Linux 文本处理的基石。它们看似简单,实则各有一个完整的能力体系:grep 的正则精通让你精准搜索,sed 的流编辑让你批量变换,awk 的编程模型让你计算统计。日常 90% 的文本处理任务——日志分析、数据清洗、配置编辑、报表生成——都能用三剑客组合搞定,而且比 Python 脚本快得多。
记住核心原则:找用 grep、改用 sed、算用 awk。精通它们,你的 Linux 命令行战斗力会跃升一个台阶。