
1. 先搞懂sed的工作方式一行一行地读这才是流编辑的底层逻辑我最早接触sed的时候第一反应是这玩意儿跟vim有什么区别不都是改文本吗后来被老运维带了一段时间才明白sed的核心价值在于流——它不打开文件也不进入交互界面而是像流水线一样把数据一行一行地送进来处理完再吐出去。这种模式在处理大文件、批量修改、管道配合时优势是vim完全没法比的。1.1 sed与普通编辑器的本质区别普通编辑器vim、nano这类的工作方式是把整个文件加载到内存里你盯着屏幕改改完再存盘。这在小文件上没问题但遇到几百MB甚至几个GB的日志文件编辑器很容易卡死甚至直接崩溃。sed就不一样它一次只读一行对这一行执行完所有命令后立即输出然后读下一行内存占用基本恒定跟文件大小无关。所以sed的核心工作流程可以总结为三个步骤读入一行数据到模式空间pattern space对模式空间中的内容依次执行所有sed命令将处理结果输出到屏幕或重定向到文件然后清空模式空间继续读下一行这里有个关键概念叫模式空间你可以把它理解成一个临时工作台。sed会把每一行内容放到这个工作台上操作默认情况下处理完就扔掉所以它不会像vim那样有撤销这种操作——因为每一行都是独立处理的上一行跟下一行没有任何关系。这个特性既是它的优势简单高效也是它的局限想要处理跨行内容就得用一些特殊命令后面我会讲。1.2 sed命令的基本结构地址命令sed的基本用法就一句话sed 地址命令 文件。地址决定操作哪几行命令决定做什么操作。地址可以省略省略的话就默认对每一行都执行命令。举个例子sed s/old/new/ file.txt这个命令里s/old/new/就是命令没有写地址意味着对每一行都把第一个匹配到的old替换成new。而sed 3s/old/new/ file.txt则是只对第3行做替换。这个地址命令的结构贯穿整个sed的所有操作。理解了这个结构后面所有内容都会顺理成章。命令的执行顺序是每读入一行就按顺序把sed命令列表里所有 applicable 的命令执行一遍全部执行完才读下一行。注意sed默认会把处理后的每一行都输出到标准输出。如果你不想看到全部输出可以用-n参数关闭默认输出只打印你关心的行。这个-n配p命令的组合后面会大量使用。2. 增删查改四大操作一张表带你看懂全部命令sed号称增删查改四合一这四种操作分别对应的命令是s替换改、d删除删、i/a插入追加增、p打印查。再加上一个-n静默模式用来配合查询基本就覆盖了90%的使用场景。2.1 改替换命令ssed最核心的操作替换命令sed是sed里使用频率最高、功能最强大的命令没有之一。基本语法是sed s/匹配内容/替换内容/标志位 文件分隔符不一定是/你可以用任意字符比如#、、|这在替换内容本身包含/时特别有用。比如你要替换一个路径用/做分隔符就得写成s/\/usr\/local/\/opt/g一堆转义看着就头疼。改用#就清爽了s#/usr/local#/opt#g。标志位有几种常用的空只替换每行第一个匹配到的内容g替换每一行所有匹配到的内容全局替换I忽略大小写p打印替换成功的行通常配合-n使用w 文件名将替换成功的行写入到指定文件我举个例子对比一下g标志。有一个文件内容如下apple apple banana apple pie执行sed s/apple/orange/ file输出是orange apple banana orange pie每行只替换了第一个apple。但如果执行sed s/apple/orange/g file输出就是orange orange banana orange pie两个apple都被替换掉了。这个差异在实际操作中经常踩坑尤其批量修改配置文件时漏了g可能导致只改了一半服务启动直接报错。2.2 删删除命令d按行号或模式精准删除删除命令d会删除匹配到的整行基本用法sed 5d file # 删除第5行 sed 5,10d file # 删除第5到第10行 sed /^$/d file # 删除所有空行 sed /error/d file # 删除所有包含error的行 sed $d file # 删除最后一行删除操作在日志清洗里特别常用。比如Nginx的错误日志里夹杂着大量固定前缀的废弃告警一行sed /deprecated/d access.log就能把这些行全部过滤掉比用grep反向过滤更直观。这里要注意一个细节d命令执行后模式空间里当前行的内容就被清空了剩余的sed命令不会对空内容继续执行然后直接读入下一行。所以在sed 3d; s/old/new/ file里第3行不会执行替换命令因为它在执行d后就已经被丢弃了。2.3 增插入命令i和追加命令a以及读文件命令r插入和追加是一对相反的操i在指定行之前插入一行或多行a在指定行之后追加一行或多行c把指定行替换为新的内容最常用的写法是sed 3i\insert this line file # 在第3行之前插入 sed 3a\append this line file # 在第3行之后追加 sed 3c\replace this line file # 替换第3行注意反斜杠\后面跟的是要插入的文本。但是这种写法在不同版本的sed里表现得不太一致尤其BSD版和GNU版有细微差别后面专门讲。多行插入时每行都要用\连接比较繁琐。更优雅的写法是用-e参数或者分号;拼接多组命令。还有一个被很多人忽略的r命令可以从文件读取内容并插入到当前流中sed 5r /tmp/header.txt file # 在第5行之后插入header.txt的内容这个功能在批量生成配置文件、给多个文件统一插入模板内容时非常实用不需要手动复制粘贴一条命令就搞定。2.4 查打印命令p与-n静默模式组合默认情况下sed会把每一行都输出所以直接用p命令配合默认模式会出现重复打印。正确的查询姿势是配合-n参数sed -n 5p file # 只打印第5行 sed -n 5,10p file # 打印第5到第10行 sed -n /error/p file # 打印所有包含error的行这个组合本质上就是grep的替代方案但sed的优势在于你可以同时做多步操作。比如sed -n /error/{s/ERROR/error/g; p} file先对匹配到error的行做替换再打印出来一步到位。3. 地址定界与正则进阶精确锁定每一行sed的寻址方式比很多人想象的灵活得多。除了简单的数字行号和正则匹配还有区间、步进、混合多种方式。这一节我把最常用的寻址方式全部过一遍保证你看完就能组合出适合自己场景的用法。3.1 六种地址定界方式完全盘点我把地址定界的写法整理成一个表格方便对照参考写法含义示例N匹配第N行sed 3p file$匹配最后一行sed $d fileN,M匹配第N到第M行sed 3,8d fileN~M从第N行开始每隔M行匹配sed 1~2d file删除奇数行/pattern/匹配包含该正则的行sed /error/d file/pattern1/,/pattern2/从匹配pattern1的行开始到匹配pattern2的行结束sed /begin/,/end/d file第6种写法特别值得展开讲讲。它定义了一个范围从第一次匹配到pattern1的行开始到之后第一次匹配到pattern2的行结束这个区间内的所有行都会被选中。这个功能在做代码块删除、日志分段提取时非常有用。比如有一个配置文件结构是这样的server { listen 80; server_name old-site.com; } server { listen 8080; server_name new-site.com; }我想删除第一个server块可以这样sed /server {/,/^}/d nginx.conf但这里有个坑/server {/会匹配到每一行的server {开头/^}/会匹配所有以}开头的行。所以这个命令会把所有server块都删掉。想要只删第一个就得加上行号限制。这种范围和上下文相关的思维刚开始用sed时很容易搞混建议动手测试一下再上生产环境。3.2 正则表达式的实用技巧分组捕获与反向引用sed默认使用基本正则表达式BRE这意味着、?、|、()这些字符在默认情况下是字面量不是特殊字符。想要用扩展正则表达式ERE需要加-E参数这个参数在GNU sed和BSD sed里都支持。分组捕获是正则里的高级用法sed通过\(和\)BRE写法或(和)ERE写法来捕获匹配内容然后用\1、\2来引用。这个功能做数据提取和格式化非常强大。举个例子。我有一个人员名单文件每行格式是姓名 电话比如张三 13800138000 李四 13900139000我想把格式改成电话-姓名用一条sed命令搞定sed -E s/^([^ ])[ ]([0-9]{11})$/\2-\1/ contacts.txt这里([^ ])捕获了姓名([0-9]{11})捕获了11位电话号码然后通过\2-\1交换了顺序。这个命令同时用到了分组捕获、反向引用和扩展正则是sed高手的标准操作。还有一个常用技巧是利用符号引用整个匹配内容。比如把所有数字前后加上括号sed s/[0-9]*/()/g file.txt代表整个匹配到的字符串。这个在批量给代码里的数字加单位时很好使。4. 复杂场景的高级应用多命令组合与跨行处理很多命令单独用没什么难度难的是把它们组合起来解决复杂问题。这一节我讲讲怎么在sed里写多个命令、怎么处理跨行内容以及怎么用sed串联整个Linux命令行工具链。4.1 多命令组合的三种写法分号分隔sed s/old/new/; s/foo/bar/ file多个-e参数sed -e s/old/new/ -e s/foo/bar/ file大括号分组sed /pattern/{s/old/new/; s/foo/bar/} file第三种写法很灵活。大括号里可以写多个命令前面加上地址限定后只有匹配的行才会执行这些命令。这种写法在配置修改场景里极其常见比如我只想修改某个特定配置段里的项sed /server_name old-site.com/,/}/s/old-site.com/new-site.com/g nginx.conf这条命令的意思是在匹配到server_name old-site.com到下一个}之间这个范围里把所有old-site.com替换为new-site.com。它把地址范围、正则匹配和替换命令完美组合在一起一条命令就完成了原本用编辑器打开、查找、逐个修改的工作。4.2 跨行处理N、D、P命令的配合标准的sed是单行处理的但有些场景必须处理跨行内容比如合并多行、删除连续重复行。这时候就需要用到N、D、P这组命令。N将下一行读入模式空间拼接到当前内容后面用换行符分隔P打印模式空间中第一个换行符之前的内容D删除模式空间中第一个换行符之前的内容然后重新执行命令一个经典例子是合并两行sed N; s/\n/ / file.txtN把下一行拼到当前行后面此时模式空间里有两行内容用换行符连接s/\n/ /把这个换行符替换成空格两行就合并成一行了。还有一个非常常用的场景删除连续重复行。类似uniq命令的功能但uniq只能处理相邻行而sed配合N也一样sed $!N; /^\(.*\)\n\1$/!P; D file.txt这条命令的逻辑是如果当前行的内容与下一行相同则不打印否则打印当前行。最后用D删除当前内容再读下一行继续判断。这个命令比较绕第一次看可能有点懵建议拆开一行一行分析会对sed的执行流程有更深的理解。4.3 用管道和find配合完成批量处理sed真正威力爆发的地方是跟其他Linux命令通过管道串联成一条流水线。我常用的几个组合# 查看日志里指定时间段之外的错误信息 grep ERROR app.log | sed -n 5,10p # 批量替换目录下所有.conf文件里的旧域名 find /etc/nginx/ -name *.conf | xargs sed -i s/old-site.com/new-site.com/g # 提取进程列表中的PID ps aux | sed -n s/^[^ ]* *\([0-9]*\).*/\1/p其中-i参数是GNU sed的原地修改选项sed -i s/old/new/g file会直接修改文件不输出到屏幕上。这个参数要注意它其实会创建一个临时文件处理完再替换原文件所以文件权限可能会变。如果对权限敏感可以先备份再操作sed -i.bak s/old/new/g file这样会生成一个file.bak备份文件。温馨提示-i很强大也很危险。建议首次批量使用时先不加-i跑一遍看输出结果确认无误后再加-i正式执行。别问我为什么这么提醒说多了都是泪。5. 实操案例全记录从配置修改到日志清洗前面理论讲得差不多了这一节直接上实战。我选三个最常见的真实场景把完整的操作过程和命令都贴出来附上我当时踩过的坑。5.1 案例一批量修改Nginx配置里的域名和端口场景有一台服务器上部署了10个站点每个站点的Nginx配置里都写死了旧域名old-site.com和端口8080现在要全部改成new-site.com和80。第一步先看一眼有哪些配置文件find /etc/nginx/conf.d/ -name *.conf第二步试运行替换命令不写-i看看输出是否正常find /etc/nginx/conf.d/ -name *.conf | xargs sed s/old-site.com/new-site.com/g; s/:8080/:80/g如果输出里所有的域名和端口都正确替换了第三步加上-i正式执行find /etc/nginx/conf.d/ -name *.conf | xargs sed -i s/old-site.com/new-site.com/g; s/:8080/:80/g这里我用分号把两个替换命令合在了一条sed里这样一条xargs就能处理两件事少写一条命令。实际中如果你有更多的替换需求都可以用分号依次追加。第四步检查是否有遗漏grep -R old-site.com\|:8080 /etc/nginx/conf.d/如果grep没有输出说明替换彻底。这个检查步骤必须做因为配置文件里如果写的是old-site.com.cn或者old-site.com:8080/path这种正则匹配范围不对可能就漏了。5.2 案例二日志清洗提取指定时间段的ERROR信息场景服务器上有一个一天的Nginx访问日志大约2GB需要提取出所有包含ERROR且来自IP为192.168.1.100的请求同时把请求中的参数部分去掉只要URL路径。日志格式大概是192.168.1.100 - - [10/Oct/2025:10:30:25 0800] GET /api/user/info?id123nametest HTTP/1.1 500 153 ERROR: database connection failed第一步用sed过滤出指定IP和ERROR关键字的行grep 192.168.1.100 app.log | grep ERROR error.log这个简单粗暴但我想尽量用sed完成因为sed可以边过滤边提取sed -n /192.168.1.100.*ERROR/p app.log error.log第二步提取URL路径。日志里URL格式是GET /api/user/info?id123nametest HTTP/1.1我想把?后面的参数去掉只保留/api/user/info。用sed的分组捕获即可sed -n s/.*GET \([^?]*\)?*. *HTTP.*/\1/p error.log这里\([^?]*\)捕获了从GET后面到问号之间的路径部分。如果URL没有问号?匹配不上正则也能正常工作因为?*表示匹配零个或多个问号。第三步对提取出来的路径做去重统计sed -n s/.*GET \([^?]*\)?*. *HTTP.*/\1/p error.log | sort | uniq -c | sort -rn这个组合拳下来2GB的日志文件处理得干干净净不需要写任何脚本全是用现成的命令行工具配合完成的。5.3 案例三批量生成配置模板每行替换成独立配置场景要给50台服务器生成各自的Nginx配置文件除了server_name和proxy_pass的IP地址不一样其他内容完全相同。我维护一个模板文件server.conf.template然后批量生成。模板内容server { listen 80; server_name SERVER_HOST; location / { proxy_pass http://SERVER_IP:8080; } }生成命令for i in $(seq 1 50); do sed -e s/SERVER_HOST/web${i}.example.com/ -e s/SERVER_IP/10.0.0.${i}/ server.conf.template server_${i}.conf done这里一个大坑是sed命令里的双引号和变量展开。sed -e s/SERVER_HOST/web${i}.example.com/中因为用了双引号${i}会被bash展开成当前循环的数字所以每条命令生成的文件内容都不同。如果你用了单引号${i}就变成字面量所有文件生成出来都一样那可就出大事了。生成完检查一下cat server_15.conf确认替换正确再批量投入使用。6. 常见问题与排查技巧实录写这一节时我把这些年用sed遇到过的奇葩问题都回忆了一遍挑几个频率最高的整理成速查表每个都附上排查思路。问题现象可能原因解决办法替换后只改了每行第一个匹配漏了g标志改为s/old/new/gsed输出中文乱码文件编码不是UTF-8或终端编码不匹配用file命令检查编码加-i转码或用iconv转码Mac上sed运行报错Linux上正常BSD sed和GNU sed参数不一致区分现状-i后必须跟后缀sed -i -E替代-r带特殊字符的内容替换失败/、、\等字符未转义改用其他分隔符或用引用或对特殊字符加\转义大批量操作误删了行地址范围写错或没有先试运行先不加-i试跑或加-i.bak备份处理大文件时感觉很慢没有用-n输出太多无用内容加-n关闭默认输出只打印需要的行6.1 sed输出乱码问题怎么排查Linux sed 打印出来有乱码在热搜词里出现了说明不少人遇到过。这个问题的根源通常是文件编码和终端编码不匹配。排查思路分三步第一步用file命令确认文件编码file app.log如果输出是UTF-8 Unicode text基本可以排除文件编码问题。如果输出里有ISO-8859或GB2312这类字样说明文件不是UTF-8编码。第二步确认终端编码。在Linux终端里可以用locale命令查看当前语言环境echo $LANG如果LANG是zh_CN.UTF-8但文件是GBK编码打印出来自然乱码。这时候要么用iconv转码要么先用sed处理完再转码。第三步如果是二进制文件夹杂文本导致乱码要先把二进制部分过滤掉。比如sed -n s/[^[:print:]\t]//g; p file.log这条命令会把所有不可打印字符去掉只留可见文本。处理日志乱码时很管用。6.2 特殊字符转义问题速查sed里特殊字符分两类一类是正则特殊字符一类是替换命令特殊字符。正则里需要转义的字符有. * [ ] ^ $ \等。如果匹配内容里有这些字符直接写会有问题。替代方案是用\转义s/\./\\/g用字符集s/[.]/\\/g替换内容里需要特别注意的是符号。在替换内容里代表整个匹配到的内容如果你想把某个字符串替换成包含的字面量必须写\。比如sed s/name/\name/g file这里\name会被解析成字面量name而不是匹配到的内容name。路径替换时推荐用#做分隔符能大幅减少转义sed s#/var/log/nginx/access.log#/data/logs/access.log#g config.conf如果分隔符本身也在替换文本里那就选一个没出现的字符总有一个合适的分隔符。6.3 GNU sed和BSD sed的差异坑Linux系统默认的是GNU sedmacOS自带的sed是BSD版本两者有很多细节差异。跨平台写脚本时经常莫名其妙报错最常见的坑有三个GNU sed支持-i不带后缀直接改文件BSD sed要求-i后面必须跟后缀哪怕后缀是空的也得写sed -i s/old/new/ fileGNU sed用\n在替换内容里表示换行BSD sed不认需要用\加真实换行GNU sed的-r参数表示扩展正则BSD sed用的是-E解决办法是如果脚本要在多平台运行建议直接用-E这个在两边都支持。-i就写成-i.bak顺手还能留备份。如果懒得管差异还有一个土办法在macOS上先装GNU sed用gsed命令就能完全兼容Linux的写法。6.4 大文件处理技巧和性能优化sed处理大文件时有几个优化技巧。第一能不打印就不打印用-n关闭默认输出只输出需要的内容能减少大量IO开销。第二多个命令写在一个sed里比写多个管道快因为少了几次读磁盘的IO。第三用sed s/foo/bar/gw changed.txt bigfile.log可以把匹配到的内容直接写入新文件避免二次grep。另外sed处理超大文件时经常配上head和tail先抽样检查不要直接上全量。比如sed -n 1,1000p bigfile.log sample.log先用前1000行测试命令是否正常工作再全量跑。这比直接对2GB文件跑错了再重来高效太多。7. 绕不开的执行顺序与注意事项写了这么多最后把sed执行过程中容易忽略的细节集中说一下。很多人在多个命令组合时踩坑本质上是没搞懂sed的命令执行顺序。sed读入一行后会从左到右依次执行命令列表里的所有命令。这里的从左到右是按分号或-e的顺序来的。如果一个命令导致模式空间内容改变后续命令处理的是改变后的内容。比如sed s/old/new/; s/new/changed/ file第一句把old替换成new第二句再把new替换成changed。最终old会变成changed而不是先变new再不变。如果你用-e写也一样sed -e s/old/new/ -e s/new/changed/ file这个执行顺序的特性在某些场景下可以利用但更多时候是坑。比如你想做条件替换如果包含A则替换B就得用地址限定sed /A/{s/B/C/} file这个命令只在匹配到A的行上执行替换B到C没有匹配A的行不变。这是最常用的条件处理写法。关于d命令和后续命令的关系前面说过了d执行完会立即跳过剩余命令并读下一行。跟d类似的还有q命令quit执行后直接终止sed整个处理流程。sed 10q file # 打印前10行后退出这个在读取超大文件时只取前几行比head还高效因为head要读完输入流才知道退出而sed 10q在第10行处理完就立刻退出。另外还有个隐藏知识点sed默认在处理完所有行后会输出最后一行即使没有显式用p。所以你写sed s/foo/bar/ file时每行都被输出一次这是默认输出不是p命令造成的。只有用了-n才需要显式p。最后提一点关于-i配合文件名的问题。sed -i后面如果不跟后缀GNU sed直接改原文件不会留下备份。如果跟了后缀比如-i.bak会先生成原文件的备份再改。我个人的习惯是正式操作前一定先跑一遍不带-i的版本看输出确认无误再带-i。跑命令前再在脑子里过一遍执行顺序尤其是多命令组合的别让sed的执行顺序坑了自己。这几年做运维和开发sed是我用得最频繁的命令之一。坦白说它不像Python脚本那样炫酷但胜在轻巧、直接、随处可用——只要是个Linux环境这命令就存在不需要装任何依赖。我见过不少同事一遇到文本处理就上Python其实很多场景一条sed就搞定了根本没必要写一堆脚本。在服务器上排查问题的时候能少装一个依赖就少装一个能一条命令解决就不要写十行脚本这是我觉得sed最值得掌握的理由。如果你刚开始用sed我的建议是别急着背所有命令先把s替换和-n加p打印这两组吃透再逐步扩展d和a/i。日常场景里这几条命令已经能解决八成的问题了。等你需要用得更深入时回头再研究N、D、P这套跨行操作会自然很多。