Appearance
grep与sed
行文本过滤和批量替换是 Shell 中的高频操作,grep 负责匹配查找,sed 负责流式编辑。
一、grep 的定位
grep(Global Regular Expression Print)从文本输入中筛选匹配指定模式的行。它可以读取文件,也接受管道输入。
bash
grep "ERROR" app.log
journalctl -u nginx --since "1 hour ago" | grep "failed"常用参数:
| 参数 | 作用 |
|---|---|
-i | 忽略大小写 |
-n | 显示匹配行的行号 |
-v | 反向匹配——输出不包含匹配内容的行 |
-r | 递归搜索目录下的所有文件 |
-E | 使用扩展正则表达式(支持 +、?、| 等) |
-F | 按普通字符串匹配(不解释正则特殊字符) |
-q | 静默模式,不输出任何内容,只看退出码 |
-A N | 显示匹配行后面 N 行 |
-B N | 显示匹配行前面 N 行 |
-C N | 显示匹配行前后各 N 行 |
查错误并显示行号:
bash
grep -n "ERROR" app.log查看错误上下文(前后 3 行):
bash
grep -C 3 "OutOfMemory" app.log脚本中只判断是否存在匹配而不输出内容:
bash
if grep -q "ERROR" app.log; then
echo "found error"
fi二、普通字符串和正则
很多场景下只是查找固定的字符串,用 -F 更直接——它不解释正则特殊字符:
bash
grep -F "10.0.0.1" access.log
grep -F "[ERROR]" app.log[、.、* 等字符在正则中有特殊含义,用 -F 之后就不需要逐个转义。
扩展正则用 -E:
bash
grep -E "ERROR|WARN|FATAL" app.log常用正则语法(配合 -E):
| 写法 | 含义 |
|---|---|
^ | 行首 |
$ | 行尾 |
. | 匹配任意单个字符 |
* | 前一字符重复 0 次或多次 |
+ | 前一字符重复 1 次或多次(需要 -E) |
? | 前一字符出现 0 或 1 次(需要 -E) |
[] | 字符集合,[0-9] 匹配一位数字 |
| | 或,多个模式取其一(需要 -E) |
粗略匹配 IPv4 地址:
bash
grep -E '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+' access.log这个正则只按格式过滤,不校验每段是否在 0–255 范围内。日志临筛够用,严格校验时需要更精确的工具。
三、grep 的退出码
grep 的退出码含义清晰,适合写入条件逻辑:
| 退出码 | 含义 |
|---|---|
0 | 至少找到一处匹配 |
1 | 未找到任何匹配 |
2 | 发生错误(文件不存在、目录无法读取等) |
在 set -e 脚本中处理 grep 无匹配的返回值:
bash
grep "ERROR" app.log || true # 允许 "没有匹配到" 不算致命错误|| true 只应在确实允许为空的地方使用。盲目加会连真正的错误(如文件不存在、退出码 2)也吞掉。更稳妥的做法是用 if 包裹。
四、sed 的定位
sed(Stream Editor)按行读取、按指令处理、再输出结果。默认不改动原文件,结果打印到标准输出。
打印指定行范围:
bash
sed -n '1,5p' app.log # 只输出第 1 到 5 行删除空行:
bash
sed '/^$/d' app.log # 删除匹配行,输出到屏幕,不修改原文件替换文本:
bash
sed 's/old/new/' app.conf # 替换每行第一个匹配
sed 's/old/new/g' app.conf # 替换每行所有匹配(g = global)s/old/new/ 只影响每行的第一个匹配,末尾加 g 才会替换整行中所有出现的 old。
五、sed 的行地址
sed 命令前可以加"地址"来限制只在匹配的行上执行操作:
| 地址写法 | 含义 |
|---|---|
1p | 第 1 行 |
1,10p | 第 1 到 10 行 |
/ERROR/p | 包含 ERROR 的行 |
$p | 最后一行 |
只看第 10 到 20 行:
bash
sed -n '10,20p' app.log只在匹配行上执行替换:
bash
sed '/server_name/s/example.com/api.example.com/' nginx.conf这个写法先找到包含 server_name 的行,再在这行里把 example.com 替换成 api.example.com。sed 不识别配置文件的结构语义——它只做纯文本匹配。多层缩进的 YAML、JSON 配置不适合用 sed 深度修改。
六、原地修改
Linux 上 sed 的 -i 参数支持"原地修改"文件(而不是输出到屏幕)。
bash
sed -i.bak 's/listen 80/listen 8080/' nginx.conf-i.bak 表示修改原文件前,先备份为 nginx.conf.bak。裸用 sed -i 不留备份,生产线操作风险较高。
批量替换目录中的多个文件:
bash
find /etc/nginx -type f -name "*.conf" -print0 |
while IFS= read -r -d '' file; do
sed -i.bak 's/old.example.com/new.example.com/g' "$file"
done批量修改配置后,马上跑服务自带的语法检查(如 nginx -t、sshd -t)确认没有语法错误再继续。
七、常见文本处理模式
去掉注释行和空白行:
bash
grep -v '^[[:space:]]*#' app.conf | grep -v '^[[:space:]]*$'[[:space:]] 是 POSIX 字符类,匹配空格和 Tab,比 \s 跨平台兼容性更好。
替换整行配置(匹配到行后整行覆盖):
bash
sed -i.bak 's/^worker_processes.*/worker_processes auto;/' nginx.conf在匹配行后面追加一行内容:
bash
sed '/http {/a\ server_tokens off;' nginx.conf多个 sed 操作用分号分隔,也可以写多个 -e。逻辑变复杂时写成独立的 sed 脚本文件,比一行长命令更便于维护和回滚。
八、使用边界
grep 和 sed 适合的场景:
| 场景 | 工具 |
|---|---|
| 从日志中过滤关键字 | grep |
| 查看匹配行的上下文 | grep -C |
| 简单替换一行配置 | sed |
| 批量删除注释和空行 | sed / grep |
| 批量文本替换 | sed -i.bak |
不适合的场景和替代方案:
| 场景 | 推荐工具 |
|---|---|
| 修改 JSON 结构 | jq |
| 修改 YAML 配置 | yq 或配置管理工具 |
| 解析多层嵌套的日志字段 | awk、Python 或日志平台 |
| 涉及上下文状态的复杂文本处理 | Python、Go 等通用语言 |
grep 和 sed 是单行处理工具,不关心上下文结构。当需要理解文件语法(缩进、嵌套、引用关系)时,用能解析该格式的专用工具会更准确。