1. 文本处理三剑客的分工
一句话总结: grep 负责找行,sed 负责按行改,awk 负责按字段算——三者的输入输出都是流,可以自由组合成管道。
文本处理的核心哲学是流式:一个命令读入文本流,处理后输出文本流,下一个命令接着消费。没有中间文件,内存占用恒定。
# 分工示例:grep 选行 → sed 替换 → awk 算字段
grep -E "ERROR|WARN" app.log \
| sed 's/^\[//' \
| awk '{print $1, $NF}'
1.1 何时选谁
| 工具 | 强项 | 弱项 |
|---|---|---|
| grep | 按模式筛选行 | 不能改内容 |
| sed | 按地址改行内容 | 字段计算弱 |
| awk | 字段拆分、统计、报表 | 交互不如 vim |
判断口诀:只看不删选 grep,改行用 sed,算列用 awk。三者叠加能解决九成日志/配置处理问题。
2. sed 基础与行寻址
一句话总结: sed 按"地址+命令"工作,地址可以是行号、范围或正则,缺省地址就是每一行。
sed 的调用格式是 sed '地址 命令' 文件。地址决定了命令作用在哪些行上。
# 行号寻址
sed -n '3p' file # 只打印第 3 行(-n 抑制默认输出)
sed -n '2,5p' file # 打印第 2~5 行
sed -n '1~2p' file # 第 1 行起隔一行打(奇数行)
# 正则寻址
sed -n '/error/p' file # 打印含 error 的行
sed '/^#/d' file # 删除注释行
sed '/^$/,/END/d' file # 删除空行到 END 之间的块
2.1 常用 sed 命令
| 命令 | 作用 | 例子 |
|---|---|---|
p | 打印 | sed -n '1,3p' |
d | 删除 | sed '/debug/d' |
s | 替换 | sed 's/a/b/' |
i | 在行前插入 | sed '2i new' |
a | 在行后追加 | sed '2a new' |
c | 整行替换 | sed '3c new' |
y | 字符映射 | sed 'y/abc/xyz/' |
3. sed 替换与流编辑
一句话总结:
s///是 sed 最常用的编辑命令,尾部的 g/i/p 标志控制全局、忽略大小写与打印。
# 基本替换:每行只替换第一个匹配
sed 's/foo/bar/' config.txt
# g 标志:全局替换整行所有匹配
sed 's/foo/bar/g' config.txt
# i 忽略大小写、p 打印被改行
sed -n 's/error/ERROR/gp' app.log
# 指定第 2 个匹配
sed 's/,/ /2' data.txt
3.1 原地编辑与分隔符选择
# -i 原地编辑(macOS 需 -i '')
sed -i.bak 's/old/new/g' app.conf # 备份为 app.conf.bak
# 路径含斜杠时换分隔符,避免转义地狱
sed 's#/usr/local#/opt#g' paths.txt
sed 's|http://|https://|g' urls.txt
一句话总结: 替换路径类内容请换用
#或|做分隔符,否则\/转义会让表达式难读易错。生产环境改配置务必先-i.bak留备份。
3.2 多行操作与保持空间
sed 默认一行一处理,但配合 N(读下一行进模式空间)、H(追加到保持空间)、G(把保持空间拼回来),能处理跨行的结构。
# N:把下一行并入当前行,处理"两行一记录"的数据
# 把被换行拆开的 key=value 重新拼成一行
sed -n 'N; s/\n/ /; p' pairs.txt
# 保持空间:倒序文件(1tac 的 sed 版)
sed '1!G; h; $!d' file
# 删除两行模式块:从 <start> 到 </end>
sed '/<start>/,/<\/end>/d' config.xml
| 命令 | 作用 |
|---|---|
N | 追加下一行到模式空间 |
P | 打印模式空间第一行 |
h | 复制模式空间到保持空间 |
H | 追加模式空间到保持空间 |
g | 用保持空间覆盖模式空间 |
G | 追加保持空间到模式空间 |
一句话总结: 单行搞不定就上
N与保持空间,但多数场景用N就够;保持空间组合能写的"花活"很多,优先级低于可读性。
4. awk 字段模型与内建变量
一句话总结: awk 默认按空白把每行拆成字段,
$1$2是字段,NF是字段数,NR是行号,字段分隔符由 FS/OFS 控制。
awk 程序的基本结构是 awk '条件 {动作}'。默认动作是把整行打印出来,默认字段分隔符是空白。
# 打印第一列和最后一列
awk '{print $1, $NF}' /etc/passwd
# NF 是字段数,$NF 是最后一个字段
echo "a b c d" | awk '{print NF, $NF}' # 4 d
# NR 是总行号,FNR 是当前文件行号
awk '{print NR, $0}' file
4.1 FS 与 OFS 字段分割
| 变量 | 全称 | 默认值 | 作用 |
|---|---|---|---|
FS | Field Separator | 空格 | 输入分隔符 |
OFS | Output Field Separator | 空格 | 输出分隔符 |
RS | Record Separator | 换行 | 输入记录分隔 |
ORS | Output Record Separator | 换行 | 输出记录分隔 |
NF | Number of Fields | — | 当前行字段数 |
NR | Number of Records | — | 已读总行数 |
# CSV:用逗号分割,输出加竖线
awk -F, '{print $1 "|" $2}' data.csv
# 用 BEGIN 设置 FS/OFS,输出重新拼装
awk 'BEGIN {FS=":"; OFS="\t"} {print $1, $3}' /etc/passwd
5. awk 模式与内建函数
一句话总结: BEGIN 在读取前执行、END 在读完执行,printf 控制格式,length/substr 处理字符串,形成 awk 的完整计算能力。
# BEGIN/END 做表头与汇总
awk 'BEGIN {print "用户\tUID"}
{print $1 "\t" $3}
END {print "共 " NR " 行"}' /etc/passwd
# printf 对齐输出
awk '{printf "%-10s %8d\n", $1, $3}' file
# 条件模式:只处理匹配行
awk '$3 >= 1000 {print $1}' /etc/passwd
5.1 常用内建函数
| 函数 | 作用 | 例子 |
|---|---|---|
length($0) | 行长度 | awk '{print length}' |
substr(s, a, b) | 取子串 | substr($1,1,3) |
index(s, t) | 找子串位置 | index($0,"err") |
split(s, arr, sep) | 切分到数组 | split($1,a,",") |
toupper/tolower | 大小写转换 | toupper($2) |
int/sqrt/rand | 数值函数 | int($2/1024) |
# 实战:把字节数转成 MB 并保留两位小数
ls -l | awk '{size=$5/1024/1024; printf "%.2f MB %s\n", size, $9}'
6. awk 数组与统计
一句话总结: awk 的关联数组以任意字符串为下标,天然适合按 key 分组计数,统计报表一写即成。
awk 数组是关联数组,下标不限于数字,这使"按用户统计"“按状态码统计"这类聚合变成几行代码。
# 按第一列计数
awk '{count[$1]++} END {for (k in count) print k, count[k]}' file
# 按 IP 统计访问次数并排序输出
awk '{ip[$1]++} END {for (k in ip) print ip[k], k}' access.log \
| sort -rn | head -10
# 求和、最大值
awk '{sum+=$2; if($2>max) max=$2}
END {print "总和", sum, "最大", max}' sales.txt
6.1 实战报表
# 按状态码统计 HTTP 日志
awk '{code[$9]++}
END {for (c in code) printf "%s %d\n", c, code[c]}' access.log
# 行号 + 字段数双指标
awk '{printf "%4d 行 %2d 字段 %s\n", NR, NF, $1}'
一句话总结: awk 数组按下标聚合、END 块收尾打印,配合 sort 就是最轻量的报表引擎,无需引入 Python 或 Excel。
7. sed 与 awk 联合实战
一句话总结: 管道串联 sed 清洗、awk 计算,一个复杂任务被拆成职责单一的小命令,这是文本处理的组合美学。
# 案例一:清洗日志时间戳并统计错误率
# 1) sed 去掉方括号 2) 按小时聚合 3) 算错误占比
grep -E "ERROR|INFO" app.log \
| sed 's/^\[//; s/\]//' \
| awk '{
split($1, t, ":");
hour=t[1] ":" t[2];
total[hour]++;
if ($0 ~ /ERROR/) err[hour]++;
}
END {
for (h in total)
printf "%s 总量%d 错误%d 率%.1f%%\n",
h, total[h], err[h], err[h]*100/total[h]
}'
7.1 常见组合套路
| 套路 | 命令 |
|---|---|
| 去重取列 | awk '!seen[$1]++ {print}' |
| 删空白行再编号 | sed '/^$/d' | awk '{print NR": "$0}' |
| 多文件头去重 | awk 'FNR==1 && NR!=1 {next} {print}' f1 f2 |
| 列合并加分隔 | paste -d, f1 f2 配合 awk |
# 去重:保留每个 key 的第一行
awk '!seen[$1]++ {print $0}' dup.txt
# 合并两个文件的对应列
paste -d, name.txt score.txt | awk -F, '{print $1, $2, $1+$2}'
7.2 访问日志分析综合案例
把访问日志按「小时 + 状态码」聚合,并挑出 TOP 错误 IP:
# 假设 access.log 格式:
# 2026-09-30 10:23:45 GET /api/user 200 192.168.1.5
# 1) 统计每小时各状态码数量
awk '{split($2, t, ":"); hour=t[1] "时";
code[$9]++;
hourly[hour]++}
END {for (h in hourly) printf "%s %d\n", h, hourly[h]}' \
access.log | sort
# 2) 找出返回 5xx 次数最多的 5 个 IP
awk '$9 ~ /^5/ {ip[$NF]++}
END {for (i in ip) print ip[i], i}' access.log \
| sort -rn | head -5
# 3) 用 sed 先清洗方括号时间戳,awk 再做聚合
grep 'ERROR' app.log \
| sed 's/^\[//; s/\] .*//' \
| awk '{print $1, "发生次数"}' | sort | uniq -c
| 步骤 | 工具 | 产出 |
|---|---|---|
| 筛行 | grep | 只留 ERROR/指定状态码 |
| 清洗 | sed | 去掉方括号、截断多余字段 |
| 聚合 | awk | 按 key 计数、求和 |
| 排序 | sort | TOP N 排序展示 |
一句话总结: 日志分析的标准流水线是
grep → sed → awk → sort,每一步职责单一。先把行筛对、再洗干净、最后算字段,复杂报表也能拆成四步管道。
8. 总结
| 环节 | 要点 |
|---|---|
| 分工 | grep 找行、sed 改行、awk 算字段 |
| sed 寻址 | 行号/范围/正则三类地址,-n 抑制默认输出 |
| sed 编辑 | s/// 加 g/i 标志,#/` |
| awk 字段 | $1~$NF,FS/OFS 控制分隔,NF/NR 记字段与行号 |
| awk 模式 | BEGIN/END 钩子,printf 格式,length/substr 字符串函数 |
| awk 数组 | 关联数组按 key 聚合,END 块输出报表 |
| 联合 | 管道串 sed 清洗与 awk 统计,一专多能 |
awk 与 sed 是"读日志、改配置、出报表"这三件事的最短路径。sed 记行寻址与 s 命令,awk 记 FS/OFS 与关联数组,再配合 grep 筛行,就能徒手处理大部分文本加工任务。下一条管道接着理解进程与信号。
延伸阅读
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。