拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

sed与vim替换命令全解析:从基础语法到正则与批量实战

sed与vim替换命令全解析:从基础语法到正则与批量实战 上周帮同事处理一批配置文件几十个文件里的路径前缀都写错了我第一反应不是打开编辑器一个个改而是直接写了一条sed命令批量替换。等替换完同事问我“sed和vim里的替换到底怎么用”我才发现很多人对这两个工具的替换功能其实停留在“会按回车”的阶段——s/old/new/ 敲下去运气好替换成功运气不好文件乱套然后一脸懵地按撤销。这篇文章就把 sed 替换文字 和 vim 替换文字这两件事从头到尾讲透基础语法、正则支持、分组引用、范围控制、常见翻车现场以及“到底该用哪个”。内容不多废话都是实操里能直接上手的。1. 先把替换这件事说清楚sed和vim分别解决什么问题1.1 两个工具的共同点与分工sed 和 vim 都是 Linux/macOS 终端环境里跟文本打交道的经典工具替换功能也是它们最常用的能力之一。但两者定位完全不同sed 是流编辑器它像一条流水线把文本一行一行读进来、处理完再输出适合“批量改文件”“在脚本里自动处理文本”这类场景。vim 是交互式编辑器它把整个文件加载到缓冲区里你可以在界面里即时看到替换前后的效果适合“打开文件手动校对着改”这类场景。我个人的习惯是如果我知道要改什么、规则是确定的而且涉及多个文件就用 sed一条命令跑完收工如果我不确定匹配要覆盖哪些内容或者需要边看边确认就打开 vim 用替换命令配合可视化确认来操作。两者不是竞争关系而是互补关系。1.2 为什么替换不是简单的“查找-替换”不少人在编辑器里用惯了 CtrlH 那种“查找-替换”到了命令行就觉得 sed 和 vim 的替换语法很绕。其实它们的底层逻辑是一致的你要告诉工具三件事——在哪一行范围内操作、匹配什么内容、替换成什么内容。不同之处在于表达方式。sed 的命令形态是 s/旧内容/新内容/ 系列参数vim 则是在冒号命令模式下用 :s/旧内容/新内容/ 系列参数。任何超出“固定字符串替换”的需求比如忽略大小写、只替换第 5 行到第 10 行、把匹配到的 URL 提取出来换个域名重新拼装都依赖对这两个命令的细节理解。这也是为什么我说“替换”值得单独写一篇拆解——它背后是正则、范围控制、特殊符号处理的一整套体系。2. sed替换的核心字段s/旧文本/新文本/里每个细节都是学问2.1 最基础的形态一次替换与全局替换sed 替换最基础的形式是sed s/old/new/ file这条命令的含义是把文件中每一行第一次出现的 old 替换成 new然后把处理后的全部内容输出到屏幕。注意两点它不会修改原文件只是把处理结果打印到标准输出。想真正改文件必须加 -i 参数后面专门说。默认情况下一行里如果有多个 old只会替换第一个。想要把每行所有匹配都替换掉需要加 gglobal参数sed s/old/new/g file实战里我几乎总是带 g因为大部分场景要的是“全部替换”而不是“一行换第一个”。不带 g 导致修改不彻底的情况我见过太多次了。2.2 指定行范围不用打开文件也能精准定位sed 的替换可以限定在指定行内语法是在 s 前面加上行号或范围。这是个非常实用的能力比如你只想改 3 到 5 行sed 3,5s/old/new/g file还可以用 $ 表示最后一行sed 2,$s/old/new/g file表示从第 2 行到文件末尾全部替换。也可以配合正则匹配行范围。比如只替换包含 error 的行里的 oldsed /error/s/old/new/g file这个写法在批量处理日志、配置文件时特别好用。比如我改 Nginx 配置时只想动 server_name 相关的行就可以写sed /server_name/s/example\.com/new-domain\.com/g file2.3 正则加持让替换从“固定文本”升级为“模式匹配”替换命令里最强大的部分就是正则表达式。sed 默认用的是基础正则表达式BRE几个关键符号如下符号含义示例.匹配任意单个字符s/hel.o/hello/*匹配前一个字符重复任意次含 0 次s/abc*/ab/^匹配行首s/^#//去掉行首的井号$匹配行尾s/$/;/在行尾加分号[abc]匹配方括号内任一字符s/[Tt]he/the/g[^abc]匹配不在方括号内的字符s/[^0-9]//g删除非数字\匹配前一个字符一次或多次需转义s/ \/ /g把多个连续空格压成一个\?匹配前一个字符零次或一次需转义s/colou\?r/color/这里容易踩坑的是加号和问号在 sed 的基础正则里它们必须写成 和 ? 才是“数量”的意思单独写 和 ? 只是字面字符。我刚用 sed 时在这里栽过跟头后来习惯了就好。匹配行首井号这个例子是配置管理里非常经典的操作。把文件里的注释行取消注释或者反过来把某行注释掉sed s/^#\(.*\)/\1/ file # 取消注释 sed s/^\(.*\)/#\1/ file # 添加注释这已经涉及分组引用了下一节展开。2.4 分组与后向引用把匹配片段搬进替换结果如果说有什么 sed 技巧让替换能力上升一个台阶那一定是分组引用。被 ( 和 ) 括起来的内容会被记住然后在替换文本里用 \1、\2 等来引用。举个实际例子把形如 foo:bar 的内容调换顺序为 bar:foo可以写echo foo:bar | sed s/\(.*\):\(.*\)/\2:\1/输出 bar:foo。这个操作如果不用分组几乎无法干净利落地实现。还有一个经常用到的符号是 它代表“整个匹配到的内容”。比如要给文中所有数字两边加方括号sed s/[0-9]/[]/g file 会自动替身为匹配到的那个数字。分组引用在批量改域名、改路径前缀、改代码里的命名前缀时特别实用。比如把 aaa_version 全部改名为 bbb_versionsed s/\(aaa\)_version/\1_extra/g file当然这个例子直接用 s/aaa_version/aaa_extra/g 也可以但当你需要同时保留多个不同前缀时分组就不可替代了sed s/\(foo\|bar\)_version/\1_extra/g file这条命令会把 foo_version 和 bar_version 分别变成 foo_extra 和 bar_extra而不会误伤其他内容。2.5 分隔符的灵活切换避开与路径的冲突sed 的替换命令默认用 / 作为分隔符但替换内容本身就可能包含大量斜杠比如文件路径。直接硬写会变成一场转义灾难sed s/\/home\/user\/data/\/data/ file看着就头疼还容易写错。sed 支持切换分隔符最常见的做法是用 | 或 #sed s|/home/user/data|/data|g file sed s#/home/user/data#/data#g file这一招在处理路径、URL 时能救命。我改成|之后替换路径字符串就再没出现过“斜杠地狱”。只要你用得顺手几乎任何字符都能当分隔符前提是分隔符不出现在被替换的内容和替换结果里。2.6 直接修改文件-i参数与备份策略sed 默认不修改原文件这其实是个安全机制。想真正原地修改用 -i 参数sed -i s/old/new/g file但我强烈建议在用到 -i 时先备份。GNU sed 的写法很直接sed -i.bak s/old/new/g file这会在原文件旁边生成一个 file.bak 备份文件替换失败时还能回退。有一点要特别注意macOS 自带的 BSD sed 对 -i 的语法要求和 GNU sed 不同。BSD sed 里-i 后面必须跟着备份后缀不能直接写 -i 单独使用# macOS BSD sed sed -i.bak s/old/new/g file而 GNU sed 允许单独写 -i# GNU sed sed -i s/old/new/g file跨平台场景下我一般统一写成 -i.bak这样两边都能跑。3. vim替换的命令体系从单行修正到全文批量操作3.1 行内替换与全文替换的基本命令vim 的替换命令在普通模式下按冒号进入命令模式核心命令也是 s。它的基础形式和 sed 非常相似:s/old/new/这表示把当前光标所在行第一个匹配替换掉。如果当前行有多个匹配也默认只替换第一个加 g 才替换当前行全部:s/old/new/g全文替换则是在 s 前加上 % 范围符号:%s/old/new/g% 代表整个文件所以 :%s/old/new/g 的含义就是“全文件所有匹配全部替换”。这是我在 vim 里用得最多的替换命令没有之一。一个非常关键的习惯是用 c 参数进入确认模式:%s/old/new/gc加了 c 之后vim 会逐处询问你是否替换。这是一个有效防止“一刀切改错内容”的方法尤其在匹配规则比较宽泛、你又不确定会不会误伤其他代码时gc 几乎是必须的。3.2 确认模式下逐处处理批量替换的安全阀确认模式交互中vim 会显示“替换为: new? (y/n/a/q/l/^E/^Y)”关键按键含义如下y替换这一处继续下一处n跳过这一处继续下一处a全部替换不再询问q退出不再替换l替换这一处后退出^E / ^Y滚动查看上下文实际用的时候我通常先按 n 扫一遍看看匹配位置是否都符合预期遇到该替换的再按 y。对于大文件里的批量修改确认模式给人的安全感是无可替代的——你每一步都能看到 vim 高亮的匹配位置心里有底。3.3 范围替换的多种表达把替换限制在指定区域除了 % 代表全文、不带范围代表当前行vim 还支持灵活的范围表达这和 sed 的范围思路很相似范围写法含义:5,10s/old/new/g第 5 行到第 10 行:.,5s/old/new/g当前行起向下 5 行:a,bs/old/new/g标记 a 到标记 b 之间:0,$s/old/new/g整个文件等价于 %s标记范围这个功能在日常编辑里很实用。比如你在一段很长的函数里先移到起始位置按 ma 打一个标记 a再移到结束位置按 mb 打标记 b然后执行:a,bs/old/new/g就只在标记范围内替换不会影响文件其他部分。有了这个能力复杂文件里的定向修改就变得很从容。3.4 vim正则的特点与匹配跳转vim 的正则体系默认是 magic 模式和 sed 的基础正则大体相似但有几个值得注意的差异vim 里 表示一次或多次例:%s/ \/ /g压缩连续空格。\d 表示数字:%s/\d\/N/g把所有连续数字替换成 N。匹配单个字符时 . 用法一致匹配任意包括换行的情况则比较麻烦一般配合 _. 使用。在一对替换里vim 中的 \r 表示换行符sed 的替换文本里则用 \n 表示换行。这个差异很隐蔽但一旦遇到多行替换就会分分钟踩坑。vim 还有两个好用的大小写控制标记\c 表示忽略大小写\C 表示区分大小写。把 \c 放在匹配模式的任意位置即可:%s/old/new/gc\c这会忽略大小写匹配整个文件里的 old、Old、OLD 等。配合替换操作的还有快速跳转在普通模式下按 / 进入搜索按 n 跳到下一处匹配。配合上面的确认替换你可以先按 /old 回车一个个 n 扫过去观察匹配情况再决定用哪种替换策略。这比直接贸然执行替换要稳妥得多。3.5 实操场景在vim中替换文件路径并跳转打开很多人搜过“vim 打开文件里引用的文件路径”大概是想解决这样一个问题你在 vim 里看一个配置文件里面写着其他文件的路径你想直接打开那个文件或者批量修改这些路径。先说说怎么打开把光标移动到路径上按 gf 就能直接打开光标下的文件路径。vim 会按 include path 搜索这个文件。这个操作本身不是替换但它是处理路径类内容时非常常见的前置动作。再说如何替换路径。比如文件里到处是/var/www/html/old-site想全部改成/var/www/html/new-site直接执行:%s#/var/www/html/old-site#/var/www/html/new-site#g注意这里直接用 # 做分隔符省去对斜杠逐个转义的痛苦。用 gf 先确认路径真实存在再用替换命令批量修改这组合在迁移配置、改项目目录时相当顺手。4. 同一个替换需求sed和vim各自怎么选4.1 选sed的场景批量文件、脚本流程、大文件流式处理sed 的优势从来不是“交互体验”而是“批量、自动、不占内存”。它天然适合以下场景一次修改多个文件配合 find、xargs 或者 for 循环一条命令扫完几十个文件。写入脚本流水线sed 和其他命令通过管道组合直接在流程中处理文本不需要打开编辑器。处理超大文件sed 是流式逐行处理内存占用可控几十 GB 的日志也能硬啃。无人工干预的定时任务备份、清洗数据、日志脱敏都能交给 sed 自动跑。比如批量把 logs 目录下所有 txt 文件里的 IP 替换成脱敏地址find logs -name *.txt -exec sed -i.bak s/[0-9]\\.[0-9]\\.[0-9]\\.[0-9]\/IP_REMOVED/g {} \;这种活用 vim 手动做会崩溃sed 秒级解决。4.2 选vim的场景交互确认、即时预览、精细化编辑vim 的优势在于“人参与判断”。当替换规则模糊、需要上下文确认时vim 远胜 sed。我通常在以下情况打开 vim替换前想先搜索一遍看匹配范围是否合理。替换后想立刻看到上下文语境确认语义没变。匹配规则太宽怕误伤需要 gc 逐处确认。只改文件里某个区块且行号不固定需要标记或可视化选中。举例你要把代码里某个变量 oldName 改名 newName但这个变量名出现在注释、字符串、日志输出等不同语境里有的该改有的不该改。这时候 vim 的 gc 确认模式就是最佳选择——你可以在每一处匹配位置判断。:%s/oldName/newName/gc4.3 能力对照与衔接先用sed批量再用vim精修实际操作里sed 和 vim 常常不是二选一而是接力配合。我的典型工作流是先用 sed 做粗糙但快速的批量替换把确定要改的部分一次搞定再打开 vim 用搜索和确认模式把剩下模糊的地方逐步精修。举个例子迁移一批旧项目配置域名根路径已确定要全部替换但某些专属路径要不要改还需要人工判断。我的操作是sed -i.bak s/old-domain.com\//new-domain.com\//g config/*.conf vim config/nginx.conf进 vim 后执行 :%s/special-path/new-special-path/gc 逐处确认。这样正文里那种“既要批量又要精细”的需求就被很好地兼顾了。5. 替换实战中最容易翻车的几个细节5.1 分隔符冲突最经典的翻车现场把包含 / 的内容直接塞进 s/.../.../ 里而不转义是 sed 和 vim 新手最常见的错误。比如想把 /usr/bin 改成 /bin写sed s//usr/bin//bin/g filesed 直接报错或者行为异常。正确的做法是换分隔符或者对斜杠转义。我现在习惯无脑选用 # 或 |尤其处理路径和 URL 时基本不会出问题。5.2 在替换文本里代表整个匹配在替换文本里 会被替换成整个匹配的内容。这个特性有时候是惊喜有时候是惊吓。比如你想把 abc 替换成 abc123写了sed s/abc/abc123/g file这没问题。但如果你写sed s/abc/123/g file结果是一样的 自动代表了 abc。反过来如果你想把 作为普通字符写进替换文本需要转义为 。5.3 贪婪匹配与行内多处的边界正则里的 .* 默认是贪婪的会尽量匹配更多内容。这在替换里经常导致“替换范围超出预期”。典型的例子是想把...里的内容替换掉写sed s/title.*\/title/title新标题\/title/ file如果一行里有两个 title 标签贪婪匹配会从第一个一直吃到最后一个。要限制匹配范围可以用排除字符集之类的写法。这是正则替换最深的坑没有之一。5.4 macOS与Linux上sed -i的差异macOS 自带的是 BSD sedLinux 发行版上默认是 GNU sed。两者在 -i 参数的语法上不兼容上面已经提过。这里再强调一遍在 macOS 上sed -i s/old/new/g file会直接报错必须写sed -i.bak或sed -i 空后缀。跨平台的脚本里为了兼容最好统一写成sed -i.bak s/old/new/g file然后处理完把备份文件删掉。这样 Linux 和 macOS 上都能正常跑。5.5 替换前先确认、备份、可回滚最后一条经验也是最重要的一条替换前永远确认“你知道自己在做什么”。我给自己的规矩是三条先不写 -i直接跑一遍 sed把输出结果用 head 或 grep 抽查几行确认替换符合预期。使用 -i 时带上备份后缀给文件留一条退路。用 vim 做批量替换时尽量加 c 确认模式而不是甩手一个 :%s/.../.../g 就完事。这三条规矩帮我避免过无数次“改完了才发现替换过度”的惨剧。文本替换是件看起来很基础的小事但用不好造成的影响可以很大。养成谨慎的操作习惯比记住所有语法细节更值钱。
返回列表