Linux运维必备神器:sed命令从入门到精通完全指南
作为一名Linux运维工程师,每天都要和大量的文本文件打交道——配置文件、日志文件、脚本文件、数据文件等等。在这些工作中,文本处理能力直接决定了你的工作效率。而在Linux众多的文本处理工具中,sed(Stream Editor,流编辑器)无疑是最强大、最常用的工具之一。
很多运维人员对sed的了解只停留在简单的替换操作上,但实际上,sed的功能远不止于此。它可以实现复杂的文本转换、批量修改、数据提取、日志分析等任务,是每个运维工程师必须精通的”瑞士军刀”。
这篇博客将带你从sed的基础概念开始,一步步深入到高级技巧,最后通过大量真实的运维实战案例,让你真正掌握sed命令,成为文本处理高手。
一、sed基础:认识流编辑器
1.1 什么是sed
sed是一个非交互式的流编辑器。它不像vim那样需要你打开文件进行交互式编辑,而是从标准输入或文件中读取数据,按照指定的编辑命令对数据进行处理,然后将结果输出到标准输出。
sed核心特点
- 流处理:逐行读取数据,处理一行输出一行,内存占用极低,可处理超大文件。
- 非交互式:可以在脚本中自动化执行,无需人工干预,适合集成到自动化流程。
- 强大的正则表达式支持:可以处理复杂的文本模式匹配和转换。
- 轻量级:几乎所有Linux发行版都默认安装,无需额外配置,可即插即用。
1.2 sed的工作原理
理解sed的工作原理是掌握sed的关键。sed的工作流程可以概括为以下几个步骤:
- 读取:从输入流(文件或标准输入)中读取一行数据,存入模式空间(Pattern Space)。
- 执行:按照sed命令的顺序,对模式空间中的内容进行处理。
- 输出:将处理后的模式空间内容输出到标准输出(除非指定了
-n选项)。 - 重复:清空模式空间,读取下一行,重复上述过程,直到文件结束。
模式空间与保持空间的关键区别模式空间是sed主要的工作区,用于存放当前正在处理的行;保持空间是一个辅助存储区,初始为空,用于保存临时数据。两者配合可以实现复杂的多行处理操作,这是sed高级功能的核心。
1.3 sed的基本语法
sed的基本语法格式如下:
sed [选项] '地址 命令' 输入文件
# 实际使用示例:将文件中的"old"替换为"new",并输出结果sed 's/old/new/g' test.txt
# 更复杂的例子:指定地址范围处理sed '1,10 s/foo/bar/g' test.txt各组成部分详解:
- 选项:控制sed的整体行为(如
-n、-i、-r等) - 地址:指定要处理的行范围(行号、正则表达式或地址范围)
- 命令:指定要执行的编辑操作(
s替换、d删除、p打印等) - 输入文件:要处理的文件,省略时从标准输入读取
二、sed常用选项详解
sed提供了多个选项来控制其行为,以下是运维工作中最常用的几个选项:
2.1 -n:静默模式(关闭默认输出)
默认情况下,sed会将模式空间的内容输出到标准输出。使用-n选项可以关闭默认输出,只输出被命令明确指定的行。这在你只关心匹配结果时非常有用。
# 【对比示例】显示默认行为的差异# 不使用 -n:输出所有行,但第5行会被打印两次(默认输出+p命令输出)sed '5p' test.txt
# 使用 -n:只显示文件的第5行,且只输出一次sed -n '5p' test.txt
# 只显示包含"error"的行,忽略其他所有行sed -n '/error/p' test.log
# 显示第2行到第5行之间包含"warning"的所有行sed -n '2,5{/warning/p}' test.log什么时候使用-n当你只关心特定行的内容(如日志查询、配置提取)时,使用
-n可以避免冗余输出,提高脚本效率。
2.2 -i:原地修改文件(直接修改源文件)
默认情况下,sed不会修改原文件,只是将处理结果输出到标准输出。使用-i选项可以直接修改原文件。这是一个强大但危险的功能,必须谨慎使用。
原地修改的高危风险使用
-i选项时一定要小心,因为它会直接修改原文件,且没有撤销功能。如果发生误操作可能会导致配置丢失!强烈建议使用:spoiler[-i.bak]选项自动创建备份文件,这样可以在出错时恢复。
# 【最危险的方式】直接修改原文件,无备份sed -i 's/old/new/g' test.txt
# 【推荐的安全方式】修改原文件,并自动创建备份文件 test.txt.bak# 如果sed命令执行出错,原文件保存在.bak中,可以恢复sed -i.bak 's/old/new/g' test.txt
# 【批量修改配置文件】修改目录中所有.conf文件,保存备份for file in *.conf; do sed -i.backup "s/localhost/0.0.0.0/g" "$file"done
# 【修改后验证】执行完-i操作后,应该立即验证改动是否正确# 比较修改前后的差异diff test.txt.bak test.txt2.3 -e & -f:多命令执行与脚本加载
如果需要执行多个编辑命令,可以使用-e选项来拼接多个命令,或者使用-f选项读取独立的脚本文件。对于复杂的sed脚本,建议使用-f选项,这样可以提高可维护性。
# 【方式一】使用 -e 选项:多个编辑命令依次执行# 先删除空行,再将"old"替换为"new"sed -e '/^$/d' -e 's/old/new/g' -e 's/foo/bar/g' test.txt
# 【方式二】使用 -f 选项:从脚本文件读取所有命令cat > sed_commands.sed << 'EOF'# 删除空行/^$/d
# 替换old为new(全局)s/old/new/g
# 替换foo为bar(全局,忽略大小写)s/foo/bar/gi
# 删除以#开头的注释行/^#/dEOF
# 执行脚本文件中定义的所有命令sed -f sed_commands.sed test.txt
# 【实用技巧】结合-i选项,原地修改多个文件sed -i.bak -f sed_commands.sed file1.txt file2.txt file3.txt2.4 -r 或 -E:使用扩展正则表达式
默认情况下,sed使用基础正则表达式。使用-r(GNU sed)或-E(BSD sed)选项可以使用扩展正则表达式,减少转义的麻烦。
# 【基础正则 vs 扩展正则】对比
# 基础正则表达式:需要转义+号sed -n '/[0-9]\+/p' test.txt
# 扩展正则表达式:无需转义+号,更直观sed -rn '/[0-9]+/p' test.txt
# 【复杂匹配示例】提取邮箱地址# 基础:需要大量转义,容易出错sed -n 's/.*\([a-zA-Z0-9._-]*@[a-zA-Z0-9.-]*\).*/\1/p' text.txt
# 扩展:更清晰直观sed -rn 's/.*([a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+).*/\1/p' text.txt
# 【提取IP地址】sed -rn 's/.*([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}).*/\1/p' test.log三、sed寻址:精准选择要处理的行
我们先从根上建立认知:sed 的寻址(Address),就是指定「后面的命令要作用在哪些行上」。
之前写的 /^$/d 里,/^$/ 就是寻址部分,d 是动作部分。不写寻址时,默认对所有行执行动作。
sed 处理每一行的固定流程:
- 读取一行到「模式空间」
- 用寻址规则判断:这一行要不要处理?
- 匹配 → 执行后面的编辑动作
- 输出结果,读取下一行,循环往复
下面我们把所有寻址规则按类型讲透,每个都配语法+例子+说明。
3.1 单地址寻址:只匹配「某一行」
只写一个地址,代表只对匹配到的那一行执行动作。分两种写法:
1. 行号寻址(数字地址)
用数字精确指定第几行。
- 语法:
n(n 为正整数) - 特殊符号:
$代表最后一行
示例:
sed '1d' test.txt # 删除第 1 行sed '$d' test.txt # 删除最后一行sed -n '5p' test.txt # 只打印第 5 行(-n 关闭默认输出)2. 正则寻址(模式地址)
用正则表达式匹配,行内容满足正则就命中。这就是你最开始接触的 /.../ 写法。
- 语法:
/正则表达式/
示例:
sed '/^$/d' test.txt # 匹配空行 → 删除(你最开始的命令)sed '/^#/d' test.txt # 匹配 # 开头的注释行 → 删除sed '/error/p' test.txt # 匹配包含 error 的行 → 打印正则寻址匹配规则关键注意:正则寻址默认是「包含匹配」——只要行里出现了对应字符串就算命中。 只有加了
^和$锚定,才是「整行匹配」,比如/^abc$/才表示整行就只有 abc。
3.2 范围寻址:匹配「从 A 到 B 的连续行」
用逗号 , 分隔两个地址,代表:从匹配地址1的行开始生效,到匹配地址2的行结束生效,中间所有行(含起止行)都会执行动作。
这是最容易绕晕的部分,也是 sed 寻址的核心用法。
1. 三种组合形式
| 组合类型 | 语法示例 | 作用 |
|---|---|---|
| 行号 + 行号 | 2,5d | 删除第 2 到第 5 行(包含第 2、5 行) |
| 正则 + 正则 | /start/,/end/d | 从含 start 的行开始删,到含 end 的行停止 |
| 行号 + 正则混合 | 3,/^$/d | 从第 3 行开始删,一直删到第一个空行 |
2. 正则范围寻址的执行逻辑(重点)
以 sed '/start/,/end/d' file 为例:
- 逐行扫描,第一次遇到
start→ 开启删除动作 - 继续往后,遇到
end→ 执行完这行的删除后,关闭动作 - 如果后面又出现
start,会再次开启,循环往复
举个实际文本例子:
line1startline3line4endline6startline8endline10执行后,两组 start~end 都会被删掉,最终剩下:
line1line6line10起止全包记住:范围寻址是「起止全包」,开头行和结尾行都会被执行动作。
3.3 特殊寻址语法
1. 取反符 !:不匹配的行才执行
语法:地址!动作
意思完全反过来:不满足该地址的行,才执行后面的命令。
示例:
sed '/^$/!d' test.txt # 不是空行的就删掉 → 只保留空行(和 /^$/d 正好相反)sed '1!d' test.txt # 不是第 1 行就删掉 → 只保留第 1 行sed '/^#/!p' -n test.txt # 只打印不是 # 开头的行2. 步进寻址 ~:按步长跳行
GNU sed(Linux 默认版本)特有,按固定间隔选中行。
- 语法:
起始行~步长
示例:
sed '1~2d' test.txt # 从第1行开始,每隔2行删一行 → 删除所有奇数行sed '0~2d' test.txt # 从第0行开始,每隔2行删一行 → 删除所有偶数行sed '3~4p' -n test.txt # 打印第 3、7、11... 行3. 空地址:作用于全部行
完全不写地址,比如 sed 'd' file,代表匹配所有行,全部删除。
3.4 一个地址执行多条命令:{} 命令组
sed 默认的语法规则是:一个寻址条件,只控制紧跟在它后面的 1 条命令。 如果后面的其他命令不加寻址,就会变成「无寻址」状态,也就是对所有行生效。
{} 的作用,就是把多条命令打包成一个整体,让它们全部共用前面同一个寻址条件。我们通过一个反面例子来看看不加 {} 会发生什么。
1. 易踩坑:不加 {} 会发生什么?
假设有一个文件 fruit.txt 内容如下,我们的需求是:只在包含「hello」的行里,把 apple 换成 苹果,把 cherry 换成 樱桃。
第一行 apple cherry第二行 hello apple cherry第三行 apple cherry错误写法(不加 {}):
sed '/hello/s/apple/苹果/;s/cherry/樱桃/' fruit.txt我们拆解这条命令:
/hello/是寻址条件。- 它只管住了紧跟的第一条命令:
s/apple/苹果/。 - 第二条命令
s/cherry/樱桃/前面没有寻址,所以是全局生效,所有行都会执行。
执行结果会变成这样,没有 hello 的行也被替换了 cherry:
第一行 apple 樱桃 ← 没有hello,但cherry还是被替换了(全局生效)第二行 hello 苹果 樱桃 ← 两个替换都执行了第三行 apple 樱桃 ← 没有hello,但cherry还是被替换了这就是最容易踩的坑:你以为两条命令都受寻址控制,实际上只有第一条受控制。
2. 正确写法:用 {} 打包多条命令
想让两条替换都只作用在包含 hello 的行上,就要用 {} 把两条命令包起来:
sed '/hello/{s/apple/苹果/;s/cherry/樱桃/}' fruit.txt语法拆解:
- 大括号里的所有命令,都属于前面
/hello/这个寻址。 - 命令之间用分号
;分隔。 - 只有匹配到 hello 的行,才会依次执行括号里的两条命令。
执行结果完美符合预期:
第一行 apple cherry ← 没匹配hello,完全不处理第二行 hello 苹果 樱桃 ← 两个替换都执行第三行 apple cherry ← 没匹配hello,完全不处理3. 两种书写格式
- 单行格式(适合短命令):命令之间用分号隔开,写在一行里。
sed -n '/^#/{s/^# //;s/ $//;p}' test.txt意思:匹配 # 开头的行 → 先去掉开头的 # → 再去掉结尾的空格 → 打印出来。
- 多行格式(适合长脚本,更易读):左括号
{结尾换行,里面每条命令占一行,最后用}收尾。
sed -n '/^#/{ s/^# // s/ $// p}' test.txt两种写法功能完全一样,只是排版不同。命令多的时候推荐多行写法,不容易看错。
4. 进阶搭配:范围寻址与多类型命令
大括号不仅能跟单地址,也能跟「范围寻址」搭配,而且里面不局限于替换命令 s,删除 d、打印 p、插入 i、追加 a 都可以放进去。
示例一:结合范围寻址
给第 2 到第 4 行,同时做两件事:行首加 // 注释,行尾加 // 注释标记。
sed '2,4{ s/^/\/\/ / s/$/ \/\/ 注释标记/}' test.txt示例二:组合多种不同命令 匹配到 error 行 → 行首加【错误】前缀 → 在这行后面追加一行分隔线。
sed '/error/{ s/^/【错误】/ a\--- 以上是报错信息 ---}' log.txt命令组一句话总结与细节
- 核心口诀:1条命令 → 直接写在寻址后面;≥2条命令 → 必须用
{}包起来,共用同一个寻址。- 嵌套说明:语法上支持
{}嵌套,但日常使用完全没必要,会大幅降低可读性。- 书写规范:寻址和
{之间可以加空格,但不要换行(部分严格版本的 sed 会报错),规范写法是挨在一起或者加一个空格。
3.5 最容易踩的 4 个坑
-
$的双重身份- 在行号寻址里:
$表示最后一行,比如$d - 在正则里:
$表示行尾,比如/end$/不要写/$/d,每一行都有行尾,会把所有行都删掉。
- 在行号寻址里:
-
正则是包含匹配,不是全等匹配
/abc/会匹配abc、xabc、abc123;只有/^abc$/才是整行恰好等于 abc。 -
范围寻址会循环触发 文件里有多组 start/end 时,每一组都会被处理,不是只处理第一次。
-
寻址只作用于紧跟它的一条命令 要多条命令共用一个寻址,必须用
{}包裹。
3.6 补充小技巧:更换正则分隔符
sed 正则寻址默认用 / 当边界符号,比如 /^$/d,两个 / 就是用来框住正则内容的分隔符。
但如果你要匹配的内容本身就带大量 /(比如文件路径 /etc/nginx/conf.d),那路径里的每个 / 都得加反斜杠转义,写出来会非常乱(俗称“牙签地狱”):
# 传统写法:需要大量转义,容易写错也难读sed '/\/etc\/nginx\/conf/d' file自定义分隔符就是为了解决这个问题:换一个不会和内容冲突的符号(比如 @、#、%)当边界,里面的 / 就不用转义了。
1. 核心规则:寻址换分隔符,第一个必须加反斜杠
sed 标准语法规定:在正则寻址里更换分隔符,必须在「第一个新分隔符」前面加一个反斜杠 \。
它的作用是告诉 sed:接下来这个字符不是正则内容,是我新指定的边界分隔符,后面第二个相同字符就是结束边界。
- 标准格式:
\c正则内容c(c代表你选的新分隔符,如@、#)
# 优雅写法:自定义分隔符(第一个字符前加反斜杠)sed '\@/etc/nginx/conf@d' file逐段拆解:
\@:标记新分隔符为@/etc/nginx/conf:正则内容(内部的/完全不用转义)@:结束分隔符d:动作
2. 90% 的人困惑的根源:两种分隔符规则不一样
sed 里有两个地方能换分隔符,语法规则完全不同,很容易搞混:
| 场景 | 原生语法 | 换分隔符要不要加前导反斜杠 | 自定义写法示例 |
|---|---|---|---|
| 正则寻址(选哪些行) | /正则/ | 必须加 | \@/etc/@d |
| s 替换命令(替换内容) | s/旧/新/ | 不用加,直接跟在 s 后面 | s#/etc#/opt#g |
对比感受一下:
# s 命令换分隔符:s 后面直接跟 #,不用加 \sed 's#/usr/bin#/bin#g' file
# 寻址换分隔符:第一个分隔符前必须加 \sed '\#/usr/bin#d' file为什么不一样?
s是一个明确的命令关键字,sed 看到s就知道后面紧跟的字符是分隔符。- 正则寻址没有前置关键字,所以必须用
\做标记,不然 sed 会把@、#当成正则里的普通字符去匹配。
3. 补充细节
- 分隔符任选:几乎所有单字符都能当分隔符,只要不和正则内容冲突就行,日常常用
@、#、%、|。 - 标准兼容:前导反斜杠是 POSIX 标准语法,所有版本 sed 都通用;建议养成加的习惯,换环境不容易报错。
- 范围寻址也能用:比如从匹配
/start/path的行到/end/path的行,两个地址都要单独加反斜杠:sed_range_delimiter.sh sed '\@/start/path@,\@/end/path@d' file
分隔符规则速记
- 用来选行的正则寻址换分隔符 → 第一个分隔符前必须加
\- 用来替换内容的 s 命令换分隔符 → 直接跟在 s 后面,不用加
\
四、sed基本编辑命令:增删改查四大操作
4.1 查询和删除:p(打印)/ d(删除)
p命令用于打印行,通常配合-n选项使用,实现”只显示特定行”的功能。d命令用于删除匹配的行,是数据清理的利器。
# 【p命令示例】打印查询# 查询/etc/passwd中包含"root"的行sed -n '/root/p' /etc/passwd
# 显示有效用户(排除#注释和空行)sed -n '/^[^#]/p' /etc/passwd
# 打印第10到20行,并显示行号sed -n '10,20p' test.txt
# 【d命令示例】删除行# 删除第2行到第5行sed '2,5d' test.txt
# 删除空行(^$表示行首直接是行尾)sed '/^$/d' test.txt
# 删除以#开头的注释行sed '/^#/d' config.conf
# 删除最后一行sed '$d' test.txt
# 【批量清理日志】删除所有空行、注释行和特定模式行sed -i -e '/^$/d' -e '/^#/d' -e '/^\s*$/d' -e '/@deprecated/d' config.txt4.2 核心命令:s(替换)—— sed最常用的功能
s命令是sed中最常用的命令,用于替换文本中的字符串。语法格式为s/pattern/replacement/flags。
s命令的关键标志位详解
g:全局替换,替换一行中所有匹配的字符串(默认只替换第一个)i:忽略大小写进行匹配n:只替换第n个匹配的字符串(n=2表示替换第2个)p:打印被替换的行(通常配合-n选项使用)w filename:将替换后的行写入指定文件- 分隔符:可以使用任意字符作为分隔符(如
#、@),避免路径转义混乱
# 【基础替换】只替换每行中的第一个"old"sed 's/old/new/' test.txt
# 【全局替换】替换一行中的所有"old"sed 's/old/new/g' test.txt
# 【忽略大小写】将所有"old"替换为"new"(不论大小写)sed 's/old/new/gi' test.txt
# 【替换指定位置】只替换每行中的第2个"old"sed 's/old/new/2' test.txt
# 【特殊符号:&】代表匹配到的**整个字符串**# 在所有数字前后加上括号sed 's/[0-9]\+/(&)/g' test.txt# 输入:user 123 admin 456# 输出:user (123) admin (456)
# 【特殊符号:\1, \2...】代表正则**分组**匹配到的第n个子串# 将"first last"格式转换为"last, first"sed 's/\([a-z]\+\) \([a-z]\+\)/\2, \1/' test.txt# 输入:John Doe# 输出:Doe, John
# 【多组分组例子】提取日志中的IP和时间戳sed 's/^\([0-9.]\+\).*\[\([^]]*\)\].*/IP:\1 TIME:\2/' access.logs命令的分隔符自定义技巧当要替换的字符串包含
/时(例如文件路径),如果继续使用/作为分隔符就需要大量的\转义。sed允许使用任意字符作为分隔符,这大大简化了复杂替换的编写。
# 【分隔符冲突示例】
# 糟糕的写法:处理文件路径时大量转义,可读性极差sed 's/\/usr\/local\/bin/\/opt\/bin/g' test.txt
# 优雅的写法一:使用 '#' 作为分隔符sed 's#/usr/local/bin#/opt/bin#g' test.txt
# 优雅的写法二:使用 '@' 作为分隔符sed 's@/usr/local/bin@/opt/bin@g' test.txt
# 【实际应用】修改Nginx配置文件中的路径sed -i 's#/var/www/html#/data/www#g' /etc/nginx/nginx.conf4.3 行操作:a(追加)/ i(插入)/ c(替换整行)
这三个命令用于增加新行或修改现有行的内容。区别在于:i在行前插入,a在行后追加,c替换整行。
# 【a命令:追加】在匹配行的后面插入新行# 在包含"error"的行后面追加一行sed '/error/ a\--- ERROR OCCURRED ---' test.log
# 在文件最后一行后面追加版本号sed '$ a\Version: 1.0.0' config.txt
# 【i命令:插入】在匹配行的前面插入新行# 在文件开头插入标题sed '1 i\===== System Configuration =====' config.txt
# 在所有"server"配置块前面添加分隔注释sed '/^server {/ i\# ===== New Server Block =====' nginx.conf
# 【c命令:替换整行】将匹配的行替换为新内容# 将包含"old"的行替换为"new line"sed '/old/ c\new line' test.txt
# 【多行追加】使用转义新行在一条sed命令中追加多行sed '/END_OF_HEADER/ a\new line 1\new line 2\new line 3' test.txt
# 【实用场景】向配置文件添加新的配置块sed '/\[database\]/ a\host = 127.0.0.1\port = 3306\user = root' config.ini4.4 y(字符转换)—— 按位映射转换字符
y命令进行字符级转换,类似于tr命令。它将第一个集合中的字符一一映射到第二个集合中的字符。
# 【基础示例】将小写字母转换为大写字母sed 'y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/' test.txt
# 【实用例】简单的密码加密(凯撒密码)# 将字母a-z转换为b-z,a(循环移位)sed 'y/abcdefghijklmnopqrstuvwxyz/bcdefghijklmnopqrstuvwxyza/' message.txt
# 【数据转换】将分隔符从,转换为|sed 'y/,/|/' data.csv
# 【反向映射】将大写转小写sed 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/' test.txt五、进阶核心:sed 反向引用(分组捕获)详解
反向引用是 sed 进阶必学的核心能力,一句话讲透本质:
先用括号
()把正则里的某段内容「捕获存起来」,再用\1、\2这种语法把存起来的内容拿出来复用。
它最常用在两个场景:
- 替换时保留部分原内容(比如提取字段、调换顺序、加前后缀)
- 匹配重复出现的内容(比如找连续重复的单词)
5.1 基础语法规则
1. 捕获分组:用括号存内容
在正则里用一对括号,把你想留住的内容包起来,这就叫一个「捕获分组」。
sed 会自动给分组编号,从左到右数左括号 (,第1个括号就是第1组,对应 \1,第2个对应 \2,以此类推,最多支持 9 个分组(\1 ~ \9)。
2. 反向引用:用 \N 取内容
\1:引用第 1 个分组捕获到的内容\2:引用第 2 个分组捕获到的内容- …
\9:引用第 9 个分组捕获到的内容
5.2 90% 新手踩的第一个坑:BRE 与 ERE 的括号转义
这是最容易写错的地方,必须先讲清楚:sed 默认用基础正则(BRE),括号必须加反斜杠转义才是「分组」;加了 -r 参数才是扩展正则(ERE),括号直接写就行。
| 模式 | 分组写法 | 对应参数 |
|---|---|---|
| 基础正则 BRE(默认) | \(内容\) | 无参数 |
| 扩展正则 ERE | (内容) | -r(GNU sed) / -E(BSD/macOS) |
举个最简单的对比,功能完全一样:
# 默认 BRE:括号必须转义echo "hello123world" | sed 's/hello\([0-9]*\)world/\1/'
# 加 -r 扩展正则:括号不用转义echo "hello123world" | sed -r 's/hello([0-9]*)world/\1/'两条命令输出都是 123,只是写法不同。
最佳实践建议日常写脚本优先加
-r或-E启用扩展正则,可读性高,不容易写错转义。
5.3 从入门到进阶:4 个经典场景
场景1:提取字段(最常用)
这是最常用的用法:把整行都匹配掉,只保留括号里抓出来的内容。
需求:从 ip addr 风格的行里,提取出 inet 后面的 IP 地址。
# 输入文本echo "inet 192.168.1.100/24 brd 192.168.1.255" | sed -r 's/^inet ([0-9.]+)\/.*$/\1/'# 输出:192.168.1.100拆解:
^inet:匹配行开头的inet([0-9.]+):第1组,捕获连续的数字和小数点(也就是IP)\/.*$:匹配斜杠和后面所有内容到行尾\1:整行替换成第1组捕获的IP
你也可以用
#当分隔符,不用转义斜杠:s#^inet ([0-9.]+)/.*$#\1#
场景2:调换字段顺序
多分组的经典用法:把捕获的内容调换位置。
需求:把 张三,男 这种「姓名,性别」格式,改成 性别:张三。
echo "张三,男" | sed -r 's/(.*),(.*)/\2:\1/'# 输出:男:张三拆解:
- 第1组
(.*):捕获逗号前面的「张三」 - 第2组
(.*):捕获逗号后面的「男」 - 替换成
\2:\1:先放第2组,加冒号,再放第1组
场景3:给匹配内容加前后缀
不用把整行替换掉,只在匹配到的内容周围加东西。
需求:把文中所有的数字,用 【】 包起来。
echo "订单号2024001已发货" | sed -r 's/([0-9]+)/【\1】/'# 输出:订单号【2024001】已发货场景4:在「查找正则」里用反向引用
反向引用不只能用在替换部分,也能用在查找正则里,用来匹配重复出现的内容。
需求:找出连续出现两次 ab 的行,也就是匹配 abab。
echo "abab hello" | sed -r '/(ab)\1/p' -n拆解:
(ab):第1组捕获ab\1:引用第1组的内容,也就是再匹配一次ab- 合起来就是匹配
abab
5.4 补充:特殊反向引用 &
有一个高频简写:& 代表整个正则匹配到的全部内容,相当于不用写括号的「全部分组」。
比如上面给数字加括号的例子,用 & 写更简洁:
# 两种写法效果完全一样echo "订单号2024001已发货" | sed -r 's/[0-9]+/【&】/'echo "订单号2024001已发货" | sed -r 's/([0-9]+)/【\1】/'使用建议只需要复用整个匹配结果时,优先用
&,代码更干净。
5.5 易错点与注意事项
反向引用常见坑点
- 分组编号从 1 开始:sed 里没有
\0(部分版本支持但不通用),要取全部匹配内容用&。- 嵌套分组按左括号数:比如
((a)(b)),从左数左括号:
- 第1个左括号 →
\1=ab- 第2个左括号 →
\2=a- 第3个左括号 →
\3=b(日常尽量别写嵌套分组,可读性很差。)- 作用域限制:
\1只能引用同一条s命令里的分组,跨命令、跨行都无效。- 分隔符无关:不管你用
/、#、@当分隔符,\1的写法永远不变。
六、sed高级技巧:模式空间与保持空间的力量
前面我们提到了sed有两个工作空间:模式空间和保持空间。掌握这两个空间的交互是sed进阶的关键,可以实现Shell和其他工具难以完成的复杂文本处理。
6.1 模式空间与保持空间的交互命令
空间交互命令速记
- h:将模式空间内容复制到保持空间(覆盖)
- H:将模式空间内容追加到保持空间(追加)
- g:将保持空间内容获取到模式空间(覆盖)
- G:在模式空间后面追加保持空间内容
- x:交换模式空间和保持空间的内容
- N:读取下一行到模式空间(用于多行处理)
- d:删除模式空间,开始处理下一行
- D:删除模式空间中第一个换行符前的部分
6.2 多行处理与高级文本操作
使用模式空间和保持空间,我们可以实现复杂的多行处理,这在处理跨行日志、HTML、JSON等多行数据时非常有用。
# 【示例1】反转文件中的行顺序(模拟tac命令)sed '1!G;h;$!d' test.txt# 详细解释:# 1!G - 第一行外,将保持空间追加到模式空间# h - 复制当前行到保持空间# $!d - 除了最后一行,删除模式空间(跳过输出),处理下一行# 效果:最后一行被输出,倒数第二行被输出...实现行反转
# 【示例2】将多行内容合并为单行sed ':a;N;$!ba;s/\n/ /g' test.txt# 详细解释:# :a - 定义标签a(用于分支跳转)# N - 将下一行追加到模式空间# $!ba - 如果不是最后一行,跳转回标签a(循环读取)# s/\n/ /g - 最后将所有换行符替换为空格# 效果:三行文本合并为一行
# 【示例3】删除包含"error"的行及其后面的2行sed '/error/{N;N;d}' test.log# 详细解释:# /error/{...} - 如果行包含"error"# N;N - 追加后续2行到模式空间# d - 删除整个模式空间(3行内容全部删除)
# 【示例4】配对删除(删除成对的标记行)# 删除从<block>到</block>的所有内容sed '/<block>/,/<\/block>/d' test.html
# 【示例5】奇偶行互换sed 'N;s/\(.*\)\n\(.*\)/\2\n\1/' test.txt# 详细解释:# N - 读下一行,在模式空间中有两行用\n分隔# s/.../\2\n\1/ - 第二行\2,换行,第一行\1,实现互换6.3 分支与标签:复杂流程控制
sed支持:label(标签)和t/b(分支)命令,可以实现复杂的流程控制,虽然用得较少,但在处理复杂逻辑时非常强大。
# 【基础分支】删除所有HTML标签# 使用循环分支去除嵌套标签sed -e :a -e 's/<[^>]*>//g;ta' test.html# 详细解释:# :a - 定义标签a# s/... - 删除一个HTML标签# ta - 如果替换成功,跳转回标签a(继续删除下一个标签)# 效果:不断循环删除,直到没有标签为止
# 【条件分支】根据内容选择性处理sed -e ':process' -e '/complete/!{N;b process;}' -e 's/\n/ /g' test.txt# 效果:只在遇到"complete"关键词时合并多行七、运维实战:sed在日常工作中的威力
7.1 日志分析与数据提取
日志分析是运维工程师的日常工作,sed可以快速提取、聚合和转换日志数据。
# 【Nginx访问日志分析】# 统计访问量最高的10个IPsed -n 's/^\([0-9.]\+\).*/\1/p' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -10
# 提取所有404错误的请求资源(IP、请求方法、路径)sed -n 's/^\([0-9.]\+\).* 404 .* "\(GET\|POST\|PUT\|DELETE\) \([^ ]\+\) .*/\1 \2 \3/p' /var/log/nginx/access.log
# 统计某一小时内的访问量(10:00-10:59)sed -n '/10\/May\/2026:10:[0-5][0-9]:/p' /var/log/nginx/access.log | wc -l
# 【应用日志错误统计】# 提取所有ERROR级别的日志,并统计错误类型sed -n 's/.*ERROR.*\[\([^]]*\)\].*/\1/p' app.log | sort | uniq -c | sort -rn
# 提取错误日志的时间戳和错误信息sed -n 's/^\([^ ]*\).*ERROR:\(.*\)/\1: \2/p' error.log
# 【数据库日志分析】# 统计执行时间超过1秒的SQL语句sed -n 's/.*Query time: \([0-9.]*\).*SET timestamp=.* \(.*\);$/\1: \2/;T;s/\([0-9.]*\)/\1/;s/^[01].*//;T;p' slow.log7.2 配置文件批量修改与安全变更
配置修改中,使用sed可以快速实现自动化变更。这是运维自动化的重要组成部分。
# 【安全的配置修改流程】
# 第一步:修改前备份cp /etc/my.cnf /etc/my.cnf.backup_$(date +%Y%m%d_%H%M%S)
# 第二步:使用sed修改配置,留下.bak备份sed -i.bak 's/^max_connections = .*/max_connections = 1000/' /etc/mysql/my.cnf
# 第三步:验证修改grep "max_connections" /etc/mysql/my.cnf
# 【修改Nginx配置】# 修改监听端口sed -i 's/^\s*listen\s\+80;/ listen 8080;/' /etc/nginx/nginx.conf
# 修改日志位置sed -i 's|/var/log/nginx|/data/log/nginx|g' /etc/nginx/nginx.conf
# 【批量修改服务器配置】# 为所有.conf文件中的localhost改为0.0.0.0for file in /etc/app/*.conf; do sed -i.backup 's/localhost/0.0.0.0/g' "$file"done
# 【修改应用配置】# 修改数据库连接字符串中的IP地址sed -i 's#jdbc:mysql://192.168.1.100:3306#jdbc:mysql://192.168.1.200:3306#g' application.properties以修改SSH配置文件为例,实际效果相当于执行了如下内容变更:
# Authentication:#PermitRootLogin yesPermitRootLogin no#StrictModes yes实现这个变更的sed命令:
# 修改SSH配置,禁止root登录sed -i 's/^#PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
# 验证修改grep -n "PermitRootLogin" /etc/ssh/sshd_config
# 应用配置(需要root权限)systemctl reload sshd7.3 批量数据清洗与格式转换
在处理大量数据时,sed是数据清洗和格式转换的利器。
# 【Windows到Unix格式转换】# 批量将文件中的DOS换行符(\r)转换为Unix换行符sed -i 's/\r$//' *.txt
# 或者使用更直观的方式sed -i 's/$//' *.txt # 删除回车符
# 【CSV到JSON格式转换】# 将CSV文件转换为JSON格式数组sed -e '1i [' -e '$s/$/]/' -e 's/^\(.*\)$/ {"name": "\1"},/' -e '$s/,$//' data.csv
# 【清理重复空格】# 将多个空格压缩为一个sed 's/ */ /g' messy.txt
# 【删除前后空格】# 删除行首和行尾的空格sed 's/^\s*//;s/\s*$//' whitespace.txt
# 【规范化数据格式】# 将日期格式从"2026-05-14"改为"2026/05/14"sed 's/\([0-9]\{4\}\)-\([0-9]\{2\}\)-\([0-9]\{2\}\)/\1\/\2\/\3/' dates.txt
# 【提取特定列】# 从日志中提取用户名和访问时间sed 's/^\([a-zA-Z0-9]*\).*\[\([^]]*\)\].*/User: \1, Time: \2/' access.log八、常见问题与避坑指南
8.1 变量解析陷阱:单引号 vs 双引号
Shell变量无法解析的常见错误在shell脚本中使用sed替换变量时,必须使用双引号而不是单引号。单引号会阻断Shell的变量展开机制,导致变量被当成字面值处理!
# 场景:需要将配置文件中的旧域名替换为新域名old_domain="old.example.com"new_domain="new.example.com"
# ❌ 错误做法:使用单引号# 结果:sed会尝试将文字"$old"替换为"$new",完全错误!sed 's/$old/$new/g' config.txt
# ✅ 正确做法:使用双引号# 结果:变量被正常展开,命令变为 sed 's/old.example.com/new.example.com/g'sed "s/$old_domain/$new_domain/g" config.txt
# 【更安全的做法】使用不同的分隔符,避免特殊字符冲突sed "s#$old_domain#$new_domain#g" config.txt
# 【在脚本中使用变量的完整示例】#!/bin/bashconfig_file="/etc/app/config.conf"new_ip="192.168.1.200"
# 修改配置文件中的数据库IPsed -i "s/db_host=[^ ]*/db_host=$new_ip/" "$config_file"
# 验证修改echo "新的数据库配置:"grep "db_host" "$config_file"8.2 正则表达式转义混乱
sed中的转义规则sed使用三层转义:
- Shell层:Shell会处理
\和引号- sed层:sed会处理正则表达式中的特殊字符
- 正则层:正则表达式的特殊含义
这导致有时需要多层转义,容易出错。使用扩展正则(
-r)可以大幅简化。
# 【转义混乱的例子】匹配文件路径中的点号
# 基础正则:需要三层转义,容易出错sed 's/file\.txt/file.bak/' test.txt
# 在分组时更容易出错# 基础:需要\\( \\)来表示分组sed 's/\([0-9]\)\.\([0-9]\)/\1,\2/' version.txt
# 扩展正则:更清晰直观sed -r 's/([0-9])\.([0-9])/\1,\2/' version.txt
# 【实际对比】提取IPv4地址
# 基础正则(繁琐的转义)sed -n 's/.*\([0-9]\{1,3\}\)\.\([0-9]\{1,3\}\)\.\([0-9]\{1,3\}\)\.\([0-9]\{1,3\}\).*/\1.\2.\3.\4/p'
# 扩展正则(清晰易懂)sed -rn 's/.*([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3}).*/\1.\2.\3.\4/p'8.3 性能问题与优化
当处理超大文件时,sed的性能可能成为瓶颈。这些优化技巧可以显著提升处理速度。
# 【避免多次读文件】# ❌ 低效:多次读文件,需要多次磁盘IOsed 's/old/new/g' file.txt > file.tmp && mv file.tmp file.txtsed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt
# ✅ 高效:使用-e选项合并命令,只需一次磁盘IOsed -e 's/old/new/g' -e 's/foo/bar/g' file.txt > output.txt
# 【避免贪心匹配】# ❌ 低效:贪心匹配导致引擎回溯,性能差sed 's/.*\(pattern\).*/\1/' file.txt
# ✅ 高效:使用非贪心匹配或精确模式sed 's/[^<]*\(pattern\)[^>]*/\1/' file.txt
# 【使用-n避免无谓输出】# ❌ 低效:输出整个文件,再用p打印特定行sed '5p' large_file.txt > /dev/null
# ✅ 高效:只输出需要的行sed -n '5p' large_file.txt > /dev/null
# 【处理超大文件时的分块处理】# 避免一次性加载整个文件到内存split -l 100000 large_file.txt chunk_for f in chunk_*; do sed 's/old/new/g' "$f" > "${f}.out"donecat chunk_*.out > result.txt九、sed vs awk vs grep:工具选择指南
作为运维工程师,经常会在sed、awk、grep之间选择。它们各有优势,理解区别才能选择最适合的工具。
三大文本处理工具的对比
工具 适用场景 学习难度 执行效率 grep 简单的行匹配、过滤 最简单 最快 sed 流编辑、行级替换、删除、格式转换 中等 中等 awk 复杂的数据处理、按列提取、统计汇总 较复杂 较慢 选择建议:
- 只需查找行内容 → 用
grep- 需要行级编辑(替换、删除、插入)→ 用
sed- 需要按字段处理、统计聚合 → 用
awk
# 【任务1:查找包含"error"的行】grep "error" test.log # 最简单高效
# 【任务2:删除所有空行】sed '/^$/d' test.txt # sed最合适grep -v '^$' test.txt # grep也可以
# 【任务3:将每行第一个old替换为new】sed 's/old/new/' test.txt # sed最简洁
# 【任务4:提取第3个字段(空格分隔)】awk '{print $3}' test.txt # awk最自然sed 's/ [^ ]* [^ ]* \([^ ]*\).*/\1/' test.txt # sed会很复杂
# 【任务5:统计访问最频繁的IP】awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -5 # awksed -n 's/^\([0-9.]*\).*/\1/p' access.log | sort | uniq -c | sort -rn | head -5 # sed十、总结与进阶资源
sed是Linux运维人员不可或缺的文本处理利器。虽然它的基础语法简单,但结合正则表达式与模式空间、保持空间机制后,却能爆发出惊人的数据处理能力。在执行危险操作前务必遵循黄金三原则:
sed操作的黄金规则
- 先测试打印:使用
-n和p命令确认你的地址和模式是正确的- 后原地修改:确认无误后才使用
-i选项直接修改文件- 多做备份:使用
-i.bak选项自动备份,在出错时可以快速恢复
# 【安全的sed使用流程】
# 步骤1:在终端测试,确保地址和模式正确sed -n '/pattern/p' test.txt
# 步骤2:测试替换效果,不修改文件sed 's/old/new/g' test.txt | head -20
# 步骤3:确认无误后,使用-i.bak备份原文件并修改sed -i.bak 's/old/new/g' test.txt
# 步骤4:验证修改结果diff test.txt.bak test.txtgrep "new" test.txt进阶学习资源推荐:
- GNU官方手册:
man sed是最权威的速查手册,包含所有选项和命令详解 - 在线调试工具:::github{repo=“aureliojargas/sedsed”},可视化展示Pattern Space和Hold Space的变化过程,非常有助于理解高级技巧
- 经典书籍:《sed与awk》(第二版)是深入理解流式文本处理哲学的必读神作,强烈推荐
- 实战练习:在自己的服务器上大量练习日志分析、配置修改等真实场景,是掌握sed的最快方式