awk 与 sed 文本处理实战

系统讲解 sed 的行寻址与流编辑命令、awk 的 FS/OFS 字段模型与 NF/NR 内建变量,以及两者组合处理日志与配置文件的完整实战。

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 字段分割

变量全称默认值作用
FSField Separator空格输入分隔符
OFSOutput Field Separator空格输出分隔符
RSRecord Separator换行输入记录分隔
ORSOutput Record Separator换行输出记录分隔
NFNumber of Fields—当前行字段数
NRNumber 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 计数、求和
排序sortTOP 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 筛行,就能徒手处理大部分文本加工任务。下一条管道接着理解进程与信号。

延伸阅读

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「shell」更多文章

  1. 脚本性能优化实战
  2. 网络请求与诊断实战
  3. 定时任务调度实战