【前情提要】
日志分析的三个层次讲清楚了,grep、awk、sed 的角色也分清了。老周说:“明天开始,一个一个练。先练 grep,它是你筛日志的第一把刀。”小林打开 grep 的 man 手册,准备学参数。但他不知道,-c、-n、-v、-i、-E、-A/-B/-C,每一个都有用。
一、老周的“针”和“海”
早上到工位,老周丢给小林一个任务:
“你博客的 access.log,现在多少行?”
小林敲:
sudo wc -l /var/log/nginx/access.log
输出:
102347 /var/log/nginx/access.log
“10 万行。”小林说。
“现在,你从这 10 万行里,找出所有状态码 500 的请求,统计有多少条,列出前 5 个 URL。”
小林打开 grep 手册,准备逐条查参数。
老周拦住他:“别查手册。手册是字典,不是教材。你直接上手,遇到问题再查。”
“那我怎么写?”
“你想想,你要什么。”
“我要 500 的请求。”
“那你就 grep " 500 "。”
小林敲:
sudo grep " 500 " /var/log/nginx/access.log
终端刷了一屏,还在继续。
“停。”老周说,“你 Ctrl+C。”
小林按了 Ctrl+C。
“你犯了两个错。”老周说,“第一,你没统计数量,直接刷屏。第二,你没想清楚 grep 到底怎么用。今天,我就把 grep 给你讲透。”
二、grep 的基本逻辑:按行匹配
“grep 的逻辑非常简单。”老周说,“你给它一个模式,它逐行扫描,把匹配的行打印出来。”
grep "模式" 文件
“比如,你找 500:”
grep " 500 " /var/log/nginx/access.log
“它会输出所有包含 500 的行。注意,前后有空格。”
“为什么加空格?”
“因为 500 可能出现在 IP 里、响应大小里、时间戳里。加空格,是精确匹配状态码字段。”老周说,“但这不是最严谨的。最严谨的是 awk '$9 == 500'。”
“那 grep 还有啥用?”
“grep 适合快速过滤文本。”老周说,“比如你找某个 IP、某个 URL、某个关键词。grep 是‘筛子’,不是‘计算器’。 你用它筛出你关心的行,再用 awk 去算。”
小林记下:
grep "模式" 文件:按行匹配- 加空格是为了精确匹配字段
grep负责筛,awk负责算
三、grep 常用参数:一个一个试
“现在,你把常用参数过一遍。”老周说,“我报参数,你敲命令,看输出。”
-c:只统计数量
“你刚才 grep " 500 " 刷屏,就是因为没加 -c。”
sudo grep -c " 500 " /var/log/nginx/access.log
输出:
87
“87 条 500 请求。”小林说。
“-c 只输出匹配行数,不输出内容。你以后统计数量,第一反应就是 -c。”
-n:显示行号
“你想知道 500 请求在第几行。”
sudo grep -n " 500 " /var/log/nginx/access.log | head -5
输出:
1234:192.168.1.100 - - [14/Sep/2026:10:00:03 +0800] "GET /api/health HTTP/1.1" 500 153 "-" "curl/7.81.0"
5678:192.168.1.101 - - [14/Sep/2026:10:05:12 +0800] "POST /api/login HTTP/1.1" 500 200 "-" "curl/7.81.0"
...
“行号在冒号前面。”老周说,“你以后定位问题,用 -n 找到行号,再用 sed -n '行号p' 看上下文。”
-v:反向匹配
“你想排除某些行。”
sudo grep -v " 200 " /var/log/nginx/access.log | head -5
“输出所有不是 200 的行。”老周说,“-v 是 invert,反向匹配。你以后排除噪音,用 -v。”
-i:忽略大小写
“日志里有时候大小写不统一。”
sudo grep -i "error" /var/log/nginx/access.log | head -5
“-i 忽略大小写,error、Error、ERROR 都能匹配。”
-E:扩展正则
“你想匹配多个条件。”
sudo grep -E " 404 | 500 " /var/log/nginx/access.log | head -5
“-E 支持扩展正则,| 表示‘或’。匹配 404 或 500。”
“那不加 -E 呢?”
“不加 -E,| 会被当成普通字符。你得写 grep " 404 \| 500 ",加反斜杠转义。”老周说,“但 -E 更直观,推荐用 -E。”
-A、-B、-C:看上下文
“你找到一条错误,想看它前后几行。”
sudo grep -A 3 -B 2 " 500 " /var/log/nginx/access.log | head -20
“-A 3 显示匹配行后 3 行,-B 2 显示前 2 行。-C 3 是前后各 3 行。”老周说,“你以后排查错误,用 -C 看上下文。”
小林把参数抄进笔记本:
-c:统计数量-n:显示行号-v:反向匹配-i:忽略大小写-E:扩展正则-A N:后 N 行-B N:前 N 行-C N:前后各 N 行
四、实战一:找出所有 404
“现在实战。”老周说,“第一题:统计 404 数量。”
sudo grep -c " 404 " /var/log/nginx/access.log
输出:
1523
“第二题:列出前 5 个 404 URL。”
sudo grep " 404 " /var/log/nginx/access.log | awk '{print $7}' | head -5
输出:
/api/health
/api/health
/images/missing.png
/posts/old-post/
/favicon.ico
“第三题:统计 404 最多的 URL。”
sudo grep " 404 " /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
输出:
500 /api/health
300 /images/missing.png
200 /posts/old-post/
100 /favicon.ico
“看到了吗?”老周说,“grep 筛出 404 的行,awk 提取 URL,sort | uniq -c | sort -rn 统计。这就是日志分析的标准管道。”
五、实战二:找出某个 IP 的请求
“第二题:找出 192.168.1.100 的所有请求。”
sudo grep "^192.168.1.100" /var/log/nginx/access.log | head -10
输出:
192.168.1.100 - - [14/Sep/2026:10:00:01 +0800] "GET / HTTP/1.1" 200 1234 "-" "Mozilla/5.0"
192.168.1.100 - - [14/Sep/2026:10:00:03 +0800] "GET /api/health HTTP/1.1" 500 153 "-" "curl/7.81.0"
...
“^ 表示行首。”老周说,“^192.168.1.100 表示这一行以这个 IP 开头。你以后按 IP 过滤,用 ^。”
“那如果我按 URL 过滤呢?”
sudo grep "/api/health" /var/log/nginx/access.log | head -10
“不用 ^,因为 URL 不在行首。”老周说,“grep 默认是‘包含匹配’,只要行里有这个字符串,就匹配。”
六、实战三:找出包含特定 URL 的请求
“第三题:找出所有访问 /posts/ 的请求。”
sudo grep "/posts/" /var/log/nginx/access.log | head -10
“第四题:统计 /posts/ 下每个 URL 的访问量。”
sudo grep "/posts/" /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
输出:
300 /posts/my-first-post/
150 /posts/hello-world/
50 /posts/old-post/
“第五题:找出所有 500 请求里,包含 /api/ 的。”
sudo grep " 500 " /var/log/nginx/access.log | grep "/api/" | head -10
“两个 grep 串联。”老周说,“第一个筛 500,第二个筛 /api/。管道可以串联多个 grep,逐步缩小范围。”
七、grep -f:从文件读模式
“你刚才都是手写模式。”老周说,“如果模式很多,怎么办?”
“写文件里?”
“对。”老周说,“grep -f 模式文件 日志文件。”
小林建了一个文件:
nano ~/patterns.txt
内容:
/api/health
/api/login
/api/user
然后:
sudo grep -f ~/patterns.txt /var/log/nginx/access.log | head -10
输出所有包含这三个模式的行。
“这有啥用?”
“你以后有一批 IP 要过滤,或者一批 URL 要排除,就写文件里。”老周说,“grep -f 适合批量模式匹配。”
八、排错挑战:grep 结果太多,怎么缩小
下午,小林想找出所有 500 请求,但:
sudo grep " 500 " /var/log/nginx/access.log
输出 87 条,刷了好几屏。
“87 条不多,但我想只看 /api/ 的 500。”
sudo grep " 500 " /var/log/nginx/access.log | grep "/api/"
输出 50 条。
“还是多,我想统计 /api/ 下每个 URL 的 500 数量。”
sudo grep " 500 " /var/log/nginx/access.log | grep "/api/" | awk '{print $7}' | sort | uniq -c | sort -rn
输出:
30 /api/health
15 /api/login
5 /api/user
“看到了吗?”老周说,“逐步缩小范围。 你先 grep 500,再 grep /api/,再 awk 提取,再统计。每一步都缩小范围,最后得出精确结论。”
“那如果我一开始就 grep "/api/",再 grep " 500 " 呢?”
“效果一样,但顺序有讲究。”老周说,“grep " 500 " 筛掉大部分行,再 grep "/api/" 更快。先筛掉多的,再筛掉少的。”
“那如果我想排除某些 URL 呢?”
“grep -v。”
sudo grep " 500 " /var/log/nginx/access.log | grep -v "/api/health" | awk '{print $7}' | sort | uniq -c | sort -rn
“排除 /api/health,看其他 500。”
排错思路总结:
grep结果太多,不要直接刷屏,先-c统计数量。- 想缩小范围,串联多个
grep。 - 顺序:先筛掉多的,再筛掉少的。
- 想排除某些行,用
grep -v。 - 想统计分布,用
awk+sort+uniq -c+sort -rn。 - 想批量模式,用
grep -f。
“记住,”老周说,“grep 是筛子,不是计算器。 你筛出你想要的行,再交给 awk 去算。别用 grep 做统计,那是 awk 的活。”
九、练习
- 练习一:用
grep -c统计access.log中状态码 200、404、500 的数量。用grep -n " 500 " | head -5查看前 5 条 500 请求的行号。用grep -v " 200 " | head -5查看非 200 请求。 - 练习二:用
grep -E " 404 | 500 "找出所有 404 或 500 的请求。用grep -A 2 -B 2 " 500 "查看 500 请求前后各 2 行。用grep -i "error"查找包含error的行(忽略大小写)。 - 练习三:创建
~/patterns.txt,写入/api/health、/api/login。用grep -f ~/patterns.txt /var/log/nginx/access.log | head -10查看匹配行。然后用grep " 500 " | grep -v "/api/health" | awk '{print $7}' | sort | uniq -c | sort -rn | head -10统计排除/api/health后的 500 URL 分布。
参考答案
练习一:
sudo grep -c " 200 " /var/log/nginx/access.log
sudo grep -c " 404 " /var/log/nginx/access.log
sudo grep -c " 500 " /var/log/nginx/access.log
sudo grep -n " 500 " /var/log/nginx/access.log | head -5
sudo grep -v " 200 " /var/log/nginx/access.log | head -5
练习二:
sudo grep -E " 404 | 500 " /var/log/nginx/access.log | head -10
sudo grep -A 2 -B 2 " 500 " /var/log/nginx/access.log | head -20
sudo grep -i "error" /var/log/nginx/access.log | head -10
练习三:
nano ~/patterns.txt
内容:
/api/health
/api/login
sudo grep -f ~/patterns.txt /var/log/nginx/access.log | head -10
sudo grep " 500 " /var/log/nginx/access.log | grep -v "/api/health" | awk '{print $7}' | sort | uniq -c | sort -rn | head -10
十、下集预告
grep 练熟了,小林能从 10 万行日志里快速筛出想要的行。老周说:“grep 是筛子,但你要统计,还得靠 awk。”小林打开 awk 文档,准备学字段处理。但他不知道,$1、$NF、-F、NR、END,每一个都有用。
下一集:《awk 实战:把日志“拆开看”》