简介这份PDF文档面向具备一定Linux基础、希望提升文本处理效率的技术人员系统梳理grep、sed、gawk三大命令行工具的使用方法。内容从正则表达式元字符集切入覆盖grep的基本集与扩展集、POSIX字符类及常用选项sed部分讲解定址功能、调用格式与删除、替换、多点编辑等流编辑操作gawk部分则展开字段处理、模式与动作、内部函数及控制结构帮助读者应对日志分析、配置文件批量修改和结构化数据统计等场景。资源包内仅含1个PDF文件大小约342KB篇幅紧凑、目录清晰按工具分章并配有大量命令实例便于对照练习与快速查阅。目前已有121人学习适合作为日常Shell文本处理的案头参考也可用于巩固正则表达式与自动化脚本编写能力。1. 三件套到底怎么分工从一次日志清洗翻车说起上周帮同事排查一个线上问题日志文件 2.3G他上来就cat access.log | grep 500结果终端卡死风扇狂转。我接手后换成grep -c先数数量再用sed -n按行号切片最后gawk做字段聚合三分钟出结果。这件事让我意识到很多人对 grep、sed、gawk 的认知停留在「grep 搜索、sed 替换、awk 打印列」这个层面真到处理大文件、多条件、跨行匹配时就抓瞎。这份文档把三个工具的选项、正则表达式语法和应用实例串在一起讲适合两类人一是刚接触 Linux Shell 命令、需要一套能照着敲的参考手册的新手二是天天跟日志、配置文件、CSV 打交道但每次写 sed 脚本都要现查 man 的运维和开发。它不教你写复杂脚本而是把每个选项的边界、正则的匹配规则、组合使用的套路讲透。下面我按「先分清职责、再逐个拆解、最后组合实战」的顺序把这份文档里最值得反复看的部分拎出来。2. grep 的匹配逻辑从基础选项到正则引擎选择2.1 三种正则模式与选项组合grep 的核心不是「搜索」而是「按什么规则匹配」。它支持三种正则引擎通过选项切换选项正则类型元字符示例适用场景默认BRE基本正则^ $ . * []简单字符串和行首行尾锚定-EERE扩展正则 ?() {}-PPCREPerl 兼容\d \w \s需要\d简写或环视时很多人写grep error|fail发现匹配不到就是因为默认 BRE 里|是普通字符必须加-E或转义成\|。我一般直接上-E少打反斜杠可读性也好。# 在 Nginx 日志里找 4xx 和 5xx 状态码用 -E 启用扩展正则 grep -E (4[0-9]{2}|5[0-9]{2}) access.log # 统计每个 IP 的请求次数先 grep 出有效行再交给 awk grep -E ^[0-9]\.[0-9]\.[0-9]\.[0-9] access.log | gawk {print $1} | sort | uniq -c | sort -rn | head -20第一行里4[0-9]{2}匹配 400-4995[0-9]{2}匹配 500-599两边空格是为了避免匹配到 URL 里的数字。第二行先用-E过滤出以 IP 开头的行再交给 gawk 取第一列最后排序统计。注意grep -E和egrep等价但egrep已标记废弃脚本里建议统一写grep -E。2.2 递归、排除与上下文控制处理代码仓库或配置目录时-r递归搜索是刚需但直接grep -r timeout /etc/会翻出一堆二进制文件和缓存。常见做法是配合--include和--exclude-dir# 只在 .conf 和 .yaml 文件里搜 timeout跳过 .git 和 cache 目录 grep -rn --include*.conf --include*.yaml --exclude-dir.git --exclude-dircache timeout /etc/ # 显示匹配行的前后 2 行上下文排查配置关联项 grep -n -C 2 worker_connections /etc/nginx/nginx.conf-n显示行号-r递归-C 2表示上下文各 2 行。这里有个血泪经验-C后面跟数字时不要加空格写成-C 2和-C2都可以但-C单独用会报错。另外--include可以写多次--exclude-dir也是顺序不影响结果。注意grep -r默认会跟随符号链接如果目录里有指向/的软链会直接扫全盘。加-r时最好确认目录结构或者用-R并配合--exclude-dir限制范围。2.3 退出码与静默模式在脚本中的用法grep 的退出码是脚本里做条件判断的关键0 表示匹配到1 表示没匹配到2 表示文件不存在或权限错误。很多人写if [ $(grep -c pattern file) -gt 0 ]其实直接用退出码更干净# 检查配置文件里是否启用了 gzip没启用就追加 if grep -q ^gzip on; /etc/nginx/nginx.conf; then echo gzip already enabled else echo gzip on; /etc/nginx/nginx.conf fi-q是静默模式不输出任何内容只返回退出码。这里用^gzip on;锚定行首避免匹配到注释行#gzip on;。如果配置文件里缩进不固定可以改成grep -qE ^\s*gzip\son;。注意-q和-c不要同时用-q会覆盖-c的输出。3. sed 的流编辑模式空间、保持空间与原地修改3.1 模式空间与保持空间的切换逻辑sed 处理文本的方式是「读一行、处理一行、输出一行」这个临时缓冲区叫模式空间。保持空间是另一块缓冲区用来暂存数据通过h、H、g、G、x命令在两者之间搬运。理解这两个空间是写复杂 sed 脚本的前提。命令作用方向h把模式空间内容覆盖到保持空间模式 → 保持H把模式空间内容追加到保持空间模式 → 保持g把保持空间内容覆盖到模式空间保持 → 模式G把保持空间内容追加到模式空间保持 → 模式x交换模式空间和保持空间双向一个典型场景把文件里所有行合并成一行用逗号分隔。常见做法是# 用 H 把每行追加到保持空间最后一行用 x 交换后替换换行符 sed -n 1h; 2,$H; ${x; s/\n/,/g; p} input.txt逐段拆解1h把第一行覆盖到保持空间2,$H从第二行到最后一行追加到保持空间${x; s/\n/,/g; p}在最后一行时交换模式空间和保持空间此时模式空间里是所有行的内容用s/\n/,/g把换行替换成逗号最后p打印。注意-n抑制默认输出否则每行都会打印一遍。3.2 地址定界与替换命令的精确控制sed 的地址定界决定了命令作用在哪些行上。常见形式有行号、行号范围、正则匹配、以及它们的组合# 只替换第 5 到第 10 行里的 foo 为 bar sed 5,10 s/foo/bar/g file.txt # 从匹配 start 的行到匹配 end 的行之间删除所有空行 sed /^start/,/^end/{/^$/d} file.txt # 替换时保留匹配内容的一部分用 \( \) 分组\1 引用 sed -E s/([0-9]{4})-([0-9]{2})-([0-9]{2})/\3\/\2\/\1/ date.txt第一个例子中5,10是行号范围s/foo/bar/g只在这个范围内生效。第二个例子用两个正则定界{/^$/d}是命令块表示在定界范围内删除空行。第三个例子用-E启用扩展正则([0-9]{4})捕获年份替换时\3\/\2\/\1把日期从YYYY-MM-DD转成DD/MM/YYYY。注意\/是转义斜杠因为分隔符是/也可以改用s|...|...|避免转义。3.3 原地修改与备份后缀的坑sed -i直接修改文件是运维脚本里最常用的选项但也是最容易翻车的地方。GNU sed 和 BSD sedmacOS 自带对-i的参数处理不同# GNU sed-i 后面直接跟备份后缀不跟空格 sed -i.bak s/old/new/g config.conf # BSD sed-i 后面必须跟一个参数空字符串表示不备份 sed -i s/old/new/g config.conf在 Linux 上写sed -i s/old/new/会报错因为 GNU sed 把当成文件名。跨平台脚本里常见做法是用变量判断或者统一用-i.bak生成备份。另外-i会直接覆盖原文件如果替换规则写错没有备份就只能从版本控制里恢复。我一般会先sed s/old/new/g config.conf | diff - config.conf确认差异再执行-i。注意sed -i处理大文件时会在临时目录生成副本如果/tmp空间不足会失败。处理超过 1G 的文件时先确认df -h /tmp有足够空间或者改用sed s/old/new/g file file.tmp mv file.tmp file。4. gawk 的字段处理从内置变量到数组统计4.1 内置变量与字段分隔符gawk 的核心是「按字段处理」默认以空格或制表符分隔$1是第一列$0是整行NF是字段数NR是当前行号。改分隔符用-F或BEGIN{FS...}# 处理 CSV取第 2 列和第 5 列用逗号分隔 gawk -F, {print $2, $5} data.csv # 处理 /etc/passwd取用户名和 UID用冒号分隔 gawk -F: {print $1, $3} /etc/passwd # 用正则作为分隔符匹配一个或多个空格或制表符 gawk -F[ \t] {print $1, $NF} access.log第一个例子-F,指定逗号分隔$2和$5是第 2、5 列。第二个例子处理 passwd 文件$1是用户名$3是 UID。第三个例子用正则[ \t]匹配连续空白$NF是最后一列。注意-F后面跟的是正则表达式不是普通字符串所以-F.会匹配任意字符要匹配点号得写-F\\.。4.2 条件判断与统计聚合gawk 的if、for、数组是做日志统计的利器。比如统计 Nginx 日志里每个状态码的出现次数# 统计状态码分布END 块里遍历数组输出 gawk {status[$9]} END{for (code in status) print code, status[code]} access.log # 按小时统计请求量先提取时间字段的小时部分 gawk -F[: ] {hour[$5]} END{for (h in hour) print h, hour[h]} access.log第一行status[$9]用第 9 列作为数组下标每次出现自增。END块在所有行处理完后执行for (code in status)遍历数组。第二行用-F[: ]把冒号和空格都当分隔符Nginx 日志的时间格式是[dd/Mon/YYYY:HH:MM:SS按冒号和空格切分后$5是小时部分。注意 gawk 数组遍历顺序是随机的如果要排序输出得用asorti或者管道接sort。4.3 多文件处理与 FNR/NR 的区别处理多个文件时NR是累计行号FNR是当前文件的行号。这个区别在合并文件或按文件分别处理时很关键# 处理两个文件在第二个文件开头插入分隔标记 gawk FNR1 NR!1 {print --- FILENAME ---} {print} file1.txt file2.txt # 分别统计每个文件的行数 gawk END{print FILENAME, FNR} file1.txt file2.txt第一行FNR1 NR!1表示「当前文件的第一行且不是全局第一行」也就是第二个及以后文件的开头插入文件名标记。FILENAME是当前文件名。第二行在END块里打印FILENAME和FNR但END只在所有文件处理完后执行一次所以只会输出最后一个文件的信息。要分别统计得用FNR1时重置计数器或者用 shell 循环逐个文件调用 gawk。注意gawk 的print默认用空格分隔字段OFS变量控制输出分隔符。如果输出要导入 CSV记得BEGIN{OFS,}否则列会错位。5. 三件套组合实战日志清洗与配置批量修改5.1 日志清洗流水线把 grep、sed、gawk 串起来可以搭一条日志清洗流水线。假设要从 Nginx 日志里提取「响应时间超过 1 秒的请求按 URL 聚合输出平均响应时间」# 提取响应时间 1 的请求按 URL 聚合求平均 grep -E [0-9]{4} access.log \ | gawk $NF 1.0 {sum[$7]$NF; count[$7]} END{for (url in sum) printf %s %.3f\n, url, sum[url]/count[url]} \ | sort -k2 -rn \ | head -20第一段grep -E [0-9]{4} 过滤出状态码为 4xx 或 5xx 的行减少后续处理量。第二段 gawk 里$NF是最后一列响应时间$7是 URLsum[$7]$NF累加响应时间count[$7]计数END块里算平均值。第三段sort -k2 -rn按第二列数值降序排head -20取前 20。注意printf %.3f保留三位小数避免输出一长串浮点数。5.2 配置文件批量修改批量修改多台机器的配置常见做法是用 sed 做模板替换grep 做校验# 把所有 .conf 文件里的 max_connections 从 100 改成 500 for f in /etc/app/*.conf; do sed -i.bak s/^max_connections\s*\s*100/max_connections 500/ $f grep -q ^max_connections 500 $f echo $f updated || echo $f failed done循环里先用sed -i.bak原地替换并生成.bak备份正则^max_connections\s*\s*100匹配行首、可选空白、等号、可选空白、100。替换后立刻用grep -q校验是否改成功成功打印 updated失败打印 failed。这里\s在 GNU sed 里支持但 BSD sed 不支持跨平台要写成[[:space:]]*。5.3 用 gawk 做多条件关联分析grep 和 sed 擅长单行处理跨行关联得靠 gawk 的数组。比如分析日志里「同一个 IP 在 5 分钟内连续失败 3 次以上」# 提取失败登录记录按 IP 和时间窗口聚合 grep Failed password /var/log/secure \ | gawk { ip $(NF-3) time $1 $2 $3 key ip if (key in last_time) { diff (mktime(time) - mktime(last_time[key])) if (diff 300) count[key] else count[key] 1 } else { count[key] 1 } last_time[key] time if (count[key] 3) print ip, count[key], time }这段脚本用mktime把时间字符串转成时间戳last_time数组记录每个 IP 上次失败时间count数组记录窗口内失败次数。如果两次失败间隔小于 300 秒计数加一否则重置为 1。当计数达到 3 时输出。注意mktime需要YYYY MM DD HH MM SS格式而 secure 日志的时间格式是Mon DD HH:MM:SS实际使用时得先补全年份或者用date -d转换。这个例子更多是展示 gawk 做关联分析的思路真实场景里我会先用sed把时间格式统一。6. 正则调优与验证从能跑到跑得稳6.1 正则性能的三个注意点正则写不好轻则匹配错误重则 CPU 跑满。三个最常见的性能坑坑点错误写法改进写法原因贪婪匹配回溯.*foo.*bar[^f]*foo[^b]*bar.*会反复回溯否定字符类不回溯未锚定行首error^.*error或^error未锚定会逐字符尝试匹配嵌套量词(a)bab嵌套量词导致指数级回溯第一行.*foo.*bar在长行里会先吞掉整行再回溯找 foo用[^f]*限制字符范围能减少回溯。第二行如果 error 出现在行首加^锚定能直接跳过不匹配的行。第三行(a)b是经典的灾难性回溯改成ab语义相同但性能差几个数量级。6.2 用 grep -o 和 sed 验证匹配结果写完正则别急着上生产先用grep -o把匹配部分单独打印出来确认边界# 提取所有 IPv4 地址-o 只输出匹配部分 grep -oE ([0-9]{1,3}\.){3}[0-9]{1,3} access.log | sort -u | head # 用 sed 把匹配部分替换成标记确认替换范围 sed -E s/([0-9]{1,3}\.){3}[0-9]{1,3}/[IP]/g access.log | head -5第一行-o只输出匹配到的 IPsort -u去重head看前几个。第二行把 IP 替换成[IP]打印前 5 行肉眼确认替换位置是否正确。注意([0-9]{1,3}\.){3}会匹配999.999.999.999这种非法 IP如果需要严格校验得用更复杂的正则或者交给 gawk 做数值判断。6.3 一个我常备的调试习惯每次写超过 10 个字符的正则我都会先在一个小样本文件上跑三遍第一遍grep -c看匹配数量是否符合预期第二遍grep -o看匹配内容边界对不对第三遍sed替换后diff看改动范围。这个习惯帮我省过好几次回滚。从那以后我每次写 sed 或 gawk 脚本都强制先sed s/.../.../ file | diff - file确认差异再决定要不要-i。希望帮到你。本文还有配套的精品资源点击获取