08 - 搜索与替换
“Know your regex, know your editor.”
8.1 搜索基础
8.1.1 基本搜索
/pattern " 向前搜索
?pattern " 向后搜索
n " 下一个匹配
N " 上一个匹配
* " 搜索单词(光标下,向前)
# " 搜索单词(光标下,向后)
g* " 部分匹配(向前)
g# " 部分匹配(向后)
8.1.2 搜索选项
/pattern/e " 光标停在匹配末尾
/pattern/b " 光标停在匹配开头
/pattern/+2 " 停在匹配下方 2 行
/pattern/-1 " 停在匹配上方 1 行
8.1.3 搜索设置
:set ignorecase " 忽略大小写(ic)
:set smartcase " 智能大小写(scs)— 有大写字母时区分
:set hlsearch " 高亮搜索结果(hls)
:set incsearch " 增量搜索(is)
:nohlsearch " 取消高亮
:noh " 同上
-- Neovim 配置
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.hlsearch = true
vim.opt.incsearch = true
8.2 正则表达式(Regular Expression)
8.2.1 Vim 正则引擎
Vim 有 4 种正则模式:
| 前缀 | 名称 | 说明 |
|---|
| 无 | magic | 默认模式 |
\v | very magic | 接近标准正则语法 |
\m | magic | 等同默认 |
\M | nomagic | 特殊字符需转义 |
\V | very nomagic | 几乎所有字符按字面匹配 |
" 使用 \v(very magic)避免大量转义
/\vfunction\s+\w+ " 标准正则风格
/function\s\+\w\+ " 默认 magic 模式(需要转义 +)
" 使用 \V(very nomagic)搜索特殊字符
/\V$100.00 " 精确搜索 $100.00
/$100\.00 " 默认模式需要转义
8.2.2 常用正则模式
| 模式 | 含义 | 示例 |
|---|
. | 任意字符 | /a.c 匹配 abc, aXc |
* | 0 次或多次 | /ab*c 匹配 ac, abc, abbc |
\+ | 1 次或多次 | /ab\+c 匹配 abc, abbc |
\? | 0 次或 1 次 | /ab\?c 匹配 ac, abc |
\{n,m} | n 到 m 次 | /a\{2,4} 匹配 aa, aaa, aaaa |
^ | 行首 | /^function 匹配行首的 function |
$ | 行尾 | /end$ 匹配行尾的 end |
\< | 词首 | \<word 匹配词首的 word |
\> | 词尾 | word\> 匹配词尾的 word |
\b | 词边界 | \bword\b 匹配完整单词 |
[] | 字符类 | /[aeiou] 匹配元音 |
[^] | 否定字符类 | /[^0-9] 匹配非数字 |
| | 或 | /foo|bar 匹配 foo 或 bar |
\(\) | 分组 | /\(foo\)\+ 匹配 foo, foofoo |
\1 | 反向引用 | /\(.\)\1 匹配连续相同字符 |
8.2.3 字符类(Character Class)
| 模式 | 含义 |
|---|
\d | 数字 [0-9] |
\D | 非数字 |
\w | 单词字符 [a-zA-Z0-9_] |
\W | 非单词字符 |
\s | 空白字符 |
\S | 非空白字符 |
\l | 小写字母 [a-z] |
\u | 大写字母 [A-Z] |
\a | 字母 [a-zA-Z] |
\h | 字母或下划线 |
8.2.4 实用正则示例
" 匹配邮箱
/\v[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}
" 匹配 IP 地址
/\v\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
" 匹配 URL
/\vhttps?://[^\s]+
" 匹配 HTML 标签
/\v\<\zs[^>]+\ze\>
" 匹配函数定义(Python)
/\v^\s*(def|class)\s+\w+
" 匹配中文字符
/\v[\x{4e00}-\x{9fff}]
8.3 替换(Substitute)
8.3.1 替换语法
:[range]s/pattern/replacement/[flags]
| 参数 | 说明 |
|---|
range | 范围(% 全文, 1,10 第1-10行, '<,'> 选区) |
pattern | 搜索模式 |
replacement | 替换文本 |
flags | 标志(g 全局, c 确认, i 忽略大小写, e 无错) |
8.3.2 替换标志
| 标志 | 含义 |
|---|
g | 全局替换(一行中所有匹配) |
c | 逐个确认(confirm) |
i | 忽略大小写 |
I | 区分大小写 |
e | 不显示错误 |
n | 只计数不替换 |
& | 复用上次标志 |
8.3.3 替换范围
:s/old/new/g " 当前行全局替换
:%s/old/new/g " 全文替换
:1,10s/old/new/g " 第 1-10 行替换
:'<,'>s/old/new/g " 可视选区替换
:'<,'>s/old/new/gc " 选区替换(逐个确认)
:.,$s/old/new/g " 当前行到文件末尾
:.,+5s/old/new/g " 当前行及以下 5 行
8.3.4 替换中的特殊字符
| 字符 | 含义 |
|---|
\r | 换行 |
\n | NULL 字节(替换中用 \r 表示换行) |
\t | Tab |
\\ | 反斜杠 |
\0 | 整个匹配 |
\1-\9 | 第 N 个捕获组 |
\u | 下一个字符大写 |
\U | 后续字符大写 |
\l | 下一个字符小写 |
\L | 后续字符小写 |
\e | 结束 \U 或 \L |
& 或 \0 | 整个匹配 |
8.3.5 替换实战
" 基本替换
:%s/foo/bar/g " 全文 foo → bar
:%s/foo/bar/gc " 逐个确认
" 大小写转换
:%s/\v(\w+)/\U\1/g " 全文转大写
:%s/\v^(\w)/\u\1/g " 每行首字母大写
" 行操作
:%s/\v^$/\r/ " 空行后加一行
:%s/\v^\s+$//g " 删除只含空白的行
:%s/\v\n\n+/\r\r/g " 多个空行压缩为一个
" 添加行号
:%s/^/\=line('.').'. '/ " 每行前添加行号
" 删除行
:%g/^\s*$/d " 删除空行
:%v/pattern/d " 删除不包含 pattern 的行
" 交换捕获组
:%s/\v(\w+)\s+(\w+)/\2 \1/g " 交换两个单词
" 引号转换
:%s/"/'/g " 双引号转单引号
" 日期格式转换
:%s/\v(\d{4})-(\d{2})-(\d{2})/\3\/\2\/\1/g " 2024-01-15 → 15/01/2024
8.3.6 替换表达式
" 在替换中使用表达式
:%s/\v\d+/\=submatch(0)*2/g " 所有数字乘以 2
:%s/\v\d+/\=printf('%04d', submatch(0))/g " 数字补零到 4 位
8.4 全局命令(Global)
8.4.1 基本语法
:[range]g/pattern/command
对匹配 pattern 的行执行 command。
8.4.2 常用全局命令
" 列出匹配的行
:g/pattern/p
" 删除匹配的行
:g/pattern/d
" 删除不匹配的行
:v/pattern/d " 等同 :g!/pattern/d
" 复制匹配的行到文件末尾
:g/pattern/t$
" 移动匹配的行到文件末尾
:g/pattern/m$
" 对匹配行执行 Normal 模式命令
:g/pattern/normal @q " 对匹配行执行宏 q
" 在匹配行上方添加文本
:g/pattern/-1append\nNew line here
" 给匹配行添加行号
:g/pattern/s/^/\=line('.').': '/
8.4.3 全局命令实战
" 删除所有注释行(Python)
:v/^\s*#/d " 保留注释行,删除其余
:g/^\s*#/d " 删除注释行
" 删除所有 debug 语句
:g/console\.\(log\|debug\)/d
" 将所有 TODO 行复制到文件末尾
:g/TODO/t$
" 给所有函数添加注释
:g/^def /normal O# TODO: implement this
8.5 vimgrep 与 Grep
8.5.1 内置 vimgrep
:vimgrep pattern files " 搜索文件
:vimgrep /TODO/ **/*.py " 搜索所有 Python 文件中的 TODO
:vim /function/ src/**/*.js " 搜索 JavaScript 文件
" 导航结果
:copen " 打开 quickfix 窗口
:cclose " 关闭 quickfix
:cn " 下一个匹配
:cp " 上一个匹配
:cfirst " 第一个匹配
:clast " 最后一个匹配
8.5.2 外部 grep
:grep pattern files " 使用外部 grep
:grep! -r "TODO" . " 递归搜索
:copen " 查看结果
8.5.3 ripgrep 集成
-- 使用 ripgrep 作为 grep 程序
vim.opt.grepprg = "rg --vimgrep --no-heading --smart-case"
vim.opt.grepformat = "%f:%l:%c:%m"
8.6 搜索高亮管理
:noh " 临时取消高亮
:set nohlsearch " 永久取消高亮
" 或使用映射
nnoremap <Esc> :noh<CR><Esc>
8.7 业务场景
| 场景 | 方法 |
|---|
| 全局重命名变量 | :%s/old_name/new_name/g |
| 删除空行 | :g/^\s*$/d |
| 提取 TODO | :g/TODO/p 或 vimgrep |
| 批量注释 | :'<,'>s/^/#/g |
| 代码审查 | vimgrep + quickfix |
| 格式化日期 | :%s + 捕获组 |
| 去重排序 | :sort u |
8.8 总结
| 功能 | 命令 |
|---|
| 搜索 | /pattern ?pattern * # n N |
| 替换 | :[range]s/pattern/repl/[flags] |
| 全局命令 | :[range]g/pattern/command |
| 文件搜索 | :vimgrep :grep |
| 结果导航 | :copen :cn :cp :cc |
下一步:第 09 章 - VimScript 编程 → 学习 Vim 的脚本语言,编写函数和自动命令。
扩展阅读