一、先从一份日志说起
翻译成人话:IP 是 10.0.0.23 的客户端,在 12/Sep/2026 10:00:05 这个时间,用 POST 方式请求了 /api/login 这个接口,结果是 500(服务器出错),返回了 96 字节,花了 0.286 秒。
排查故障不需要读整行,只关心其中 5 个字段:第 1 个字段是来源 IP,第 4 个是请求时间,第 7 个是请求 URI,第 9 个是 HTTP 状态码,第 11 个是请求耗时。状态码 2 开头是成功,4 或 5 开头就是异常请求。
二、每条命令自带三条水道
Linux 程序一启动,就自动接好三条数据通道,Shell 用编号管理它们
编号 | 名称 | 默认连接 | 装什么 |
0 | stdin,标准输入 | 键盘 | 程序读取的数据 |
1 | stdout,标准输出 | 终端 | 正常处理结果 |
2 | stderr,标准错误 | 终端 | 错误和诊断信息 |
可以这样想象:0 号是进水管,1 号是出水管,2 号是排污管。平时出水管和排污管都对着你的屏幕,所以正常结果和报错混在一起看不出来。
执行 ls /etc/hosts /etc/file11111111 就能看到两条通道同时工作:/etc/hosts 的内容从 1 号通道出来,找不到文件的报错从 2 号通道出来,屏幕上一起显示,但它们本质上是两条独立的水道。
三、重定向:改变水的去向
重定向就是改变数据原来的去向。
大于号(覆盖保存标准输出):echo 111 > yangge.txt 把 111 写进文件。再执行 echo 222 > yangge.txt,文件里就只剩 222 了。大于号像倒水进杯子,再倒一次会先把杯子清空。它其实是 1 加大于号的简写,只管 1 号通道(stdout)。
双大于号(追加标准输出):echo 222 >> yangge.txt 不清空,把新内容写到文件末尾。写日志就是靠它,今天写一点、明天再写一点,越攒越多。
2 加大于号(保存标准错误):正常结果走 1 号通道,报错走 2 号通道。ls /etc/hosts /etc/file11111111 > yangge-1.txt 2> yangge-2.txt 就是把出水接进一个文件、把排污接进另一个文件。数字 2 就是标准错误的文件描述符,所以要写在符号前面。
/dev/null 是一个黑洞文件,写进去的东西全部消失。不需要看报错时,用 2> /dev/null 把排污管直接接进黑洞,只丢报错,不碰原文件。
小于号(让命令从文件读取):wc -l 文件名 和 wc -l < 文件名 结果不同,前者命令自己打开文件,输出带文件名;后者由 Shell 把文件内容从 0 号进水管喂给命令,所以只显示行数。这是最直接的「从文件读输入」演示。
四、管道:把两条命令串成流水线
管道(竖线符号)把前一条命令的 stdout 连接到后一条命令的 stdin(对应课件第 4、5 页)。形象地说:上一根水管的出水口,直接对接下一根水管的进水口。
head -n 20 access.log | wc -l 的数据流向是:访问日志先经过 head 取前 20 行,这 20 行不落盘、不显示,直接通过管道交给 wc -l 数行数,结果是 20。
要注意两点:第一,中间结果没有写入文件,而是直接交给了下一条命令,这是管道和重定向最大的区别,大于号把 stdout 写入文件,管道把 stdout 交给下一条命令的 stdin;第二,普通管道只传 stdout,stderr 不会跟着走。ls /etc/hosts /etc/file11111111 | wc -l 的报错仍然显示在终端,不会进入 wc。
tee 是管道旁边的三通接头:数据既流向下一节管子(或终端),又分一股存进文件。head -n 10 access.log | tee yangge.log 屏幕上能看到这 10 行,文件里也存了这 10 行。加 -a 就是追加模式,不清空原文件。
五、grep:带条件的渔网
grep 在输入中查找符合模式的行,符合条件的整行被捞出来。常用的选项各有分工:-n 显示行号,方便回到原文件定位;-i 忽略大小写,POST 和 post 都能抓到;-v 反向选择,把不符合的行留下;-c 统计匹配行数;-o 只输出匹配到的那一小段,不是整行;-A n、-B n、-C n 分别显示匹配行之后、之前、前后各 n 行的上下文;-E 使用扩展正则。
两个容易误解的点:一是 -c 统计的是匹配行数,不是某个词在一行中出现的次数;二是模式里写 ' 404 '(前后带空格)而不是 404,是为了匹配完整字段,否则 URI 里含 404 的行(比如 /api/v404x)也会被误捞。
六、正则表达式:描述文本特征的规则
正则表达式是一套描述文本特征的规则(对应课件第 6、7 页)。日志筛选先掌握这些基础符号:
元字符 | 含义 | 示例 |
^ | 行首 | ^10.0.0. |
$ | 行尾 | 0.[0-9]+$ |
[0-9] | 一个数字 | [45][0-9]{2} |
* 、+ | 重复 0 次以上、1 次以上 | [0-9]+ |
{n,m} | 重复 n 到 m 次 | [0-9]{1,3} |
( ) | 把多个字符组成整体 | (GET|POST) |
| | 或关系 | 404|500 |
\ | 把特殊字符还原为普通字符 | . 匹配点号 |
把 4xx 和 5xx 的匹配式拆开看:' (4[0-9]{2}|5[0-9]{2}) ' 读作「一个空格,然后 4 或 5 开头跟两位数字,再一个空格」。前面的空格保证匹配的是独立字段,4[0-9]{2} 就是 4 开头的三位数,竖线表示两组二选一。
基本正则和扩展正则的符号写法不同。日常日志筛选优先使用 grep -E,分组、或和重复次数更容易阅读。
七、sed:流水线上的编辑工
sed 是流式编辑器,工作模型是:按行读取输入,把当前行放进模式空间,执行地址和命令,然后输出结果,处理完一行再读下一行,直到文件结束。默认情况下,它不会修改原文件,这一点对新手特别友好,怎么敲都不会把源数据弄坏。
地址决定处理哪些行(比如 1,3 是第 1 到 3 行,/ 500 / 是包含 500 的行);命令决定怎样处理(p 打印、d 删除、s 替换);-n 关闭默认输出,和 p 配合只显示目标行;-E 使用扩展正则。
s 替换命令可以自己选分隔符。s#/api/students/[0-9]+#/api/students/:id# 用井号做分隔符,是因为 URI 里斜杠太多,用井号可以少写很多转义。行末的 g 表示替换当前行中全部匹配,不加 g 只替换第一处。
真正要修改文件时用 -i,而课件推荐写 -i.bak:原地修改的同时自动生成一个 .bak 备份文件。原则是:日志分析通常不修改原始日志,生产配置使用 sed -i 前必须完成预览并保留备份。
八、awk:按字段切分的统计员
awk 把每一行看作一条记录(Record),把一行中按分隔符拆开的内容看作字段(Field),默认分隔符是连续空白。访问日志每行字段顺序相同,所以 awk 可以直接取字段:0 是整行,1 是来源 IP,7 是 URI,9 是状态码,$11 是耗时;NR 是当前第几行,NF 是这行有几个字段;FS 决定从哪里切开,OFS 决定输出字段用什么连接。
日志默认用连续空白分隔,不用专门指定;读 /etc/passwd 这种冒号分隔的文件时,用 -F: 告诉 awk 从冒号处切开。-F 只影响读取,不影响原文件。
awk 的结构是条件加花括号动作,执行分三段:BEGIN 在读日志之前执行一次;条件加动作每读一行执行一次;END 在全部处理完之后执行一次。计数就是靠一个变量:条件满足时 count 加 1,END 里再打印出来;求和同理,sum 累加耗时字段。
FS 和 OFS 分工明确:FS 只影响输入怎样拆分,OFS 只影响多个字段怎样输出,都不会修改原始数据。
九、sort 与 uniq -c:统计的黄金搭档
按状态码统计数量前要认识三个小工具:sort 把相同内容排列到一起;uniq -c 合并相邻的重复行并显示数量;sort -nr 按数字从大到小排列统计结果。
为什么 uniq -c 前必须先 sort?因为 uniq -c 只会合并相邻的重复行,相同内容不排到一起就合并不了。所以完整处理链固定是四步:grep -oE 筛出状态码片段,sort 排到一起,uniq -c 去重计数,sort -nr 按次数从大到小排。这一套组合是日志统计里最常用的套路,本课后面所有统计都是它的变体,只是 awk 取的字段不同。
十、三个工具如何分工
grep 是筛选员,按条件挑出整行;sed 是修整员,对挑出来的行做删除和替换;awk 是统计员,按字段提取并计数求和。实战流程是 grep 筛选、sed 替换、awk 提取,每一步先用大于号保存结果、确认无误再进行下一步。
还有一个边界要知道:grep、sed 和 awk 适合处理普通文本日志;JSON 和 YAML 存在嵌套结构,应该用对应的结构化数据处理工具,相关命令在后续课程讲解。
十一、四个小概念
printf:awk 的格式化输出,IP=%s 状态=%s 里的 %s 是占位符,后面的1、9 依次填进去,输出比 print 更整齐。
关联数组:count[$9]++ 表示以状态码为下标分别计数,count[500] 就是状态码 500 的次数。相当于给每种状态码各设一个计数格。
2>&1:把 2 号通道合并进 1 号通道,正常输出和报错进同一个文件。注意重定向顺序会影响最终结果。
Here Document:用两个 EOF 标记多行内容的开始和结束,一次性给命令喂多行输入,结束的 EOF 必须单独占一行。
命令组与命令替换:花括号把多条命令组成一组,整组输出可以一起重定向;美元符号加圆括号让 Shell 先执行里面的小命令,把结果当字符串用,比如把日期塞进备份文件名。
十二、一句话总结
命令有三条通道(0 进、1 出、2 错),大于号和双大于号把结果写文件(覆盖与追加),2 加大于号单收报错,管道把 1 号通道接给下一条命令的 0 号通道,tee 边显示边保存;grep 用正则筛行,sed 在模式空间里按行加工且默认不动原文件,awk 按字段提取并统计;sort 加 uniq -c 加 sort -nr 是计数三件套。