nginx默认日志中$1是ip、$9是状态码,但因time_local含空格易致字段错位,应优先用双引号分隔或match()提取;统计ip频次须用awk哈希计数而非uniq -c;匹配状态码需锚定^$避免误判;按小时切分时间字段需正确转义正则中的方括号。

怎么用 awk 提取 Nginx 日志里的 IP 和状态码
直接按空格切分,$1 是客户端 IP,$9 是 HTTP 状态码——但前提是日志格式没被自定义过。Nginx 默认 access log 的字段顺序是固定的:remote_addr - remote_user [time_local] "request" status bytes ...,所以 $1 和 $9 大概率可用。
常见错误是默认空格分隔时,[time_local] 里带空格(比如 [08/Sep/2026:10:36:00 +0800])会导致字段错位。这时不能硬靠 $9,得先用正则或 split() 定位。
- 稳妥做法:用双引号作为边界提取
"request"和状态码,例如awk -F'"' '{print $1,$3}' access.log | awk '{print $1,$(NF-1)}' - 更稳做法:用
match()配合substr()直接从整行匹配状态码,避免字段漂移 - 如果改过
log_format,必须先确认实际字段位置,用head -1 access.log | tr ' ' ' ' | nl数列
统计访问最多的 IP 为什么用 count[$1]++ 而不用 uniq -c
因为 uniq -c 只能处理已排序的连续重复行,而原始日志里同一 IP 的请求是散列的;awk '{count[$1]++} END{...}' 是哈希计数,不依赖顺序,准确且一次到位。
性能上,awk 内存计数比 sort | uniq 少一次磁盘 I/O 和大内存排序,100 万行日志快 3–5 倍。
- 错误写法:
awk '{print $1}' access.log | sort | uniq -c | sort -nr—— 多余的管道和排序 - 正确写法:
awk '{count[$1]++} END{for (ip in count) print count[ip], ip}' access.log | sort -nr | head -20 - 注意:
for (ip in count)遍历顺序不确定,必须靠sort排序,不能省
awk 统计 404/502 错误时,$9 ~ /^(404|502)$/ 比 grep 更准在哪
grep "404|502" 会把 2404、5021、甚至 URL 中的 404 都抓出来;而 $9 ~ /^(404|502)$/ 明确限定只匹配第 9 字段、且必须是完整值,不会误伤。
- 典型误判:
awk '$9 ~ /404/'→ 匹配4040、1404,应加锚点^和$ - 更安全写法:
awk '$9 == "404" || $9 == "502" {print $1, $7, $9}' access.log,用等值判断彻底规避正则歧义 - 如果日志里状态码后带短横(如
"502-"),需先gsub(/[- ]/, "", $9)清洗
按小时切分日志再分别统计,split($4, a, "[\[/:]") 怎么写才不报错
Nginx 时间字段形如 [08/Sep/2026:10:36:00 +0800],要取小时得先拆掉方括号和分隔符。split() 第二个参数是数组名,第三个是正则分隔符,方括号在正则里有特殊含义,必须转义。
错误写法:split($4, a, "[/:]") —— 没处理开头的 [,导致 a[1] 是空字符串;更糟的是 [ 在正则里表示字符组,不转义会语法错误。
- 正确写法:
split($4, a, "[\[/:]"),两个反斜杠是因为 shell 和 awk 各吃一个 - 然后小时在
a[4](即08/Sep/2026:10拆成"08","Sep","2026","10") - 生产环境建议加
if (length(a) >= 4)判断,防空字段崩溃
真正麻烦的不是语法,而是日志格式一致性——只要有一行 log_format 不对齐,整个 awk 统计就偏。上线前务必用 head -50 和 tail -50 对照字段位置,别信“默认应该一样”。











