返回首页

awk 与 sed 批量文本处理实战指南 — 从日志分析到配置管理

📅 创建于 2026-06-02 🔄 更新于 2026-06-02 📝 1335 字

来源:运维派 | 发布日期:2026-05-31

awk 与 sed 批量文本处理实战指南

一句话总结: awk 管结构(字段分析),sed 管文本(行内替换)。配合管道能覆盖 90% 的运维文本处理需求。


一、awk 实战

核心工作原理

awk 按行读取,每行按分隔符分割成字段:$0=整行,$1=第一字段,$NF=最后一字段,NF=字段数。

# 基础:打印第一列和第二列
awk '{print $1, $2}' /etc/hosts

# 自定义分隔符
awk -F',' '{print $2}' access.log         # CSV
awk -F'|' '{print $1, $3}' config.txt     # 竖线分隔
awk -F'[,;]' '{print $2}' mixed.txt       # 正则:逗号或分号

BEGIN / END 块

# BEGIN 在读取前执行,END 在所有行处理完后执行
awk 'BEGIN {print "IP\t次数"} {ip[$1]++} END {for (i in ip) print i, ip[i]}' access.log
awk 'END {print "总行数:", NR}' access.log

条件过滤

# 只处理状态码 500 的行
awk '$9 == 500 {print $1, $7, $9}' access.log

# 正则匹配
awk '/\/api\/login/ {print $1, $7}' access.log
awk -F',' '$2 ~ /error/ {print $1, $3}' error.log

数组与统计分析

awk 关联数组不用提前声明,是统计分析的根基。

# 统计 IP 访问量 Top 20
awk '{ip[$1]++} END {for (k in ip) print ip[k], k}' access.log | sort -rn | head -20

# 统计每接口平均响应时间
awk -F',' '{cnt[$2]++; sum[$2]+=$4} END {for (k in cnt) printf "%s: %d次, 平均%.2fms\n", k, cnt[k], sum[k]/cnt[k]}' access.log

# 双维度统计:二维数组
awk '{h=int(substr($4,13,2)); status_pct[h,$9]++} END {
    for (h=0;h<=23;h++) printf "%02d:00 200=%d 404=%d 500=%d\n", h, status_pct[h,200]+0, status_pct[h,404]+0, status_pct[h,500]+0
}' access.log

# 去重计数(类似 uniq -c)
awk '!seen[$0]++' file.txt                              # 全局去重
awk '!seen[$1]++ {print $1}' access.log                  # 按第一字段去重

awk 内置函数

awk '{print toupper($1), tolower($2)}' file.txt        # 大小写转换
awk '{print substr($1, 1, 10)}' file.txt               # 截取前10字符
awk '{gsub(/error/, "ERROR"); print}' error.log        # 原地全局替换
awk '{n=split($0, parts, "/"); print "字段数:", n}'    # 分割字符串
awk 'BEGIN {printf "%s\t%.2f\t%06d\n", "name", 3.1415, 42}'  # 格式化输出

awk 脚本文件

复杂逻辑写成脚本文件可调试和复用。

#!/usr/bin/awk -f
# 用法: awk -f analyze_nginx.awk access.log
BEGIN {FS="[ ]+"; print "开始分析..."}
$9 ~ /^[0-9]{3}$/ {
    ip[$1]++; status[$9]++; url=$7; sub(/\?.*/,"",url); page[url]++
}
END {
    print "\n=== Top 10 IP ==="
    for (i in ip) ranking[ip[i]]=i
    c=0; for (p=999999; p>=0 && c<10; p--) if (ranking[p]) {print ranking[p],"=>",p,"次"; c++}
    print "\n=== 状态码分布 ==="
    for (s in status) printf "  %s: %d (%.1f%%)\n", s, status[s], status[s]*100/NR
}

处理大文件技巧

# 用 BEGIN 初始化减少迭代开销
awk 'BEGIN {FS=","} $3>100 {cnt++} END {print cnt}' large.csv

# 用 next 跳过不需要的行
awk 'NR==1 {next} $3>100 {cnt++} END {print cnt}' large.csv

# 超大文件先分割再合并
split -l 100000 big.log chunk_
for f in chunk_*; do awk '{...}' "$f"; done | awk '{...}'  # 合并结果

两文件关联查询(类 SQL JOIN)

# 文件A(user_id,username,email) JOIN 文件B(order_id,user_id,amount)
awk -F',' 'NR==FNR {user[$1]=$2; next} $2 in user {print user[$2], $1, $3}' users.txt orders.txt

Nginx 日志 8 大实战场景

以下假设 standard combined log format(字段: IP, 用户, 时间, 请求, 状态码, 字节, Referer, UA 或 request_time):

# 1. 统计每分钟请求量(排查流量高峰)
awk '{print $4}' access.log | sed 's/\[//;s/+0800//' | awk '{print substr($1,1,16)}' | sort | uniq -c | sort -rn | head -20

# 2. 状态码分布
awk '{s[$9]++} END {for (k in s) printf "%s: %d (%.1f%%)\n", k, s[k], s[k]*100/NR}' access.log

# 3. 慢请求($NF=request_time,>3s)
awk '$NF ~ /^[0-9.]+$/ && $NF > 3 {printf "[%s] %s %s %.1fs\n", substr($4,2,21), $1, $7, $NF}' access.log

# 4. 每个 IP 的请求量和带宽消耗
awk '{ip[$1]++; bytes[$1]+=$10} END {for (i in ip) printf "%s: %d次 %.2fMB\n", i, ip[i], bytes[i]/1024/1024}' access.log | sort -k2 -rn

# 5. 按接口统计调用量和平均响应时间
awk '{
    url=$7; sub(/\?.*/,"",url); cnt[url]++
    if ($NF ~ /^[0-9.]+$/) resp[url]+=$NF
} END {
    printf "%-40s %10s %12s\n","接口","调用量","平均(ms)"
    for (u in cnt) printf "%-40s %10d %12.2f\n", u, cnt[u], (u in resp)?resp[u]/cnt[u]:0
}' access.log | sort -k3 -rn | head -20

# 6. 分析 User-Agent,区分爬虫和浏览器
awk -F'"' '{ua=$6; if (ua~/bot|crawl|spider/i) bot[ua]++; else browser[ua]++}
END {for (b in bot) print bot[b],b}' access.log | sort -rn | head -20

# 7. 找出 404 死链
awk '$9==404 {print $7}' access.log | sort | uniq -c | sort -rn

# 8. 时段请求方法分布矩阵
awk '{
    m=substr($6,2); h=substr($4,13,2); mtx[h,m]++
} END {
    printf "%-6s %6s %6s %6s %6s\n","时段","GET","POST","PUT","DELETE"
    for (h=0;h<=23;h++) {hh=sprintf("%02d",h)
        printf "%-6s %6d %6d %6d %6d\n", hh":00", mtx[hh,"GET"]+0, mtx[hh,"POST"]+0, mtx[hh,"PUT"]+0, mtx[hh,"DELETE"]+0
    }
}' access.log

二、sed 实战

核心语法

sed [选项] '地址命令' 文件

# 常用选项
-n          # 不自动打印,配合 p 使用
-i.bak      # 直接修改文件并生成 .bak 备份
-r / -E     # 扩展正则

替换命令 s

# 基本替换
sed 's/error/ERROR/' error.log           # 只替换每行第一个
sed 's/error/ERROR/g' error.log          # 全局替换
sed '3,7s/error/ERROR/g' error.log       # 只处理第 3-7 行
sed -n 's/old/new/p' file.txt            # 预览替换结果

# 分隔符:避免路径中转义
sed 's#/etc/nginx#/opt/nginx#g' config.conf
sed 's|/etc/nginx|/opt/nginx|g' config.conf

# 捕获组
sed -E 's/(192\.168\.1\.)[0-9]+/\1XXX/' config.txt
# 多个替换
sed -e 's/old1/new1/g' -e 's/old2/new2/g' file.txt

地址与删除

sed '5d' file.txt                 # 删除第 5 行
sed '1,10d' file.txt              # 删除 1-10 行
sed '/^$/d' file.txt              # 删除空行
sed '/^#/d' config.conf           # 删除注释行
sed '/error/,+2d' error.log       # 删匹配行及其后 2 行
sed 's/^[ \t]*//' file.txt        # 删行首空格
sed '5!d' file.txt                # 保留第 5 行,删其他

插入/追加

sed '10i\new line' file.txt       # 第 10 行前插入
sed '10a\new line' file.txt       # 第 10 行后追加
sed '$a\文件末尾追加' file.txt     # 文件末尾追加
sed '1i\开头插入' file.txt         # 文件开头插入
sed '10c\替换整行' file.txt        # 替换第 10 行

生产场景:批量修改配置文件

# Nginx:批量改端口和域名
sed -i 's/listen\s*80;/listen 8080;/g' /etc/nginx/conf.d/*.conf
sed -i 's/server_name\s*old-domain;/server_name new-domain;/g' /etc/nginx/conf.d/*.conf
sed -i '/server {/a\    add_header X-Server "nginx-1.24" always;' /etc/nginx/nginx.conf

# MySQL:改连接数和超时
sed -i 's/^max_connections.*=.*100/max_connections = 500/' /etc/mysql/my.cnf
sed -i '/^\[mysqld\]$/a\innodb_buffer_pool_size = 4G' /etc/mysql/my.cnf

# 应用:改数据库连接
sed -i 's/jdbc:mysql:\/\/old-host:3306/jdbc:mysql:\/\/new-host:3306/g' app.properties
sed -i 's/server\.port=.*/server.port=8080/' application.yml

# 提取某时间段日志
sed -n '/29\/May\/2026:10:00/,/29\/May\/2026:11:00/p' access.log

多行处理

sed '/^$/N;/^\n$/d' file.txt        # 压缩连续空行为一行
sed 's/$/\r/' unix.txt > win.txt    # Unix→Windows 换行
sed -i 's/\r$//' win.txt            # Windows→Unix 换行

三、awk + sed 组合实战

# 1. sed 预处理 + awk 统计
sed 's/错误/error/g; s/警告/warning/g' app.log | awk '/error/ {cnt++} END {print cnt}'

# 2. awk 提数据 + sed 格式化
awk -F'=' '{print $1, $2}' config.txt | sed 's/^/| /; s/$/ |/; s/  / | /g'

# 3. 合并日志级别统计
sed 's/|/,/g' app.log | awk -F',' '{l[$3]++; m[$4]++} END {print "级别:", l["ERROR"], l["WARN"]}'

# 4. 两文件关联查询(类似 JOIN)
awk -F',' 'NR==FNR {u[$1]=$2; next} $2 in u {print u[$2], $1, $3}' users.txt orders.txt

四、完整日志分析脚本

#!/bin/bash
# nginx_log_analyzer.sh - 分析 Nginx 日志输出统计报告
LOG="${1:-/var/log/nginx/access.log}" REPORT="${2:-nginx_report_$(date +%Y%m%d).txt}"

[ ! -f "$LOG" ] && echo "文件不存在" && exit 1

{
  echo "Nginx 日志分析报告: $LOG ($(date))"
  echo "总请求: $(awk 'END{print NR}' "$LOG")"
  echo ""
  echo "=== 状态码分布 ==="
  awk '{s[$9]++} END {for (k in s) printf "  %s: %d (%.1f%%)\n", k, s[k], s[k]*100/NR}' "$LOG"
  echo ""
  echo "=== Top 20 IP ==="
  awk '{ip[$1]++} END {for (i in ip) print ip[i], i | "sort -rn | head -20"}' "$LOG"
  echo ""
  echo "=== Top 20 接口 ==="
  awk '{
    url=$7; sub(/\?.*/,"",url); cnt[url]++
    if ($NF~/^[0-9.]+$/) resp[url]+=$NF
  } END {for (u in cnt) print cnt[u], (u in resp)?resp[u]/cnt[u]:0, u | "sort -rn | head -20"}' "$LOG"
  echo ""
  echo "=== 每小时请求分布 ==="
  awk '{h=int(substr($4,13,2)); hh[h]++} END {for(h=0;h<=23;h++) printf "  %02d:00-%02d:59: %d次\n", h, h, hh[h]+0}' "$LOG"
  echo ""
  echo "=== 异常统计 ==="
  awk '{if($9>=500)e5++; if($9==404)e404++; if($9==403)e403++} END{printf "  5xx:%d 404:%d 403:%d\n", e5+0, e404+0, e403+0}' "$LOG"
} | tee "$REPORT"

五、常见陷阱

陷阱 说明 正确做法
-i 直接改文件 误操作无法恢复 sed -i.bak 's/old/new/g' file
单引号内不能用变量 sed 's/$OLD/$NEW/g' 不生效 sed "s/$OLD/$NEW/g"awk -v v="$VAR"
正则特殊字符未转义 IP 中的 . 匹配任意字符 sed 's/192\\.168\\.1\\.1/.../g'
awk 中 $0 不是 shell 变量 awk '$1 == $VAR' 不生效 awk -v var="$VAR" '$1 == var'
大文件加载到内存 awk 默认整行处理不占内存,但数组过大可能 分块处理:split -l 100000
Unicode 问题 中文环境干扰 LC_ALL=C awk '{print $1}' file

调试经验: 先用 head 看样本 → awk '{print NF, $0}' 确定字段数 → awk '...' | head 预览 → 再加 -i 修改。


六、工具选型速查

场景 推荐 原因
按字段提取 awk / cut awk 功能更强,cut 只支持固定分隔符
简单替换 sed 语法最简洁
复杂统计 awk 数组+函数强大
过滤行 grep 比 sed 更直观
批量改配置 sed 配合 -i 直接改文件
格式化输出 awk printf 支持好

性能参考(100MB 日志): cut -d' ' -f1 ~0.5s < awk '{print $1}' ~0.8s < sed 's/old/new/g' ~1.2s < awk '{gsub(/error/,"ERROR");print}' ~2.5s。


关联页面

页面 说明
linux-essential-commands-reference Linux 常用命令大全
scp-rsync-file-transfer 文件传输与备份命令
linux-compression-tools-comparison 压缩工具对比(tar/gzip)