shell04-sed 流编辑器
sed 流编辑器
目录
- sed 的定位:三剑客中负责「编辑」
- sed 的基本工作方式
- sed 寻址:只处理指定的行
- sed 替换:s 命令
- sed 删除与增插:d / a / i / c
- sed 就地修改:-i 与备份
- sed 多命令与脚本文件
- sed 常用场景实战
- sed 与 grep、awk 的分工协作
- 常见坑与技巧
- 总结与练习
- 参考资料
0. sed 的定位:三剑客中负责「编辑」
第 3 篇讲了 grep——它的本事是筛行(把匹配的行挑出来)。但很多时候你不只要「挑出来」,还要改:把配置文件里的某个值换成另一个、删掉几行、在某行后面插入新内容。这就是 sed 的活。
文本三剑客分工:
- grep:筛行(找出匹配的行);
- sed:编辑(替换、删除、增插、按行定位);
- awk:处理(按列提取、统计)。
sed 的典型用途:
- 批量替换配置文件里的内容;
- 删除某几行、在某行后插入新内容;
- 按模式定位并修改。
一句话:sed = 逐行读入 + 按规则「编辑」每行 + 输出。
一、sed 的基本工作方式
1.1 什么是流编辑器
sed(stream editor,流编辑器)。「流」是指它处理文本的方式:像流水线一样,一行接一行地读入、处理、输出,不一次性把整个文件都读进内存。它的工作方式是一条流水线:
逐行读入 → 应用编辑命令 → 输出结果
理解「流」很关键:sed 是逐行处理的,每读入一行就执行一次命令,处理完输出,再读下一行。因为不用把整个大文件载入内存,所以即使处理几十 GB 的日志也不怕。
默认情况下,sed 不会修改原文件,只是把处理后的结果输出到标准输出。要真正改原文件,必须用 -i(第 5 章讲)。
1.2 基本语法
sed '命令' 文件
sed 的命令由两部分组成:寻址(可选)+ 动作。
- 寻址:指定「处理哪些行」。不写就表示「所有行」;
- 动作:对选中的行做什么(替换 s、删除 d、增插 a/i 等)。
这是理解 sed 的核心框架:先选出要处理的行,再决定怎么处理。
1.3 第一个例子:什么都不改
sed -n 'p' file # 打印每行(-n 抑制默认输出,只输出指定的)
这里 p 是「打印」命令,-n 是「安静模式」(suppress),它会关闭 sed 默认的输出。为什么需要 -n?因为 sed 默认会把每一行都打印出来(除非它被命令修改或删除);加了 p 命令又会再打印一遍,于是每一行会被打印两次。用 -n 关掉默认输出,就只看到 p 明确要打印的内容。所以 -n 配合 p 是「只输出我想看的内容」的标准组合,后面会反复用到。
二、sed 寻址:只处理指定的行
寻址决定 sed 「对哪些行」执行后面的动作。不写寻址时,对所有行执行;写了寻址,就只对选中的行执行。这是 sed 最常用的能力之一。
2.1 按行号寻址
最直接的寻址方式是行号:
sed -n '3p' file # 只打印第 3 行
sed -n '2,5p' file # 打印第 2 到第 5 行
sed -n '3,8p' file # 打印第 3 到第 8 行
sed '3,8d' file # 删除第 3 到第 8 行(其余输出)
'3p':寻址是「第 3 行」,动作是p(打印);'2,5p':寻址是「第 2 到第 5 行」用逗号表示范围,动作还是打印;'3,8d':寻址第 3-8 行,动作d(删除)——被删的行不输出,其余照常输出。
注意 '3,8d' 没加 -n,所以未被删除的行会正常输出。
2.2 按模式寻址
除了行号,更常用的是用正则模式定位「匹配的行」。模式用 /正则/ 包起来:
sed -n '/error/p' file # 打印包含 error 的行(类似 grep)
sed -n '/^#/p' file # 打印以 # 开头的行
sed '/error/d' file # 删除包含 error 的行
'/error/p':对「包含 error 的行」执行p——这跟grep error效果一样;'/^#/p':匹配「以 # 开头」的行(第 3 篇的正则语法在这里同样适用);'/error/d':删除所有包含 error 的行。
模式寻址的强大之处在于:不用事先知道行号,让 sed 自己去找。这对处理内容不断变化的文件尤其有用。
2.3 模式范围:匹配两处之间的行
有时你想处理「从某个模式开始、到另一个模式结束」之间的一整段。用逗号连接两个模式:
# 从匹配 start 的行,到匹配 end 的行,这之间的所有行
sed -n '/start/,/end/p' file
'/start/,/end/p' 表示:从「匹配 start 的那行」开始,一直到「匹配 end 的那行」为止,打印这一整段。这在提取日志里某次会话、配置文件里某一段时非常好用。
2.4 取反:!
在寻址后加 ! 表示「不匹配这些行」:
sed -n '3!p' file # 打印除了第 3 行以外的所有行
sed '/error/!d' file # 删除不包含 error 的行(即只保留含 error 的)
'3!p' 是「除了第 3 行以外的都打印」;'/error/!d' 是「删除不包含 error 的行」——结果等于「只保留含 error 的行」,也就是反向的 grep。
三、sed 替换:s 命令
替换是 sed 最常用的功能。格式是:
sed 's/旧/新/标志' file
s 是 substitute(替换)命令,用「旧」去匹配,找到后替换成「新」;最后的「标志」控制替换的范围和行为。
3.1 基本替换
# 把每行第一个 "old" 替换成 "new"
sed 's/old/new/' file
# 全局替换(每行所有匹配)
sed 's/old/new/g' file
# 忽略大小写
sed 's/old/new/gi' file
- 默认(不加标志)只替换每行第一个匹配;
g(global)替换每行所有匹配——这是最常用的;i忽略大小写,可以和g连写成gi。
3.2 只替换第 n 个匹配
# 只替换每行第 2 个匹配
sed 's/old/new/2' file
标志 2 表示只替换每行的第 2 个匹配。这个用得少,但知道有这回事。
3.3 结合寻址:只替换某几行
替换命令前面也可以加寻址,限定「只对哪些行做替换」:
# 只替换第 3 行
sed '3s/old/new/' file
# 只替换第 2 到第 5 行
sed '2,5s/old/new/g' file
# 只替换匹配 pattern 的行
sed '/pattern/s/old/new/g' file
'3s/...' 是「第 3 行」+ s 替换;'2,5s/.../g' 是「第 2-5 行」+ 全局替换;'/pattern/s/.../g' 是「匹配 pattern 的行」+ 全局替换。把寻址和替换组合起来,就能做到「只改特定范围」。
3.4 用其他分隔符
当内容里含很多 /(比如路径)时,用 / 当分隔符会写出一堆转义。这时可以换分隔符,比如 #、@:
# 替换路径,用 # 当分隔符避免转义斜杠
sed 's#/usr/local#/opt#g' file
sed 's@old@new@g' file
s#/usr/local#/opt#g 用的是 # 作分隔符,所以路径里的 / 不用转义,直接写。用 @ 同理。规则:s 后面跟的任意字符都能当分隔符,选一个不在内容里出现的即可。
3.5 正则替换与捕获分组
s 命令里可以用正则(第 3 篇学的那套),包括捕获分组 (...) 和引用 \1:
# 把 "name: xxx" 改成 "name=xxx"
sed -E 's/name: ([a-z]+)/name=\1/' file
# 交换两个字段
sed -E 's/([^:]+):([^:]+)/\2:\1/' file
第一个:([a-z]+) 捕获名字,\1 在替换部分引用它,于是 name: tom 变成 name=tom。第二个:([^:]+) 捕获冒号前的部分、([^:]+) 捕获冒号后的部分,替换成 \2:\1 就把两段交换了(a:b → b:a)。
用
sed -E(扩展正则,类似 grep -E)能少写很多反斜杠。分组()、引用\1、量词+在 -E 下更自然。
3.6 只打印被替换的行
配合 -n 和 p 标志,可以只输出「发生了替换」的行:
sed -n 's/old/new/p' file # 只打印发生了替换的行
p 作为替换的标志,意思是「如果发生了替换,就把这行打印出来」。加上 -n 关闭默认输出,就只剩发生替换的行了。
四、sed 删除与增插:d / a / i / c
除了替换,sed 还能删除、追加、插入、整行替换。这四个命令按「对行做什么」区分。
4.1 删除:d
d(delete)删除选中的行。被删的行不输出,其余照常:
sed '3d' file # 删除第 3 行
sed '2,5d' file # 删除第 2-5 行
sed '/error/d' file # 删除包含 error 的行
sed '/^#/d' file # 删除注释行
最后一个 '/^#/d' 删除所有以 # 开头的行(注释行),是「清洗配置文件」的常用手法。
4.2 追加:a
a(append)在某行之后追加内容:
# 在第 3 行后追加一行
sed '3a new-content' file
# 在匹配 pattern 的行后追加
sed '/pattern/a new-content' file
'3a new-content' 是在第 3 行后面加上一行 new-content;'/pattern/a ...' 是在每个匹配 pattern 的行后面追加。
4.3 插入:i
i(insert)和 a 相反,在某行之前插入内容:
sed '3i new-content' file # 第 3 行前插入
sed '/pattern/i new-content' file
4.4 修改行:c
c(change)用新内容替换匹配的整行:
sed '3c replaced-line' file # 把第 3 行替换为 replaced-line
sed '/pattern/c replaced-line' file
注意 c 是替换「整行」,而 s 是替换「行里的某段内容」——两者作用范围不同。
4.5 追加多行
a/i/c 追加多行时,用反斜杠 \ 在行尾续行:
sed '3a\
line1\
line2' file
这里在 3a 后面写 \ 表示「下一行继续是追加内容」,于是第 3 行后面会追加两行(line1、line2)。i、c 同理。实际在脚本里内容较多时,更常写成多行脚本(见第 6 章)。
五、sed 就地修改:-i 与备份
5.1 -i:直接修改原文件
前面所有例子,sed 都只是把处理结果输出到屏幕,原文件没变。要直接改原文件,用 -i(in-place,就地):
sed -i 's/old/new/g' file # 直接修改 file
-i 让 sed 把处理后的结果写回原文件。这是「改配置」的主力用法,但也因此有覆盖风险。
危险:-i 会覆盖原文件,改错了无法恢复。改前务必先备份。
5.2 -i.bak:改前留备份(强烈推荐)
为了防「改错无法恢复」,可以这样写:
# 修改前先备份为 file.bak,再改 file
sed -i.bak 's/old/new/g' file
-i.bak 的意思是:修改前先把原文件复制一份为 file.bak,然后再改 file。如果改错了,可以从 file.bak 恢复。生产环境改配置一律 -i.bak,这是必须养成的习惯。
5.3 完整安全改配置的套路
改系统配置(比如 nginx)时,不要只执行一条 sed,而要走完整的「备份-修改-校验-重载」流程:
# 1. 备份
cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak.$(date +%F)
# 2. 修改
sed -i 's/worker_processes 1/worker_processes 4/' /etc/nginx/nginx.conf
# 3. 校验语法
nginx -t
# 4. 重载生效
systemctl reload nginx
这四步是「改配置的标准动作」,每一步都不可少:备份让你能回退;修改用 -i 就地写入;校验(nginx -t)确认改完语法没问题,有问题就立刻回退;重载(systemctl reload)平滑生效而不用重启服务。改任何关键服务配置,都建议走这个套路。
六、sed 多命令与脚本文件
6.1 一个 sed 里执行多条命令
一个 sed 命令通常只做一个操作。要同时做多个操作(比如先替换 A 再替换 B),有两种写法:
# 分号分隔
sed 's/a/b/g; s/c/d/g' file
# -e 指定多条
sed -e 's/a/b/g' -e 's/c/d/g' file
- 第一种用分号
;把多条命令塞进一个引号里; - 第二种用
-e每指定一条命令写一个-e,更清晰、也方便一条条加。
两条命令是按顺序依次执行的:先 s/a/b/g,再 s/c/d/g。命令多、顺序重要时,-e 的写法更直观。
6.2 从文件读命令
命令太多时(比如一堆替换规则),可以把它们写进一个文件,用 -f 引用:
sed -f commands.sed file
commands.sed 里每行写一条命令。这样把「处理规则」从「被处理的文件」里分离出来,规则可以复用、也更容易维护。
6.3 示例:多条替换
改配置时经常要一次改多处,用 -e 串起来:
sed -i.bak -e 's#listen 80#listen 8080#' -e 's/server_name _/server_name example.com/' nginx.conf
一次完成「改端口」和「改 server_name」两处替换,还带了 -i.bak 备份。这是改配置的常见用法。
七、sed 常用场景实战
7.1 去掉注释和空行(清洗配置)
sed -e 's/^[[:space:]]*#.*$//' -e '/^[[:space:]]*$/d' nginx.conf
两条命令:第一条把「以空白开头、后面跟 # 注释」的行内容替换成空($// 替换为空),第二条删除「空行」(^$[[:space:]]*$ 匹配只有空白、没有内容的行)。合起来就是去掉注释和空行,把配置清洗干净。
7.2 批量改配置文件里的路径
sed -i 's#/var/log/old#/var/log/new#g' /etc/app/*.conf
对目录下所有 .conf 文件,把路径 /var/log/old 全局替换成 /var/log/new。用 # 当分隔符,路径里的斜杠就不用转义了。
7.3 在配置里某段后插入内容
# 在 [mysql] 段后插入一行配置
sed '/^\[mysql\]/a socket=/tmp/mysql.sock' my.cnf
这里寻址是 /^\[mysql\]/——注意方括号 [、] 在正则里是元字符(字符类),要匹配字面的 [、] 必须转义成 \[、\]。所以这个模式匹配「以 [mysql] 开头的行」,在它后面追加一行 socket 配置。
7.4 提取文件指定行(结合 -n 和 p)
sed -n '10,20p' file # 提取第 10-20 行
-n 关闭默认输出,p 只打印第 10-20 行——相当于「按行号截取文件片段」。
7.5 显示行号(类似 grep -n)
sed -n '=' file # 只打印行号
sed -n '1,5{=;p}' file # 打印前 5 行及行号
= 命令输出当前行号。第一个只打印行号;第二个用 {=;p} 在 1-5 行上同时执行「打印行号」和「打印内容」,效果类似 grep -n。
八、sed 与 grep、awk 的分工协作
8.1 三者怎么选
拿到一个文本处理需求,先想清楚「它属于哪一类」:
- 只要筛行(挑出匹配的行)→ grep;
- 要改内容(替换、删、增插)→ sed;
- 要按列统计、计算、格式化 → awk。
8.2 管道协作
三剑客常配合管道使用,各干各的强项:
# grep 先筛行,sed 再清洗
grep "ERROR" app.log | sed 's/ERROR/错误/g'
# awk 取列,sed 处理
awk '{print $2}' file | sed 's/,//g'
第一个:grep 先把含 ERROR 的行筛出来,再交给 sed 把 ERROR 替换成 错误;第二个:awk 取出第 2 列,sed 把里面的逗号去掉。先粗筛(grep),再精改(sed),最后计算(awk),是常见的协作链。
8.3 注意:sed 不擅长按列
sed 按「行」工作,对「列」的处理很别扭(比如「把第 3 列替换掉」用 sed 很费劲)。awk 才是列处理的主力。遇到按列的需求,直接用 awk,别硬用 sed 折腾。
九、常见坑与技巧
9.1 -i 不备份就改,改错没法恢复
这是 sed 最危险的地方。-i 直接覆盖原文件,一旦替换规则写错,原文件就没了。
生产环境改配置务必 -i.bak,或先手动 cp 备份。宁可多一步备份,也不要赌自己不会写错。
9.2 路径里的斜杠要换分隔符
替换含 / 的路径时,用 # 或 @ 当分隔符,避免一堆转义:
sed -i 's#/etc/nginx#/usr/local/nginx#g' file
9.3 GNU sed 与 BSD sed 的差异(跨平台)
不同操作系统自带的 sed 有差异,主要在 -i 和 -E 上:
- macOS/BSD sed:
-i必须带后缀(sed -i '' ...),且对-E的某些用法支持不完整; - Linux GNU sed:
-i可省略后缀,支持-E扩展正则。 - RHEL/Rocky/Ubuntu 都是 GNU sed,本系列按 GNU sed 讲。如果你要在 macOS 上跑,注意
-i的写法差异。
9.4 & 符号在替换里的含义
替换内容里的 & 表示「整个匹配的内容」,相当于一个隐式的 \0(整个匹配)。常用于「给匹配到的内容加壳」:
# 把匹配到的词用括号包起来
sed 's/error/(&)/g' file # error → (error)
& 会原样引用被匹配到的 error,所以结果是 (error)。配合 g 全局替换,能把一行里所有匹配都包起来。
9.5 转义元字符
要替换字面的 .、* 等元字符,必须转义(和第 3 篇正则一个道理):
sed 's/\./,/g' file # 把字面点号换成逗号
\. 匹配字面点号,换成逗号。如果忘了转义,. 会被当成「任意字符」,把不该替换的也替换了。
十、总结与练习
10.1 核心速查
- sed 定位:编辑(替换、删除、增插);
- 基本语法:
sed '寻址动作' 文件; - 寻址:行号(3、2,5)、模式(/error/)、范围(/a/,/b/)、取反(!);
- 替换:
s/旧/新/g,-E扩展正则,\1引用分组,换分隔符#; - 删除/增插:d 删、a 后插、i 前插、c 改行;
- 就地修改:
-i改原文件,-i.bak留备份(推荐); - 多命令:
;或-e分隔。
10.2 动手练习
- 用 sed 把文件里所有
old替换成new(含全局和 -i.bak); - 删除配置文件的注释行和空行;
- 在第 5 行后插入一行新配置;
- 用 -E 和 \1 把
name: xxx改成name=xxx; - 改 nginx 配置 worker_processes 并备份、校验、重载。
参考资料
- GNU sed 手册(man sed):https://www.gnu.org/software/sed/manual/
- 菜鸟教程 - sed 命令:https://www.runoob.com/linux/linux-comm-sed.html
- sed 单行脚本速查(Sed one-liners):https://www.gnu.org/software/sed/manual/sed.html
