线上改配置、批量处理日志,sed 一条命令顶一个脚本。但 sed 的坑也不少:忘了 g 只换了每行第一个、macOS 上 -i 直接报错、替换明明写对了却不生效……
10 个场景按真实踩坑频率排列,命令都在 GNU sed(Ubuntu)上逐条实测过,配套的完整命令清单见 sed 速查。
场景 1:忘了 g,每行只换了第一个
最常见的坑,没有之一:
echo "foo and foo" | sed 's/foo/bar/'
# bar and foo ← 第二个 foo 原地不动
s 命令默认只替换每行的第一个匹配。要全换必须加 g:
echo "foo and foo" | sed 's/foo/bar/g'
# bar and bar
场景 2:替换路径时满屏转义,换个分隔符
替换路径时用默认的 / 当分隔符,每个斜杠都要转义,可读性极差:
# 难读:sed 's/\/usr\/local\/bin/\/opt\/bin/g'
# 换 | 当分隔符,一目了然:
sed 's|/usr/local/bin|/opt/bin|g' app.conf
#、@、: 都能当分隔符,挑一个内容里不出现的就行。
场景 3:-i 直接改文件,改错没有后悔药
-i 是原地修改,改完原内容就没了。批量处理前留个备份:
sed -i.bak 's/foo/bar/g' app.conf # 原文件自动备份为 app.conf.bak
确认没问题再删 .bak。这条在改线上配置时救过不止一次场。
场景 4:macOS 上 sed -i 直接报错
同样的命令在 Linux 上好好的,到 macOS 报 invalid command code:
# macOS 的 BSD sed 要求 -i 必须跟备份后缀,空串表示不留备份:
sed -i '' 's/foo/bar/g' app.conf
跨平台脚本里这是高频炸点。要兼容两种系统,就统一写 -i.bak。
场景 5:只想看看匹配行,不想改文件
不用改文件时别把输出打到屏幕上滚动,-n 关掉默认输出、配合 p 只打印匹配行:
sed -n '/error/p' app.log # 等价 grep error app.log
再加行号更直观:grep -n 做不到的事见场景 7 的范围打印。
场景 6:删空行和注释,看配置清爽版
配置文件有效内容经常被几十行注释淹没:
sed '/^$/d' app.conf # 删空行
sed '/^#/d' app.conf # 删 # 开头的注释行
sed '/^$/d;/^#/d' app.conf # 两个条件一起(分号串联命令)
场景 7:只改某个范围,别动全文
替换整份文件风险大,sed 支持精确圈定范围:
sed '2,5s/foo/bar/g' app.conf # 只改第 2 到 5 行(闭区间)
sed '/BEGIN/,/END/s/foo/bar/g' nginx.conf # 只改 BEGIN 到 END 标记之间的行
按行号适合处理一次性的数据文件,按模式范围适合处理带标记块的配置。配合 -n '/BEGIN/,/END/p' 还能只把这一段打印出来看。
场景 8:替换明明对,就是不生效——是 \r 在作怪
从 Windows 传上来的脚本,sed 模式怎么写都匹配不上行尾。罪魁祸首是 CRLF 换行符里的 \r,它看不见但真实存在。让它现形:
sed -n 'l' deploy.sh
# 输出里行尾的 \r$ 就是证据(l 命令把不可见字符全部转义显示)
去掉它:
sed -i 's/\r$//' deploy.sh
脚本跨平台执行报 command not found 或 bad interpreter 时,先怀疑它。定时任务场景的类似坑见 crontab 脚本不记日志的排查。
场景 9:给匹配内容统一加前后缀
& 代表整个匹配串,加前缀后缀不用重复写匹配内容:
echo "error timeout refused" | sed 's/\w\+/[&]/g'
# [error] [timeout] [refused]
注意默认 BRE 模式下加号量词要写成 \+(直接写 + 会被当成字面字符,匹配不到任何东西——又一个「看起来对但不生效」的坑)。批量给日志关键词加标记、给变量名加引号都是这个套路。
场景 10:分组后向引用,交换两列
\(...\) 分组、\1 \2 引用,重排内容:
echo "api.example.com,8080" | sed 's/\(.*\),\(.*\)/\2 \1/'
# 8080 api.example.com
注意分组括号在默认的 BRE 模式里要写成 \( \)(和场景 9 的 \+ 是同一个规则:BRE 里这些符号不加反斜杠就是字面字符)。复杂的正则可以先到 正则在线测试 里验证,再贴进 sed。
小结
| 场景 | 一句话 |
|---|---|
| 替换 | 默认只换第一个,记得 g |
| 路径 | 换 ` |
| 原地改 | 用 -i.bak 留备份,兼容 macOS |
| 查看 | -n + p 只打印匹配 |
| 范围 | 行号 2,5 或模式 /BEGIN/,/END/ |
| 诡异不匹配 | 先 sed -n 'l' 查 \r |