Skip to content

awk与日志分析

日志临时统计中经常需要对字段进行过滤、计数、求和和分组。awk 是专门做这件事的工具——它按"记录和字段"的模型处理文本,比 grep 多了一层结构化能力。

一、awk 的处理模型

awk 按"记录"(默认一行)读取输入,按"字段"(默认按空白字符切分)组织数据。每条记录进来之后执行模式匹配和对应的动作。

bash
awk '{print $1}' access.log

awk 的核心概念:

概念含义
记录默认一行文本,对应 $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.log

awk 的基本结构是 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/passwd

BEGIN 在 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.log

Nginx 默认日志中各字段的位置取决于 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.log

printfprint 更适合需要控制小数位和格式的输出。

六、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 -nr

JSON 日志配合 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 | head

split$7? 字符切成两段,第一部分就是不带查询参数的干净路径,然后用这个干净路径做分组计数。这个操作在分析接口热度时很常用。