文本三剑客之 sed:流编辑器与批量替换
sed(Stream EDitor,流编辑器)解决"改"的问题:批量替换配置里的域名、删掉文件的某些行、给每行加前缀——不打开编辑器,一条命令完成。它的工作模型很简单:逐行读入 → 按脚本处理 → 打印结果,原文件默认不动(除非 -i)。
1. s 命令:替换
sed 使用率 90% 以上的功能就是 s(substitute):
sed 's/旧/新/' file # 每行替换第一处匹配
sed 's/旧/新/g' file # g 标志:每行替换全部匹配
sed 's/旧/新/2' file # 只替换每行的第 2 处
sed 's/旧/新/gi' file # i 标志:忽略大小写cat > greet.txt << 'EOF'
hello world, hello linux
HELLO again
goodbye world
EOF
echo '$ sed "s/hello/hi/" greet.txt # 每行只换第一处'
sed 's/hello/hi/' greet.txt
echo '$ sed "s/hello/hi/g" greet.txt # 全部换'
sed 's/hello/hi/g' greet.txt
echo '$ sed "s/hello/hi/gi" greet.txt # 大小写通吃'
sed 's/hello/hi/gi' greet.txt
echo '$ cat greet.txt # 原文件没有变!sed 默认只改输出流'
cat greet.txt模式部分是正则表达式(加 -E 用扩展正则,与 grep -E 同一套)。分隔符不是必须用 /——处理路径、URL 时换个分隔符可读性天差地别:
sed 's/\/var\/www\/old/\/var\/www\/new/' cfg # 斜杠地狱,别这么写
sed 's#/var/www/old#/var/www/new#' cfg # 用 # 做分隔符,清爽
sed 's|http://old.com|https://new.com|g' cfg # 用 | 也行2. 地址:只处理某些行
sed 命令前可以加"地址"限定作用范围,形式是行号或正则:
sed '3s/a/b/' file # 只在第 3 行替换
sed '2,5s/a/b/g' file # 第 2 到 5 行
sed '/^#/s/a/b/' file # 只在注释行(# 开头)里替换
sed '/BEGIN/,/END/s/a/b/' # 从匹配 BEGIN 的行到匹配 END 的行
sed '$s/a/b/' file # $ 表示最后一行3. d 删除与 p 打印
sed '/^#/d' app.conf # 删除所有注释行
sed '/^$/d' app.conf # 删除所有空行
sed '1d' file # 删除第一行(如去掉 CSV 表头)
sed -n '5,8p' file # -n 关闭默认输出,p 打印:只输出 5~8 行
sed -n '/ERROR/p' file # 等价于 grep ERROR-n 加 p 的组合要理解:sed 默认把每行处理完都打印一遍,-n 关掉这个默认行为,只有被 p 命令点名的行才输出——这就变成了"精确取行"工具,比 head+tail 组合更直接。
cat > app.conf << 'EOF'
# main config
port = 8080
# database settings
db_host = localhost
db_port = 5432
# cache
cache_ttl = 300
EOF
echo '$ sed "/^#/d" app.conf # 去注释行'
sed '/^#/d' app.conf
echo '$ sed "/^#/d; /^$/d" app.conf # 分号串联多个命令:再去空行'
sed '/^#/d; /^$/d' app.conf
echo '$ sed -n "4,6p" app.conf # 只看 4 到 6 行'
sed -n '4,6p' app.conf
echo '$ sed -n "/^db_/p" app.conf # 只看 db 开头的配置项'
sed -n '/^db_/p' app.conf4. -i:原地编辑
前面所有例子都只改输出、不动原文件。加 -i(in-place)直接写回:
sed -i 's/8080/9090/' app.conf # 直接修改文件
sed -i.bak 's/8080/9090/' app.conf # 修改前先留一份 app.conf.bak(推荐)
sed -i '/^debug/d' /etc/app/*.conf # 批量修改多个文件printf 'env = dev\nport = 8080\nhost = dev.example.com\n' > app.conf
echo '$ sed -i.bak "s/dev/prod/g" app.conf'
sed -i.bak 's/dev/prod/g' app.conf
echo '--- 修改后的 app.conf ---'
cat app.conf
echo '--- 自动备份 app.conf.bak ---'
cat app.conf.bak
# 经典组合:find + sed 批量改整个项目
mkdir -p srcs
echo 'api = http://old-api' > srcs/a.cfg
echo 'cdn = http://old-cdn' > srcs/b.cfg
find srcs -name '*.cfg' -exec sed -i 's|http://old|https://new|' {} +
echo '--- 批量替换后 ---'
cat srcs/a.cfg srcs/b.cfgLinux(GNU sed):sed -i 's/a/b/' f 直接可用,-i.bak 表示留备份。macOS(BSD sed):-i 后面必须跟备份后缀参数,不留备份要写 sed -i '' 's/a/b/' f(注意空字符串)。跨平台脚本要么统一装 gsed,要么用 sed 's/a/b/' f > tmp && mv tmp f 的通用写法。本课沙箱是 GNU sed。
5. 进阶一瞥:分组回引与 &
替换部分可以引用模式里捕获的内容:& 代表整个匹配,\1 \2 代表 -E 模式下第 1、2 个括号分组:
# & :给每个数字加上方括号
echo 'port 8080 and 9090' | sed -E 's/[0-9]+/[&]/g'
# 输出:port [8080] and [9090]
# \1 \2 :调换 "key = value" 为 "value = key"
echo 'name = ada' | sed -E 's/(\w+) = (\w+)/\2 = \1/'
# 输出:ada = name
# 实战:从 nginx 日志行提取 IP(把整行替换成第一个字段)
echo '10.2.3.4 - - [31/Jul/2026] "GET /"' | sed -E 's/^([0-9.]+) .*/\1/'
# 输出:10.2.3.4sed 还有追加(a)、插入(i)、行内换行处理等更多命令,但工作中"替换 + 删除 + 取行"三板斧覆盖了绝大多数需求;更复杂的字段级逻辑,下一章的 awk 更合适。
6. sed 与 grep 的配合分工
| 需求 | 工具 |
|---|---|
| 找出包含 X 的行 | grep |
| 把 X 换成 Y | sed s |
| 删掉某些行 / 取某区间 | sed d / sed -n p |
| 按列取值、算数、统计 | awk(下一章) |
管道里三者常串着用:grep 先缩小范围 → sed 清洗格式 → awk 统计,第 20 章会看到完整流水线。
小结
- 模型:逐行读入、处理、输出;默认不修改原文件
s/旧/新/g是绝对主力;分隔符可换成#|避免斜杠地狱;-E用扩展正则- 地址限定范围:行号、
2,5、/正则/、$(末行) /^#/d删注释、/^$/d删空行、-n '5,8p'精确取行-i原地改,建议-i.bak留备份;注意 macOS 的 BSD sed 语法差异&与\1分组回引,能做"提取重排"类改写- 下一章:三剑客之 awk——按列处理与统计报表 →
- 造一个含注释、空行的配置文件,用一条 sed 同时删掉注释行和空行,并把
localhost替换为127.0.0.1(提示:分号串联三个命令)。 - 用
seq 10生成 10 行数字,分别用sed -n '3,6p'和head -n 6 | tail -n 4取第 3~6 行,确认结果一致。 - 用
sed -i.bak把某文件里所有http://改为https://(用|做分隔符),再 diff 修改后文件与 .bak 备份查看变化。 - 挑战:
echo 'user=ada id=42',用sed -E加分组回引改写成id=42 user=ada。