Appearance
awk与日志分析
日志临时统计中经常需要对字段进行过滤、计数、求和和分组。awk 是专门做这件事的工具——它按"记录和字段"的模型处理文本,比 grep 多了一层结构化能力。
一、awk 的处理模型
awk 按"记录"(默认一行)读取输入,按"字段"(默认按空白字符切分)组织数据。每条记录进来之后执行模式匹配和对应的动作。
bash
awk '{print $1}' access.logawk 的核心概念:
| 概念 | 含义 |
|---|---|
| 记录 | 默认一行文本,对应 $0 |
| 字段 | 按分隔符切分后的独立元素,$1、$2、...、$NF |
NR | 当前处理的总行号(Number of Records) |
NF | 当前行的字段数量(Number of Fields) |
FS | 输入字段分隔符(Field Separator),默认空白 |
OFS | 输出字段分隔符(Output Field Separator),默认空格 |
打印行号和整行内容:
bash
awk '{print NR, $0}' app.log打印整行的最后一个字段:
bash
awk '{print $NF}' app.logawk 的基本结构是 awk '条件 { 动作 }' 文件——条件省略时每行都执行动作,动作省略时默认打印整行 {print $0}。
二、字段分隔符
使用 -F 指定输入分隔符。处理 /etc/passwd 时冒号分隔:
bash
awk -F: '{print $1, $3, $7}' /etc/passwd同时设置输出分隔符(用 BEGIN 块在读取任何数据之前执行):
bash
awk -F: 'BEGIN {OFS="\t"} {print $1, $3, $7}' /etc/passwdBEGIN 在 awk 开始读取第一行数据之前执行,适合初始化变量和设置分隔符。END 在所有数据处理完毕后执行,适合输出汇总统计。
bash
awk 'END {print "total lines:", NR}' app.log指定多个字符作为分隔符(正则):
bash
awk -F'[ :]' '{print $1, $2, $3}' access.log三、条件过滤
按字段值过滤:
bash
awk '$9 == 500 {print $0}' access.log # 第 9 字段等于 500按数字范围过滤:
bash
awk '$10 > 1048576 {print $1, $7, $10}' access.log # 响应大小超过 1 MiB按正则匹配行内容:
bash
awk '$0 ~ /timeout|ERROR/ {print NR, $0}' app.log排除匹配行:
bash
awk '$0 !~ /healthz/ {print}' access.log组合多个条件:
bash
awk '$9 >= 500 && $7 !~ /healthz/ {print $1, $7, $9}' access.logNginx 默认日志中各字段的位置取决于 log_format 配置。线上做统计前,先取几行样例确认 $9 在当前日志格式下是否确实是状态码。
四、统计计数
awk 内建关联数组——key 可以是任意字符串,适合按字段值分组统计。
统计每个状态码出现的次数:
bash
awk '{count[$9]++} END {for (code in count) print code, count[code]}' access.log结果按数量降序排列,交给 sort 完成:
bash
awk '{count[$9]++} END {for (code in count) print code, count[code]}' access.log |
sort -nr -k2统计访问最多的 IP Top 10:
bash
awk '{ip[$1]++} END {for (i in ip) print ip[i], i}' access.log |
sort -nr |
head五、求和和平均值
累加某个字段(如响应大小在第 10 字段):
bash
awk '{sum += $10} END {print sum}' access.log求平均值,需要同时统计行数:
bash
awk '{sum += $10; count++} END {if (count > 0) print sum / count}' access.log用 printf 格式化输出带有单位的数字:
bash
awk '
{
sum += $10
}
END {
printf "total: %.2f MB\n", sum / 1024 / 1024
}
' access.logprintf 比 print 更适合需要控制小数位和格式的输出。
六、Nginx 日志分析实例
常见的 combined 日志格式大约对应这些字段位置(具体取决于 nginx.conf 中的 log_format 定义):
$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent"combined 格式中 $request 字段包含 HTTP 方法和 URL,内部有空格。使用默认空白切字段时,请求内容会使字段偏移不一致。简单统计状态码和 IP 通常还能用,复杂统计建议将日志格式切换为 JSON 或使用专门的日志分析平台。
默认 combined 格式下常见统计:
按状态码统计:
bash
awk '{code[$9]++} END {for (c in code) print c, code[c]}' access.log | sort -nr -k2统计访问 IP Top 20:
bash
awk '{ip[$1]++} END {for (i in ip) print ip[i], i}' access.log | sort -nr | head -20查看所有 5xx 错误的请求:
bash
awk '$9 >= 500 {print $1, $7, $9}' access.log | head如果日志已经是 JSON 格式,用 jq 提取字段更精准:
bash
jq -r '.status' access.json.log | sort | uniq -c | sort -nrJSON 日志配合 jq 能避免 awk 字段偏移的问题。结构化日志是比"靠字段位置猜测"更可靠的长期方案。
七、awk 脚本文件
统计逻辑稍复杂时,写成 awk 脚本文件更好维护。
awk
# status_count.awk
{
code[$9]++
}
END {
for (c in code) {
print c, code[c]
}
}执行:
bash
awk -f status_count.awk access.log | sort -nr -k2适合保留的常用分析逻辑——状态码分布、慢请求统计、接口访问量排名——写成 awk 文件备查。一次性临时分析直接在命令行里写就够。
八、常用内置函数
awk 的常用字符串和时间函数:
| 函数 | 作用 |
|---|---|
length(str) | 字符串长度 |
substr(str, start, len) | 截取子串 |
split(str, arr, sep) | 按分隔符将字符串拆分到数组 |
tolower(str) / toupper(str) | 大小写转换 |
gsub(regex, repl, str) | 全局正则替换 |
strftime(format, timestamp) | 时间格式化(GNU awk) |
一个实用例子——去掉 URL 中的查询参数后统计接口访问量:
bash
awk '
{
path = $7
split(path, parts, "?") # 按 ? 拆开,前半段是路径,后半段是查询参数
clean_path = parts[1]
count[clean_path]++
}
END {
for (p in count) {
print count[p], p
}
}
' access.log | sort -nr | headsplit 把 $7 按 ? 字符切成两段,第一部分就是不带查询参数的干净路径,然后用这个干净路径做分组计数。这个操作在分析接口热度时很常用。