做运维这些年,天天跟日志、配置文件、批量文本处理打交道。要说Linux命令行里最趁手的工具,shell三剑客——grep、sed、awk——绝对是绕不开的三座大山。很多刚入行的朋友问我,三剑客先学哪个?我的答案一直是:先啃sed。为什么?因为grep解决的是“找出来”,awk解决的是“算清楚”,而sed解决的是“改明白”。日常运维里,批量替换配置、提取日志关键段、按格式重排文本,sed一个命令全搞定,而且是流式处理,处理几十万行的文件也不会卡死。今天我就把sed的学习心得掰开揉碎讲一遍,从执行原理到实战命令,每一个细节都会讲到。
这篇内容我按自己当年带新人的思路组织,先讲清sed的工作机制,再讲高频用法,最后给出一堆踩坑经验和面试实战题。适合刚接触Linux运维的朋友建立知识骨架,也适合有几年经验但一直停留在sed 's/old/new/g'水平的老哥补一补内功。
1. sed在运维工具箱里的位置
1.1 三剑客的分工
先搞清楚sed在三剑客中到底处于什么位置。grep偏向“查”,特别适合从文件或命令输出中过滤出匹配行;sed偏向“改”,可以在“查”的基础上做替换、删除、插入、追加;awk偏向“算”,按列拆分会更顺手,还能做条件判断和循环。很多场景三者可以互相配合,比如先用grep过滤出关心的行,再用sed批量修改,最后用awk统计结果。
回到实际运维场景,我最常用sed的地方集中在:批量修改nginx或MySQL配置文件、把程序日志里的时间戳格式批量转换、给几百台服务器的hosts文件统一加一条解析记录。这些任务的共同特点是,文件大、改动规则统一,用编辑器一个个手动改根本不现实,sed一条命令扫过去,几秒钟完事。
还有一个关键优势:sed对文件的修改可以做到“非交互”。写脚本批量处理服务器配置时,交互式编辑器完全没法用,sed天然就是为这种自动化场景设计的。你可以把它安稳地嵌在shell脚本里,配合for循环或while循环,一次性处理一批服务器。
1.2 什么场景下优先选sed
有些朋友一上来就学awk,觉得awk功能强大什么都能干,结果学了一个月还在懵。我的建议是,具体问题具体分析。如果任务是“把第N行改成XXX”、“把匹配到某关键字的行后面插入一段内容”、“删除从A到B之间的所有行”,这类偏向对列和格式进行精细控制的任务,用awk显得杀鸡用牛刀,sed反而干净利落。
再举个例子,你有一个几百MB的应用日志文件,里面每条日志都以JSON格式输出,但偶尔会有堆栈信息把一行撑成了几百行甚至上千行,这时候你可以利用sed的范围匹配,把“从Exception开始到}结束”之间的所有行合并处理。这种能力,awk做起来比较绕,sed则顺手得多。
一句话总结:遇到“按行定位、按规律修改”的任务,优先想sed。遇到“按列提取、按条件聚合”的任务,再换awk。这不是说谁取代谁,而是让每个工具在合适的场景里发挥最大价值。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先把sed的执行机制吃透
2.1 按行处理的工作模型
学习sed最大的坎不是命令记不住,而是不理解它“一次读一行”的工作模型。很多人写sed命令时脑子里还停留在“整个文件一次读入内存再处理”的思维,于是写出来的命令总是达不到预期。
sed的实际工作流程是这样的:它从输入流中逐行读取内容,每次读一行,去掉末尾的换行符后放入一个叫“模式空间”(pattern space)的临时缓冲区,然后执行你给定的命令脚本,处理完毕后把模式空间的内容输出到标准输出(除非用了-n抑制输出),再读取下一行,如此循环往复,直到文件结束。
你可能会问,这跟“一次读全部”有什么区别?区别很大。流式处理让sed无论面对多大的文件,内存占用都几乎恒定,这是它能在生产环境处理超大型日志的底气。另一个重要启发是:你在sed命令里对每一行的处理是独立的,想跨行操作就必须借助范围地址或保持空间。理解了这个模型,很多奇怪现象就能解释通了。
举个很典型的例子,为什么sed 's/foo/bar/'只替换每一行的第一个foo?因为sed是逐行处理的,处理完当前行就输出,然后读下一行,并不会跨行去“记住”之前替换过几次。同一个模式空间内,默认确实只替换第一个匹配项,除非加上g修饰符。
2.2 地址定界:sed的瞄准镜
sed命令的精髓在于“地址定界”,说白了就是你得告诉sed到底对哪些行动手。地址可以是行号、正则表达式、行号范围,甚至可以用步进方式表示“每隔几行”。
先看最简单的行号定位。sed -n '3p' file表示只打印第3行;sed -n '3,8p' file表示打印第3到8行。sed '3d' file表示删除第3行并输出其余内容。行号定位直接明了,适合脚本里对固定行操作。需要注意,sed支持$表示最后一行,比如sed -n '$p' file打印最后一行。
再用正则定位。sed -n '/error/p' app.log表示打印所有包含error的行。正则地址用两个斜杠包起来,写法跟grep基本一致。这里有个常用技巧,/^#/匹配注释行,/^$/匹配空行,处理配置文件时特别常用。如果你要操作“从匹配到begin的行开始,到匹配到end的行结束”这一整段,可以用/begin/,/end/这样的范围表达式。这个能力在做代码块抽取时是神器。
更进阶的是步进寻址。sed -n '1~2p' file表示从第1行开始,每隔2行取一行,也就是打印奇数行;sed -n '2~2p' file打印偶数行。这类“每隔N行处理一次”的需求,在抽样查看大日志时特别实用。
2.3 模式空间与保持空间
很多教程把sed的两个“空间”概念放在高级部分,但我建议一开始就建立认知,否则学到多行处理时容易卡壳。前面说的模式空间是主要工作区,每一行都在这里被处理。保持空间(hold space)则是一个“暂存区”,你可以把当前行的内容复制或追加进去,后面再取出来用,相当于sed的便签本。
常用命令包括:h把模式空间内容复制到保持空间(覆盖),H把模式空间内容追加到保持空间(换行拼接),g把保持空间内容复制回模式空间(覆盖),G把保持空间内容追加到模式空间(换行拼接),x交换两个空间的内容。
我举一个最简单也最常见的场景:倒序打印文件。sed -n '1!G;h;$p' file的输出正好是原文件的倒序。原理就是每读一行,先把当前行追加到保持空间已有的内容上方,最后一行时把累积内容打印出来。虽然实际中用tac更简单,但理解这个过程能帮你真正掌握sed的机制。
再比如把连续的两行合并成一行。可以用N命令把下一行读入模式空间并拼接到当前行后面(用换行符隔开),然后替换掉换行符。sed 'N;s/\n/ /'就能把每两行合并成一行。这类需求在整理格式错乱的配置文件或处理多行日志时经常出现。
3. sed日常增删改查实操
3.1 查:给sed加上“安静模式”
查是sed最基础的功能。很多新手会问:查内容不是grep更合适吗?确实,但如果你的查询只是后续处理的前奏,或者查询条件比较复杂,sed完全可以胜任。
聪明的做法是先给sed加上-n参数,这表示“不要默认输出每一行”,只输出你明确要求打印的行。不带-n时,sed会把所有行都输出一遍,被命令处理过的行会额外输出一次,结果经常是重复内容,新手容易懵。
实际使用中,sed -n '/keyword/p'就等于一个功能精简版的grep。但它厉害的地方在于可以和后续命令组合。比如sed -n '/^ERROR/p' app.log | head -50,查看日志中前50条ERROR记录,一条管道命令就完成了。
如果要打印某个区间的内容,比如“从第100行到第200行,并且只保留包含error的行”,可以这样写:sed -n '100,200{/error/p}'。这里的花括号表示“对前面地址范围内的每一行,执行括号内的命令”。这个写法在真实运维中非常实用,定位日志的某个时间段的报错信息时经常用到。
3.2 替换:s命令的各种玩法
替换是sed使用频率最高的功能,没有之一。基本格式是sed 's/原内容/新内容/标志位'。默认情况下,替换动作只作用于每一行匹配到的第一个内容;加上g标志后,才会替换该行所有匹配项。
举一个运维中的高频场景,批量修改配置文件里的IP。假设一个配置文件里有多处192.168.1.10需要改成192.168.1.20,一条命令搞定:sed -i 's/192.168.1.10/192.168.1.20/g' config.ini。这里的-i表示直接修改原文件,不加-i只是把修改后的内容输出到屏幕,原文件不受影响。我在教新人时,会特别强调先不加-i跑一遍看看输出,确认无误后再真正落盘,这个习惯能避免太多悲剧。
替换内容里常常会出现正则的元字符,比如.、*、[、/等等,这时候就涉及到转义。一个常见坑是路径替换,因为你习惯用/作为sed的命令分隔符,而路径里也全是/,转义起来能疯掉。解决办法是用另一个符号作为分隔符,比如sed 's#/usr/local#/opt#g'。除了#,你也可以用@、:等符号,只要紧跟在s后面出现三次即可。这个技巧非常实用,强烈建议记牢。
再来一个进阶用法:在替换时引用匹配到的内容。用&表示“匹配到的整个部分”,或者用\(和\)把某部分括起来,用\1、\2引用。比如把每行开头的第一个单词用大括号包起来:sed 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)/{\1}/'。这类操作在批量处理代码、生成文档、调整日志输出格式时非常有用。
3.3 删与插:增删行的正确姿势
删除用d命令。sed '5d' file删除第5行,sed '/debug/d' file删除所有包含debug的行。删除还能和范围一起用,比如sed '/<config>/,/<\/config>/d' file可以删除配置文件里从config开始到config结束的整个配置区块。
往文件里加内容用i(在匹配行之前插入)和a(在匹配行之后追加)。比如给nginx配置中所有server块之前加一行注释:sed -i '/server {/i # add by ops' nginx.conf。这个命令会在每个匹配server {的行之前插入注释。如果想在某些行之后追加多行内容,可以用\n来换行,例如sed '/listen 80;/a\ listen 443 ssl;\n ssl_certificate /etc/nginx/ssl/server.crt;' nginx.conf。
还有c命令用来“整行替换”,把匹配到的行整体替换成新内容。sed '/^#disable_auth/c\disable_auth=true' config.properties会把以#disable_auth开头的注解行直接改为配置项。对于各种“注释/取消注释”的切换场景,c命令比先删后加要高效得多。
这里有一个替换和插入的区别需要理清:s命令是“行内修改”,动的是行内容;a/i/c是“行级操作”,增删的是整行。写脚本时选错命令,结果会差得很远。
4. 进阶用法与脚本化
4.1 用sed脚本处理多步骤任务
当你要对文件做一系列操作时,可以把多条sed命令写在一个脚本文件里,用-f指定。比如有一个脚本fix.sed,里面写:
code复制s/old_ip/new_ip/g
s/old_domain/new_domain/
/^#/d
然后执行sed -f fix.sed config.ini,三条规则就会依次作用于每一行。这种方式的好处一是可复用,二是易于维护。我在批量调整多台服务器配置时,经常把sed规则写成文件,通过版本管理统一分发。
sed脚本里的多条命令还可以直接用分号分隔写在一行上,比如sed -n '/error/{s/ERROR/WARN/;p}'——先找到包含error的行,把ERROR改成WARN,再打印出来。这种写法用花括号把多条命令组合在一起,实现了类似“if条件里的多步骤操作”效果。实际调试时会发现它非常强大,可以代替很多临时脚本。
还有一个小细节,sed脚本中的#开头表示注释行,可以用来写说明。但要注意,如果你在命令行里写的规则恰好以#开头,sed会把它当成注释忽略掉。这是一个容易踩的暗坑,记住了能省很多排查时间。
4.2 -i参数的安全使用
-i参数是sed的“落盘”选项,直接修改文件。这个参数简单粗暴,但也暗藏危险。最典型的问题出现在跨平台环境:Linux下sed -i可以直接用,但macOS(BSD sed)下用sed -i会报错,必须写成sed -i ''。我在管理混合环境脚本时,一般会在脚本里先判断操作系统,再决定调用方式,或者干脆用sed -i.bak总没错,它会在修改前自动生成一个.bak备份文件。
我的习惯是用-i.bak而不是裸-i。原因是:如果替换规则写错,还能从.bak文件恢复;批量生成备份也能方便对比改动前后差异。确认无误后,再清理备份文件即可。
还有一点必须提醒:千万不要在未做备份的情况下对系统关键文件直接sed -i操作。比如/etc/fstab、/etc/passwd这种文件,一旦改错,服务器可能直接起不来。线上操作前,先复制一份到临时目录,或者至少用-i.bak留后路。
4.3 正则里的转义层级
sed的正则转义算是学习中的一个硬骨头,因为涉及到“两层转义”的概念。Shell会把双引号内的内容先做一轮解析,比如$、反引号、反斜杠这些符号有特殊意义;然后sed才会解析正则语法里的转义。
一个经典场景是匹配IP地址。你可能想用[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+这种方式匹配,但在sed的BRE(基础正则表达式)模式下,+需要写成\+。所以实际写法是sed -n '/[0-9]\+\.b[0-9]\+\.b[0-9]\+\.b[0-9]\+/p' file,注意这里的点也要转义。
搞清楚BRE和ERE的区别能省掉大量调试时间。sed默认用BRE,括号要转义\(和\),加号要转义\+,问号要转义\?,大括号要转义\{和\}。如果你用sed -E,就会切换到ERE模式,此时正则写法跟Perl风格更接近,+、?、()都不需要转义。我更推荐在复杂脚本里加上-E,可读性会好很多。
转义层级问题在实际写脚本时经常演变成“为什么我在命令行能跑,放到脚本里就挂了”。原因往往是脚本里用了双引号包裹整个sed命令,而双引号内的反斜杠被Shell吃掉了一层。解决办法是把sed命令放到单引号里,让Shell完全不管里面的内容。这是我在教新人时最强调的一个原则:sed规则尽量用单引号包裹。
5. 避坑指南与常见问题
5.1 踩坑实录
这些年积累下来,sed相关的问题真不少,我把最常见、最坑的几个列成一个速查表,方便大家定位。
| 现象 | 原因 | 解决办法 |
|---|---|---|
| 替换不生效 | 没有加g,只替换了行内第一个匹配项 |
需要全局替换时加g标志 |
| 路径替换时转义混乱 | 用了/作为定界符,跟路径冲突 |
改用#或@作为定界符 |
| 文件被改坏,原内容丢失 | 裸sed -i且规则写错 |
用sed -i.bak保留备份 |
| 命令在Linux可以,macOS报错 | BSD sed和GNU sed语法差异 | 按OS区分,或统一用-i.bak |
| 双引号里的规则执行异常 | 反斜杠被Shell先行解析 | 规则改用单引号包裹 |
| 匹配中文字符串失败 | 正则没有加-E或字符集不支持 |
优先用-E模式,或配合grep做前置过滤。 |
除了表格里的情况,我再分享一个高频翻车案例。有一次我要把某个配置文件里的注释行批量取消注释,文件里的格式是# option=true,我需要去掉行首的#和后面的一个空格。我写成了sed -i 's/^#//',结果空格还在,变成 option=true,程序解析直接报错。最终改成sed -i 's/^# //'才解决。这类问题在于对文件格式的细节观察不够,用cat -A查看行尾和隐藏字符是个好办法。
还遇到过一个问题:处理包含特殊字符的替换内容,比如密码里的&。在sed替换中,&是特殊符号,代表“匹配到的内容”,所以如果你要替换的字符串里包含&,必须写成\&才不会出错。假设要把配置里的占位符__PWD__替换成abc&123,命令要写成sed 's/__PWD__/abc\&123/',这个细节很容易被忽略。
5.2 常见面试场景速查
很多公司面试运维岗都会考sed,我总结了几个出现频率最高的面试题和思路,大家可以自测一下。
第一题:如何删除文件中所有的空行? 答案是sed -i '/^$/d' file,^$匹配空行,d删除。这道题考的是正则和删除动作的组合。
第二题:如何把文件中第3行到第5行的内容整体替换成新内容? 写法是sed -i '3,5c\new content' file。这里的c命令对“整块替换”这个场景特别合适。
第三题:如何只打印第10行到第20行之间的包含error的行? 写法是sed -n '10,20{/error/p}'。这道题考的是地址范围和花括号组合能力,看似基础,但能答上来的候选人说明对sed有一定理解。
第四题:将文件中的每一行开头加上一个Tab缩进,怎么做? 写法是sed 's/^/\t/' file。考的是替换位置锚点^的用法。
第五题:如何查看文件最后5行,但不用tail命令? 可以用sed -n '$-4,$p' file,注意这里使用了$表示最后一行。虽然实际工作中直接用tail,但这道题考验对$地址符的理解。
曾经还有个朋友面试被问:sed 's/^/ /'和sed 's/$/ /'的区别是什么?相信读到这里你已经很清楚了,前者是行首加空格,后者是行尾加空格。搞懂地址锚点,这类问题就是送分题。
关于sed的学习,我最深的体会是:不要把命令死记硬背,而是先理解它的工作模型——逐行读取、模式空间处理、按地址定位,然后再去背命令参数,你会发现一切都有章可循。很多朋友学了三剑客但还是不会用,往往是因为没有在“原理”层面建立认知,直接跳到“技巧”层面,结果技巧一多就混淆了。sed的命令虽然多,但核心模型就一个,模型立住了,千变万化的命令都能拆解成“对哪些行做什么操作”。
如果你正在准备运维面试,或者刚开始学习shell脚本,建议把今天讲的内容挨个在终端里敲一遍,特别是用-n和不加-n的对比、-i前先预览的习惯、以及#作为定界符的路径替换,这三个点是实际工作中最高频使用、也最容易出错的细节。敲完一遍,再用sed脚本批量处理一次真实配置文件,这套基本功就算真正长在身上了。
