Linux系统常用命令看到第十五篇,说明能坚持读到这里的,多半已经在日常使用中体会到Shell的威力了。这篇聊一个每次讲都能讲出新花样的组合——grep、sed、awk,俗称“文本处理三剑客”。单拎出来每一个都不复杂,但把它们串在一起,就能解决大部分日志分析、配置修改、数据提取的日常需求。这篇会把三者的侧重点、用法细节和组合思路拆开讲一遍,内容相对扎实,建议边看边在终端里试。
1. 三个命令的分工
先统一定位:很多人分不清 grep、sed、awk 到底该用哪一个,其实可以把它们当成三个各司其职的处理器。
grep 是过滤器,只负责从输入里挑出符合条件的行,不做修改。它的核心能力是快速缩小范围,比如从几百MB日志里捞出一批含关键字“error”的行。sed 是编辑器,在不打开文件的情况下对文本做替换、删除、插入等修改,默认不改原文件,而是输出修改后的结果。awk 是分析器,适合处理有结构的文本,特别是空白分隔的表格、日志片段、配置文件片段,它可以按列拆分数据并做求和、计数、条件判断等操作。
用一句话总结:grep 先筛出来,sed 再改一改,awk 最后算一算。
这三个命令的共同基础是正则表达式。理解正则,哪怕只会几个常用符号,也足以应对大多数场景。通配符是Shell层面用的,正则表达式是文本匹配规则,两者别混淆。常用符号包括:点号匹配任意单个字符、星号匹配前一个字符零次或多次、方括号匹配字符集合、脱字符表示行首、美元符表示行尾。
2. 为什么先把范围缩到最小
使用三剑客的第一原则是:能缩小范围就先缩小范围。很多人拿到一个日志文件,不管三七二十一先 awk 一把,然后在里面做一堆匹配,结果表达式写得又长又难维护。实际更高效的做法是先 grep 过滤,再交给 sed 或 awk 处理。比如排查线上故障,第一步一定是快速定位时间窗口,第二步才是提取具体字段。这个习惯比记住任何参数都重要。
再强调一个处理大文件的心法:命令都是流式处理的,数据是一条条流过管道,内存占用非常少。但如果在 awk 之外还要嵌套一堆循环,或者把每行结果存进数组再做多层循环,性能就会明显下降。保持管道简单、步骤清晰,是在处理海量数据时不出乱子的关键。
3. grep 常用操作详解
grep 的使用频率最高,但很多人来来回回只会grep keyword file。其实有几个组合用法值得形成肌肉记忆。
3.1 常用参数组合
最基本的用法是从日志文件里匹配关键字:
grep "error" app.log实际工作中几乎不会只匹配单个词,通常都会用管道配合。查看包含关键字的同时也显示前后几行,是定位上下文最实用的方法:
grep -n -C 3 "timeout" app.log-n显示行号,-C 3显示匹配行前后各3行。排查问题的时候很少会看孤零零的一行,往往需要上下文才能判断这个错误是不是致命,所以-C参数建议常用。
如果只要文件清单,不关心具体内容,用-l只列出包含匹配的文件名:
grep -l "password" *.conf想知道哪些文件不包含某个关键字,用-L。反向排除某个词,用-v,比如想看日志里除了健康检查接口之外的所有请求:
grep -v "/healthz" access.log多条件同时匹配,可以用-E配合正则。例如匹配两种关键字之一:
grep -E "ERROR|FATAL" app.log3.2 固定字符串搜索与性能注意
正则表达式虽然方便,但有一个性能陷阱:当搜索超长字符串时,正则引擎需要回溯,速度会变慢。如果关键字里恰好包含点号、星号这些有特殊含义的字符,还容易误匹配。遇到这种情况,加-F参数强制把它当成固定字符串处理,不对特殊语法做解释:
grep -F "auth.token=12345" access.log固定字符串搜索的速度远快于正则,这在日志量特别大时体感明显。我通常的做法是:先在粗粒度上用-F快速缩小范围,再把结果交给正则做精细处理。
3.3 结合文件通配
grep 可以直接翻目录,不需要先拼接所有文件名:
grep "connection refused" /var/log/nginx/*.log如果涉及子目录,加上-r递归搜索:
grep -r --include="*.log" "disk full" /var/log--include限制文件类型,避免误读二进制文件。还嫌慢就用--exclude-dir跳过一些无用的归档目录。
提示:
grep a file和cat file | grep a效果一样,但后者多了一个无意义的 cat 进程,文件大了还费内存。直接 grep 就行,这种无意义管道属于同事看了会想优化掉的操作。
4. sed 修改与提取
sed 是流编辑器,一句话解释就是逐行读入、按规则处理、再逐行输出。默认不改文件本身,只有在加了-i参数之后才会修改原文件。理解这个特性非常重要,很多人第一次用 sed 替换后找不到变化,以为是命令没生效,其实是没加-i。
4.1 替换与全局标记
替换是 sed 最常用的功能,基本语法是s/被替换的内容/替换成的内容/。看个配置修改的例子:
sed 's/worker_processes 1/worker_processes 4/' nginx.conf执行后屏幕会输出修改后的内容,但 nginx.conf 本身没变。确认无误后想真正保存修改:
sed -i 's/worker_processes 1/worker_processes 4/' nginx.conf注意每行只替换第一个匹配的位置。如果一行里出现多次匹配,必须加全局标记:
sed -i 's/old/new/g' config.txtg这个字母必须记牢,它是“global”的缩写,意思是一行内全部替换。还有一个高频问题是忽略大小写,在替换命令末尾加i标志即可:
sed -i 's/error/故障/i' app.log4.2 精确寻址
sed 支持指定要处理的行号或范围,这在修改特定配置片段时非常有用。
只打印第5行到第10行:
sed -n '5,10p' file.txt-n是关闭默认输出,因为 sed 默认每行都输出,配合p手动打印指定范围内的内容,就是精准提取行段的正确姿势。
删除某个范围内的行:
sed -i '100,200d' large.log这个用法很适合清理日志中间的无效片段。基于正则寻址更灵活,比如找到“begin”到“end”中间的所有行:
sed -n '/begin/,/end/p' data.txt4.3 备份习惯
-i直接修改原文件,操作失误就没有后悔药了。养成备份习惯能少踩很多坑:
sed -i.bak 's/old/new/g' app.conf这样替换前会生成 app.conf.bak,出问题可以直接回滚。批量修改多个文件时也能这样操作。执行替换前先在无-i的情况下跑一遍,把输出重定向到临时文件,用diff对比改动,确认无误后再真正落地:
sed 's/old/new/g' app.conf > /tmp/app.conf.new diff app.conf /tmp/app.conf.new注意:不要把
-i和-n混在一起用于替换场景,-n会抑制输出,导致你无法看到替换结果。验证修改效果时,先单独跑sed不带-i看输出,再决定是否正式落盘。
5. awk 数据提取与统计
awk 严格来说不算命令,而是一门小巧的文本处理语言。它的核心模型是:按行读取,按列拆分,按条件处理。默认按连续空白字符拆分成列,用$1、$2引用第一列、第二列,$0是整行。
5.1 取列与分隔符
从访问日志中提取 IP 和状态码:
awk '{print $1, $9}' access.log默认分隔符是空白,遇到用逗号或冒号分隔的文件就要用-F指定:
awk -F':' '{print $1}' /etc/passwd如果是逗号分隔的 CSV,改成-F','。注意输出时默认用空格连接多个字段,想保留原始分隔符效果,可以用OFS变量:
awk -F',' 'BEGIN{OFS=","} {print $1, $2}' data.csvBEGIN块在 awk 读取文件之前执行,适合设置分隔符和初始化变量,能显著减少重复表达。
5.2 条件过滤与计数
awk 同样支持正则匹配和条件判断。统计 500 错误数量:
awk '$9 == 500 {count++} END {print count}' access.logEND块在所有数据处理完之后执行,适合输出统计结果。这里的count++就是典型的累加操作。
接口响应耗时超过 2 秒的请求数:
awk '$10 > 2 {slow++} END {print slow}' access.log多条件组合也能直接写:统计 GET 请求且状态码为 200 的数量:
awk '$1 == "GET" && $9 == 200 {cnt++} END {print cnt}' access.log如果 log 里访问量大的接口需要排行,可以按接口聚合:
awk '{print $7}' access.log | sort | uniq -c | sort -rn | head -105.3 数值求和与分组统计
统计响应体总大小:
awk '{sum += $10} END {print sum}' access.log统计每个接口的请求次数,用关联数组即可:
awk '{cnt[$7]++} END {for (path in cnt) print cnt[path], path}' access.log数组在 awk 中非常自然,键可以是任意字符串,这正是日志分组统计的高效写法。
5.4 常见误区
awk 的print $1, $2输出字段间默认是空格,不是原始分隔符。在替换或提取时如果发现输出格式变了,这就是原因。解决办法是显式设置OFS。
另一个常见误区是忘记条件表达式与字符串的比较。比如判断第一列是否等于 GET,必须加引号写作$1 == "GET",写成$1 == GET会把 GET 当成变量,得到空值,匹配结果全错。
提示:awk 读取大文件是边读边处理,非常省内存。但如果把每一行都存进数组不设清理机制,例如
{a[$1]=$2}这种,在超大文件中内存就可能被拖垮。合理评估数组使用场景,避免全量缓存无界增长。
6. 三个命令的组合实战
单一命令解决不了的问题,组合起来往往就是常规套路。以下三个场景是生产环境里最常见的标准用法。
6.1 日志分析组合套路
场景:从访问日志里找出所有 500 错误,提取出对应请求的 IP 和请求路径,再按 IP 统计次数。
分步执行是这样的:
第一步,先用 grep 过滤出 500 行,缩小范围:
grep " 500 " access.log > /tmp/500.log第二步,用 awk 提取关键字段,并做 IP 计数:
awk '{cnt[$1]++} END {for (ip in cnt) print cnt[ip], ip}' /tmp/500.log | sort -rn | head -20这里grep负责粗筛,awk负责精提取与统计,各干各的,逻辑清晰。
如果只是想快速瞄一眼哪些接口出问题最多:
grep " 500 " access.log | awk '{print $7}' | sort | uniq -c | sort -rnawk '{print $7}'提取出路径列,sort | uniq -c统计每个路径出现次数,sort -rn按次数倒序。这个四连管道用的频率极高,强烈建议背下来。
6.2 配置批量修改组合
批量替换配置文件里某段参数:
grep -rl "MemTotal" /etc/conf.d/ | xargs sed -i 's/oldSetting/newSetting/g'这里的grep -rl先定位哪些文件包含关键字,只输出文件路径,交给 xargs 传给 sed 做批量替换。相比自己一个个文件手改,效率提升明显,脚本化也更安全。
如果只想替换满足某个条件的行,sed 与 awk 可以交叉使用,例如只修改第200行到第500行之间的配置:
sed -i '200,500s/threads=2/threads=4/' app.conf6.3 实时查看与过滤
排查问题时经常会持续观察日志的输出,用tail -f配合 grep 实时过滤:
tail -f app.log | grep --line-buffered "ERROR"--line-buffered参数很关键,没有它 grep 的输出会因为管道缓冲而延迟,导致你无法实时看到新增的日志行。需要实时分析就用tail -f,不需要时就别用,避免长期占用文件句柄。
提示:
tail -f+ grep 在生产环境观察日志是标准组合,但要在排查结束时及时用 Ctrl+C 终止。如果不终止,终端会一直占用资源,一些谨慎的自动化平台甚至会因此判定有未退出进程。
7. 实战中的经验与避坑记录
每次写 Linux 命令相关的内容,都会强调几个反复遇到的坑,这三剑客的坑尤其集中。
第一,grep 搜索关键字时,如果关键字带点号或星号,一定要先问自己一句:这个符号是想让它作为正则生效,还是单纯就是个普通字符?如果是普通字符,加-F准没错。曾经有一次排查“订单号.gzip”格式的日志,直接用 grep 匹配出来的结果比实际多了整整一倍,就是漏了-F,把点号当成了任意字符。
第二,sed 的-i在部分环境中行为略有差异,有些版本要求必须显式指定备份后缀。稳妥起见,只要是重要配置文件,一律使用-i.bak方式执行,改错了还有一个兜底。生产环境的配置修改前先备份,这个习惯能救命的次数比想象中多。
第三,awk 里字符串比较一定要加引号。第一次写统计 GET 请求数量的脚本时,因为漏了一个引号,统计结果要么为0,要么等于总行数,逻辑完全乱了套。排查下来发现是变量和字符串混为一谈。从现在起养成习惯:$1 == "GET",引号一定不能省。
第四,同时处理多个文件时,awk 默认会把第一个文件的每一行后面加一个空行,多文件统计时要注意这一点。如果不想要这个行为,可以用FNR==1条件配合nextfile等方式调整。实际使用中我通常会把多个文件先用 cat 拼接成标准输入再交给 awk,避免多文件边界带来的隐藏问题:
cat access.log access.log.1 | awk '{print $1}' | sort | uniq -c第五,管道命令要有意识地控制数量。四连、五连的管道虽然看着潇洒,但每多一个管道就多一分缓冲开销。小文件无所谓,上百MB的大文件时管道缓冲对性能的影响不再可忽略。可以先落盘中间结果:
awk '{print $7}' access.log > /tmp/urls.txt sort /tmp/urls.txt | uniq -c | sort -rn | head -20分步执行还有一个好处:每一步结果可视化,排查逻辑出错时更容易定位是哪一步产生了脏数据。
8. 日常高频命令速查
整理成一张速查表,方便贴在终端旁边。不用刻意背,碰到场景来查一次,多用几次就记住了。
| 目标 | 命令 |
|---|---|
| 快速查找关键字并显示行号 | grep -n "key" file |
| 排除含关键字的行 | grep -v "key" file |
| 同时匹配多个模式 | grep -E "err1|err2" file |
| 显示匹配前后各3行上下文 | grep -C 3 "key" file |
| 只列含关键字的文件名 | grep -l "key" *.conf |
| 固定字符串匹配(不用正则) | grep -F "1.2.3" file |
| 无备份替换某关键字 | sed -i 's/old/new/g' file |
| 带备份替换(推荐) | sed -i.bak 's/old/new/g' file |
| 打印第5到10行 | sed -n '5,10p' file |
| 删除第100到200行 | sed -i '100,200d' file |
| 按冒号切分取第一列 | awk -F':' '{print $1}' file |
| 统计总行数 | awk 'END{print NR}' file |
| 按第一列分组统计次数 | awk '{c[$1]++} END{for(k in c) print c[k],k}' file |
| 统计数字列总和 | awk '{s+=$1} END{print s}' file |
速查表里不少组合其实是互相关联的,单独记参数难,配合实战场景去记就轻松得多。比如表格里的分组统计,本质上就是“先拆列,再入数组,最后遍历输出”三步走。把整个套路想明白,参数自然忘不了。
我个人平时用得最多的是先grep快速定位异常,再用awk提取字段并统计分布,必要时用sed做临时性修改,三者的边界清晰,组合起来基本不重样。工具的魅力不在于背下所有参数,而在于面对具体问题时能迅速找到正确的组合方式。这也是为什么文本处理能力永远是 Linux 使用者的基本功:不管上层技术怎么变,日志还是那些日志,文本还是那些文本,会处理文本的人永远多一张底牌。