【前情提要】 日志分析的三个层次讲清楚了,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。”

排错思路总结:

  1. grep 结果太多,不要直接刷屏,先 -c 统计数量。
  2. 想缩小范围,串联多个 grep。
  3. 顺序:先筛掉多的,再筛掉少的。
  4. 想排除某些行,用 grep -v。
  5. 想统计分布,用 awk + sort + uniq -c + sort -rn。
  6. 想批量模式,用 grep -f。

“记住,”老周说,“grep 是筛子,不是计算器。 你筛出你想要的行,再交给 awk 去算。别用 grep 做统计,那是 awk 的活。”

九、练习

  1. 练习一:用 grep -c 统计 access.log 中状态码 200、404、500 的数量。用 grep -n " 500 " | head -5 查看前 5 条 500 请求的行号。用 grep -v " 200 " | head -5 查看非 200 请求。
  2. 练习二:用 grep -E " 404 | 500 " 找出所有 404 或 500 的请求。用 grep -A 2 -B 2 " 500 " 查看 500 请求前后各 2 行。用 grep -i "error" 查找包含 error 的行(忽略大小写)。
  3. 练习三:创建 ~/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 实战:把日志“拆开看”》