Linux Shell 文本编辑利器:sed 流编辑器从入门到精通
概述
在 Linux 文本处理工具链中,sed(Stream Editor,流编辑器)是一款 “轻量高效” 的核心工具。它与 Vim 这类交互式编辑器完全不同 —— 无需手动点击或输入键盘命令,而是通过 “预设规则” 自动处理数据流,尤其适合批量修改、筛选文本。
1. sed 的核心工作原理
sed 遵循 “逐行处理、一次遍历” 的逻辑,整个流程可概括为 4 步:
- 读取行:从输入(STDIN 或文件)中读取一行数据,存入 “模式空间”(临时缓冲区);
- 匹配规则:根据预设的编辑命令,检查当前行是否匹配规则;
- 执行修改:若匹配,对模式空间中的行执行增、删、改、查等操作;
- 输出结果:将处理后的行输出到 STDOUT,然后清空模式空间,读取下一行。
这种 “一次遍历” 的特性让 sed 处理大文件时速度极快 —— 无需加载整个文件到内存,逐行处理即可完成编辑,这是交互式编辑器无法比拟的优势。
2. sed 命令的基础格式
sed 命令的标准语法如下:
sed [options] script file
各参数的作用如下:
- options:修改 sed 行为的选项(如抑制默认输出、指定命令文件);
- script:定义编辑规则的脚本(单个命令或多个命令组合);
- file:待处理的目标文件(若省略,sed 会从 STDIN 读取数据)。
常用选项说明
选项 | 英文全称 | 功能描述 |
---|---|---|
-e commands | --expression | 在命令行中指定多个编辑命令(多个命令用分号分隔或多次使用 -e) |
-f file | --file | 从指定文件中读取编辑命令(适合命令较多的场景) |
-n | --quiet/--silent | 抑制 sed 的默认输出(仅显示被命令修改过的行,需配合 p 命令使用) |
一、sed 入门实战:基础编辑命令
sed 的核心是 “编辑命令”,最常用的是替换命令(s),其次是增、删、改、查等命令。先从命令行中的简单用法入手。
1. 替换命令:s/旧文本/新文本/(最核心命令)
替换命令(s 即 substitute)是 sed 最常用的功能,用于将文本中的 “旧字符串” 替换为 “新字符串”,基础格式为:
sed 's/pattern/replacement/' file
- pattern:要匹配的旧文本(可结合正则表达式);
- replacement:用于替换的新文本;
- 分隔符 / 可替换为其他字符(如 !、#),避免与文本中的 / 冲突(后文详解)。
示例 1:替换单行文本
通过管道传递数据给 sed,快速替换字符串:
# 将 "test" 替换为 "big test"
echo "This is a test" | sed 's/test/big test/'
# 输出:This is a big test
示例 2:替换文件中的所有行
对文件 data1.txt 中的所有行执行替换(将 dog 改为 cat):
# 查看原始文件
cat data1.txt
# The quick brown fox jumps over the lazy dog.
# The quick brown fox jumps over the lazy dog.
# 执行替换(sed 不修改原文件,仅输出结果)
sed 's/dog/cat/' data1.txt
# 输出:
# The quick brown fox jumps over the lazy cat.
# The quick brown fox jumps over the lazy cat.
# 验证原文件未被修改
cat data1.txt # 内容仍为 "dog"
2. 执行多个命令:-e 选项或分号分隔
若需对同一行执行多个编辑命令,有两种方式:
- 用分号 ; 分隔命令(命令间无空格);
- 用 -e 选项逐个指定命令(更易读)。
示例:同时替换两个字符串
# 方式 1:分号分隔命令
sed 's/brown/red/; s/dog/cat/' data1.txt
# 输出:The quick red fox jumps over the lazy cat.
# 方式 2:-e 选项指定多个命令
sed -e 's/brown/red/' -e 's/dog/cat/' data1.txt
# 输出同上
进阶:多行输入命令(bash 次提示符)
若命令较多,可利用 bash 的 “次提示符”(>)分行输入,提高可读性:
sed -e '
> s/brown/green/ # 第1个命令:brown→green
> s/fox/toad/ # 第2个命令:fox→toad
> s/dog/cat/' # 第3个命令:dog→cat
data1.txt
# 输出:The quick green toad jumps over the lazy cat.
3. 从文件读取命令:-f 选项
当编辑命令超过 3 条时,建议将命令存入单独的脚本文件(后缀常用 .sed),再用 -f 选项调用,便于维护和复用。
示例:用脚本文件执行多命令
# 1. 创建 sed 脚本文件 script1.sed
cat script1.sed
# s/brown/green/
# s/fox/toad/
# s/dog/cat/
# 2. 调用脚本文件处理数据
sed -f script1.sed data1.txt
# 输出:The quick green toad jumps over the lazy cat.
二、sed 替换进阶:掌握替换标志与特殊场景
基础替换命令仅替换 “每行第一个匹配项”,实际场景中常需全局替换、指定替换位置或输出到文件,这就需要用到替换标志。
1. 替换标志:控制替换行为
替换命令的完整格式为:
sed 's/pattern/replacement/flags' file
常用的 4 种标志如下:
标志 | 功能描述 |
---|---|
n | 数字(如 2),仅替换每行中的第 n 个匹配项 |
g | 全局(global)替换,替换每行中的所有匹配项 |
p | 打印(print)替换后的行,需配合 -n 选项使用(仅输出被修改的行) |
w file | 将替换后的行写入(write)到指定文件,同时正常输出到 STDOUT |
示例 1:全局替换(g 标志)
默认替换仅修改每行第一个匹配项,加 g 标志可替换所有匹配:
# 原始文件 data4.txt(每行有 2 个 "test")
cat data4.txt
# This is a test of the test script.
# 未加 g:仅替换第一个 "test"
sed 's/test/trial/' data4.txt
# 输出:This is a trial of the test script.
# 加 g:替换所有 "test"
sed 's/test/trial/g' data4.txt
# 输出:This is a trial of the trial script.
示例 2:指定替换位置(n 标志)
用数字标志指定替换 “第 n 个匹配项”:
# 替换每行中的第 2 个 "test"
sed 's/test/trial/2' data4.txt
# 输出:This is a test of the trial script.
示例 3:仅输出被修改的行(p 标志 + -n)
-n 抑制默认输出,p 标志打印替换后的行,组合后仅显示被修改的内容:
# 原始文件 data5.txt(仅第一行含 "test")
cat data5.txt
# This is a test line.
# This is a different line.
# 仅输出被修改的行
sed -n 's/test/trial/p' data5.txt
# 输出:This is a trial line.
示例 4:将结果写入文件(w 标志)
w file 会将替换后的行保存到文件,同时正常输出到 STDOUT:
# 替换并将结果写入 test.txt
sed 's/test/trial/w test.txt' data5.txt
# 输出(STDO):
# This is a trial line.
# This is a different line.
# 查看写入的文件
cat test.txt
# 输出:This is a trial line.
2. 替代分隔符:避免 / 转义的麻烦
替换命令默认用 / 作为分隔符,但如果文本中包含 /(如路径 /bin/bash),则需用 \ 转义(如 /bin/bash),容易出错。
sed 允许用其他字符(如 !、#、|)作为替代分隔符,只需将第一个分隔符替换即可:
# 原始需求:将 /bin/bash 替换为 /bin/csh(默认分隔符需转义)
sed 's/\/bin\/bash/\/bin\/csh/' /etc/passwd
# 用 ! 作为替代分隔符(更简洁)
sed 's!/bin/bash!/bin/csh!' /etc/passwd
三、sed 行寻址:精准定位目标行
默认情况下,sed 命令会应用于所有行。若需仅处理 “特定行”,需使用 “行寻址”(Addressing)—— 通过行号或文本模式定位目标行。
1. 行寻址的基础格式
行寻址需在命令前指定地址,格式如下:
# 单个地址 + 命令
sed '[address]command' file
# 多个命令分组(应用于同一地址)
sed 'address {
command1
command2
}' file
地址支持两种形式:数字行号和文本模式。
2. 数字行寻址:按行号定位
数字行号是最直观的寻址方式,sed 将文件第一行编号为 1,依次递增。
示例 1:指定单个行号
仅修改第 2 行的内容(将 dog 改为 cat):
sed '2s/dog/cat/' data1.txt
# 输出:
# The quick brown fox jumps over the lazy dog. # 第1行未修改
# The quick brown fox jumps over the lazy cat. # 第2行已修改
# The quick brown fox jumps over the lazy dog. # 第3行未修改
示例 2:指定行区间(起始行,结束行)
修改第 2 到第 3 行(含边界):
sed '2,3s/dog/cat/' data1.txt
# 输出:第2、3行被修改,其余行不变
示例 3:从某行到末尾(起始行,$)
$ 代表 “最后一行”,适合不确定文件总行数的场景:
# 修改第 2 行到最后一行
sed '2,$s/dog/cat/' data1.txt
3. 文本模式寻址:按内容定位
通过 “文本模式” 匹配行,格式为 /pattern/command(pattern 可结合正则表达式),仅处理包含该模式的行。
示例 1:匹配固定文本
仅修改包含 luojia 的行(将 bash 改为 csh):
# 先查看原始行
grep 'luojia' /etc/passwd
# 输出:luojia:x:1002:1002::/home/luojia:/bin/bash
# 仅修改 luojia 的行
sed '/luojia/s/bash/csh/' /etc/passwd
# 输出:luojia:x:1002:1002::/home/luojia:/bin/csh
示例 2:结合正则表达式
匹配包含 “数字” 的行,将 lazy 改为 sleeping:
# 原始文件 data6.txt
cat data6.txt
# This is line number 1.
# This is line number 2.
# This is the 3rd line.
# 匹配含数字的行(正则 /[0-9]/)
sed '/[0-9]/s/lazy/sleeping/' data1.txt
4. 命令分组:多命令应用于同一地址
对同一地址执行多个命令时,用 {} 分组,避免重复写地址:
# 对第 2 行执行两个替换命令
sed '2{
s/fox/toad/
s/dog/cat/
}' data1.txt
# 输出:第2行的 fox→toad、dog→cat,其他行不变
四、sed 核心命令:增、删、改、查全掌握
除了替换命令,sed 还提供了删除、插入、附加、修改等命令,覆盖文本编辑的全场景。
1. 删除行:d 命令(谨慎使用!)
删除命令(d 即 delete)用于删除匹配地址的行,无备份时务必谨慎(建议先加 -n 预览)。
示例 1:删除指定行号
删除第 3 行:
sed '3d' data6.txt
# 输出:移除第3行,保留其他行
示例 2:删除行区间
删除第 2 到第 3 行:
sed '2,3d' data6.txt
示例 3:删除匹配模式的行
删除包含 number 1 的行:
sed '/number 1/d' data6.txt
注意:模式区间删除的坑
若用两个模式指定删除区间(/start/,/end/d),sed 会从 “匹配 start 的行” 开始删除,直到 “匹配 end 的行” 结束。若未找到 end,会删除到文件末尾:
# 原始文件 data7.txt(含两个 "1")
cat data7.txt
# This is line number 1.
# This is line number 2.
# This is line number 1 again.
# 错误示例:从第一个 "1" 删到 "3"(未找到 3,删到末尾)
sed '/1/,/3/d' data7.txt
# 输出:空(所有行被删除)
2. 插入与附加行:i 和 a 命令
- 插入(i):在指定行之前插入新行;
- 附加(a):在指定行之后插入新行。
格式说明
命令需单独占一行(命令行中用 \ 换行,或在脚本文件中直接换行):
# 命令行格式(用 \ 换行)
sed '[address]i\
new line content' file
# 脚本文件格式(更直观)
echo '2i\
This is an inserted line' > insert.sed
sed -f insert.sed data6.txt
示例 1:在指定行前插入
在第 3 行前插入新行:
sed '3i\
This is an inserted line.' data6.txt
# 输出:新行位于第3行之前
示例 2:在指定行后附加
在最后一行($)后附加新行:
sed '$a\
This line is added to the end.' data6.txt
示例 3:插入多行
每行末尾用 \ 分隔,即可插入多行:
sed '1i\
Line 1: Inserted\
Line 2: Inserted' data6.txt
3. 修改行:c 命令(替换整行)
修改命令(c 即 change)用于替换整行内容,而非部分文本,适合批量重写特定行。
示例 1:修改指定行号
将第 2 行替换为新内容:
sed '2c\
This is a completely new line.' data6.txt
# 输出:
# This is line number 1. # 第1行不变
# This is a completely new line. # 第2行被替换
# This is the 3rd line. # 第3行不变
示例 2:修改匹配模式的行
将包含 3rd line 的行替换为新内容:
sed '/3rd line/c\
This line was changed by sed.' data6.txt
# 输出:包含 "3rd line" 的行被完全替换
注意:行区间修改的特性
若为 c 命令指定行区间(如 2,3c),sed 会用一行新文本替换整个区间的所有行,而非逐行修改:
# 用一行文本替换第2-3行
sed '2,3c\
This line replaces lines 2 and 3.' data6.txt
# 输出:
# This is line number 1.
# This line replaces lines 2 and 3.
# This is the 4th line.
4. 转换命令:y 命令(单字符一对一映射)
转换命令(y 即 translate)是 sed 中唯一处理 “单个字符” 的命令,格式为:
sed '[address]y/inchars/outchars/' file
它会将 inchars 中的每个字符,一对一映射为 outchars 中对应位置的字符(需保证两者长度相同,否则报错)。
示例 1:字符替换(数字映射)
将文本中的 1→7、2→8、3→9:
# 原始文件 data9.txt
cat data9.txt
# This is line 1.
# This is line 2.
# This is line 3.
# 执行字符映射
sed 'y/123/789/' data9.txt
# 输出:
# This is line 7.
# This is line 8.
# This is line 9.
示例 2:全局映射特性
y 命令是全局生效的,会替换行中所有匹配的字符,无法限制位置:
# 替换所有 "a→A"、"b→B"
echo "aabbaabb" | sed 'y/ab/AB/'
# 输出:AABB AABB(所有 a/b 均被替换)
五、sed 打印命令:精准输出关键信息
除了编辑文本,sed 还能通过打印命令提取关键信息,常用的有 3 种:p(打印行)、=(打印行号)、l(列出行,含不可打印字符)。
1. 打印行:p 命令(配合 -n 用)
p 命令用于打印指定行,单独使用会重复输出(默认输出 + p 命令输出),需配合 -n 选项抑制默认输出,仅保留 p 命令的结果。
示例 1:打印匹配模式的行
仅打印包含 3rd line 的行:
sed -n '/3rd line/p' data6.txt
# 输出:This is the 3rd line.
示例 2:打印行区间
打印第 2 到第 3 行:
sed -n '2,3p' data6.txt
# 输出第2、3行内容
示例 3:对比修改前后的行
先打印原始行,再打印修改后的行,便于验证:
sed -n '/3/{
p # 打印原始行
s/line/test/p # 替换后打印
}' data6.txt
# 输出:
# This is the 3rd line. # 原始行
# This is the 3rd test. # 修改后行
2. 打印行号:= 命令
= 命令会打印行号(位于行内容之前),适合定位文本在文件中的位置。
示例 1:打印所有行行号
sed '=' data1.txt
# 输出:
# 1
# The quick brown fox jumps over the lazy dog.
# 2
# The quick brown fox jumps over the lazy dog.
示例 2:打印匹配行的行号
结合 -n 和模式匹配,仅显示包含 text 的行的行号和内容:
# 原始文件 data7.txt
cat data7.txt
# This is line number 1.
# This is more text we want to keep.
# 打印行号 + 内容
sed -n '/text/{
= # 打印行号
p # 打印内容
}' data7.txt
# 输出:
# 2
# This is more text we want to keep.
3. 列出行:l 命令(显示不可打印字符)
l 命令(小写 L)会显示文本中的不可打印字符(如制表符 \t、换行符 $),用八进制或 C 语言命名规范表示(如 \t 代表制表符)。
示例 1:显示制表符
查看文件中是否包含制表符(肉眼难以分辨):
# 原始文件 data10.txt(含制表符)
cat data10.txt
# This line contains tabs. # 制表符分隔
# This line does contain tabs. # 空格分隔
# 用 l 命令显示不可打印字符
sed -n 'l' data10.txt
# 输出:
# This\tline\tcontains\ttabs.$ # \t 表示制表符,$ 表示换行符
# This line does contain tabs.$
示例 2:显示转义字符
查看文本中的转义控制码(如铃声 \a):
# 含铃声转义符的文件 data11.txt
cat data11.txt # 执行时会听到铃声,但无可见字符
# 用 l 命令显示转义符
sed -n 'l' data11.txt
# 输出:This line contains an escape character. \a$ # \a 是铃声转义符
六、sed 文件操作:读写外部文件
sed 不仅能处理输入数据流,还能直接读写外部文件,常用命令为 w(写入文件)和 r(读取文件)。
1. 写入文件:w 命令(保存关键行)
w 命令用于将匹配行写入指定文件,格式为:
sed '[address]w filename' file
- filename:目标文件路径(需有写权限);
- 地址支持行号、模式、区间,未指定则写入所有行。
示例 1:写入指定行区间
将第 1-2 行写入 test.txt:
sed '1,2w test.txt' data6.txt
# STDOUT 正常输出所有行,同时将 1-2 行写入 test.txt
# 验证结果
cat test.txt
# This is line number 1.
# This is line number 2.
示例 2:按模式写入
从邮件列表中提取 Browncoat 成员,写入 Browncoats.txt:
# 原始邮件列表 data12.txt
cat data12.txt
# Blum, R Browncoat
# McGuiness, A Alliance
# Bresnahan, C Browncoat
# 提取 Browncoat 成员
sed -n '/Browncoat/w Browncoats.txt' data12.txt
# 验证结果
cat Browncoats.txt
# Blum, R Browncoat
# Bresnahan, C Browncoat
2. 读取文件:r 命令(插入外部数据)
r 命令用于将外部文件的内容插入到数据流中,格式为:
sed '[address]r filename' file
- 内容会插入到 “指定地址之后”;
- 地址仅支持单个行号或模式(不支持区间)。
示例 1:在指定行后插入
在第 3 行后插入 data13.txt 的内容:
# 外部文件 data13.txt
cat data13.txt
# This is an added line.
# This is a second added line.
# 插入到第3行后
sed '3r data13.txt' data6.txt
# 输出:
# This is line number 1.
# This is line number 2.
# This is the 3rd line.
# This is an added line. # 插入的内容
# This is a second added line. # 插入的内容
# This is the 4th line.
示例 2:替换占位文本(实用场景)
用外部文件内容替换模板中的占位符(如套用信件中的 LIST):
# 模板文件 notice.std(含占位符 LIST)
cat notice.std
# Would the following people:
# LIST
# please report to the ship's captain.
# 用 data12.txt 替换 LIST
sed '/LIST/{
r data12.txt # 插入外部文件内容
d # 删除占位符 LIST
}' notice.std
# 输出:
# Would the following people:
# Blum, R Browncoat # 插入的列表
# McGuiness, A Alliance
# Bresnahan, C Browncoat
# Harken, C Alliance
# please report to the ship's captain.
七、sed 实战案例:解决实际运维问题
掌握基础命令后,结合实际场景灵活运用,才能发挥 sed 的真正价值。以下是 3 个高频运维场景的实战案例。
案例 1:批量清理日志中的注释行和空行
需求:处理 Nginx 配置文件 nginx.conf,删除所有注释行(# 开头)和空行,便于快速查看有效配置。
分析:
- 注释行:以 # 开头(模式 /^#/);
- 空行:仅含空格或无字符(模式 /^$/ 或 /[1]*$/);
- 用 d 命令删除匹配行。
实现命令:
# 删除注释行和空行,输出到 clean_nginx.conf
sed -e '/^#/d' -e '/^[[:space:]]*$/d' nginx.conf > clean_nginx.conf
# 预览结果(前10行)
head clean_nginx.conf
案例 2:批量修改文件名(配合 find)
需求:将当前目录下所有 .txt 文件的后缀改为 .log(如 data1.txt → data1.log)。
分析:
- 用 find 找到所有 .txt 文件;
- 用 sed 提取文件名(去掉 .txt),拼接新文件名;
- 用 xargs 执行 mv 命令。
实现命令:
# 安全起见,先预览修改计划(不实际执行)
find . -name "*.txt" | sed 'p; s/\.txt$/.log/' | xargs -n2 echo mv
# 确认无误后,执行实际修改(去掉 echo)
find . -name "*.txt" | sed 'p; s/\.txt$/.log/' | xargs -n2 mv
命令解释:
- find . -name "*.txt":查找当前目录下所有 .txt 文件;
- sed 'p; s/.txt$/.log/':打印原始文件名(p),再输出修改后的文件名(s/.txt$/.log/);
- xargs -n2 mv:将每两个文件名(原始 + 新)作为 mv 的参数,执行重命名。
案例 3:提取 CSV 文件中的指定列
需求:从 users.csv(格式:姓名,年龄,邮箱,电话)中提取 “姓名 + 邮箱” 两列,用 | 分隔。
分析:
- CSV 分隔符为 ,,需用 s 命令替换分隔符;
- 保留第 1 列(姓名)和第 3 列(邮箱),删除其他列。
实现命令:
# 原始 CSV 格式:张三,25,zhangsan@example.com,13800138000
# 目标格式:张三|zhangsan@example.com
sed 's/^\([^,]*\),[^,]*,\([^,]*\),.*/\1|\2/' users.csv > name_email.csv
# 查看结果
cat name_email.csv
正则解释:
- ([,]*):匹配行首到第一个 , 的内容(姓名),存入分组 1;
- ,[^,]*:匹配第二个字段(年龄),不保留;
- ,([^,]*):匹配第三个字段(邮箱),存入分组 2;
- ,.*:匹配剩余字段(电话),不保留;
- \1|\2:用分组 1 和 2 拼接结果,用 | 分隔。
八、sed 常见问题与避坑指南
1. 为什么 sed 不修改原文件?
sed 是 “流编辑器”,默认仅处理数据流并输出到 STDOUT,不会修改原文件。若需修改原文件,有两种方式:
- 安全方式:输出到新文件,验证后替换原文件(推荐):
sed 's/old/new/' file > new_file && mv new_file file
- 直接修改:GNU sed 支持 -i 选项(in-place),直接修改原文件(需谨慎,建议先备份):
# 备份原文件(file → file.bak),再修改原文件
sed -i.bak 's/old/new/' file
# 不备份(风险高,不推荐)
sed -i 's/old/new/' file
2. 为什么 Windows 格式的文件处理出错?
Windows 文本文件的换行符是 \r\n,而 Linux 是 \n,sed 会将 \r 视为不可见字符,导致匹配异常(如行尾多一个 ^M)。
解决方法:先将文件转换为 Linux 格式(用 dos2unix),再处理:
# 安装 dos2unix(若未安装)
sudo apt install dos2unix # Debian/Ubuntu
# sudo yum install dos2unix # CentOS/RHEL
# 转换文件格式
dos2unix windows_file.txt
# 再用 sed 处理
sed 's/old/new/' windows_file.txt
3. 为什么正则匹配不到包含空格的行?
空格在正则中是 “普通字符”,若模式中未包含空格,会导致匹配失败。例如:
- 错误:sed '/Thisis/a new line' file(模式中无空格,无法匹配 This is);
- 正确:sed '/This is/a new line' file(模式中包含空格)。
九、总结:sed 学习路径与最佳实践
1. 学习路径建议
- 基础阶段:掌握替换命令(s)、常用选项(-n/-e/-f),能完成简单文本替换;
- 进阶阶段:理解行寻址(数字 / 模式)、核心命令(d/i/a/c/y),能精准定位和编辑文本;
- 实战阶段:结合 find/xargs/grep 等工具,解决批量重命名、日志清理、数据提取等场景。
2. 最佳实践
- 先预览,再执行:修改文件前,先用 sed 输出到 STDOUT 预览结果(如 sed 's/old/new/' file | head),确认无误后再写入文件;
- 复杂命令写脚本:超过 3 条命令时,用 .sed 脚本文件(-f 选项),便于维护和复用;
- 优先用 GNU sed:GNU sed(Linux 默认)
[:space:] ↩︎