☰
awesome-cheatsheets 之 VimScript 语言速查:五分钟掌握 Vim 脚本核心语法与实战要点
2026/10/8 13:04:29 网站建设 项目流程
  • 文档

【免费下载链接】awesome-cheatsheets

超级速查表 - 编程语言、框架和开发工具的速查表,单个文件包含一切你需要知道的东西 :zap:

项目地址:https://gitcode.com/gh_mirrors/awe/awesome-cheatsheets
点击查看免费下载

本文以开源速查表仓库 awesome-cheatsheets 中的 languages/vimscript.md 为核心骨架,系统讲解 VimScript(Vim 脚本语言)的变量与作用域、数据类型、字符串比较、控制流、函数定义以及用字典模拟面向对象等核心知识点。无论你是想读懂别人的.vimrc配置,还是准备编写自己的 Vim 插件,读完本文你都能掌握 VimScript 的语法主干,并学会借助 Vim 内建帮助系统(:h)与交互式命令快速验证和调试脚本。

学习环境准备:内建帮助与交互式调试

Vim 拥有非常丰富的内建文档系统,任何关键词都可以通过:h <关键词>查阅。例如,后续章节中出现的let、function、deepcopy()、ignorecase等,都可以直接输入:h let、:h function、:h deepcopy查看官方解释。与本篇文档配套的 editors/vim.txt 速查表也专门列出了两条与脚本相关的入口:

  • :h eval:脚本编写帮助(VimScript 的完整语法手册);
  • :h function-list:查看 VimScript 内建函数列表。

此外,如果想在编写过程中随时尝试各种 VimScript 表达式,可以在 NORMAL 模式下使用gQ命令进入 VimScript 的交互式环境调试命令,边写边验证,效率远高于反复编辑配置文件后重启 Vim。

最后注意一个约定:下文例子中出现的形如<符号>的记号,表示正式使用时应该被完全替换成真实的内容,包括左右两边的尖括号在内;而单独的<和>在 VimScript 中则被用作比较运算符,二者含义不同,阅读时需加以区分。

变量:声明、删除与作用域

变量的创建与删除

  • let命令用来对变量进行初始化或者赋值;
  • unlet命令用来删除一个变量;
  • unlet!命令同样可以删除变量,但会忽略诸如"变量不存在"之类的错误提示。

也就是说,当你无法确定某个变量是否已经定义、又希望安全地清理它时,应该使用带感叹号的unlet!,避免脚本因报错而中断。

作用域:默认规则与显式前缀

默认情况下,如果一个变量在函数体以外初始化,它的作用域是全局变量;如果它是在函数体以内初始化的,它的作用域是局部变量。同时,你可以通过在变量名前加冒号前缀,明确地指明变量所属的作用域:

g:var - 全局 a:var - 函数参数 l:var - 函数局部变量 b:var - buffer 局部变量 w:var - window 局部变量 t:var - tab 局部变量 s:var - 当前脚本内可见的局部变量 v:var - Vim 预定义的内部变量

各作用域的实际含义如下:

  • g:全局变量,任何脚本、任何函数中都可以访问;
  • a:函数参数,只能在函数内部使用,用于读取调用者传入的实参(详见"函数"一节);
  • l:函数局部变量,只能在声明它的函数内部使用;
  • b:buffer 局部变量,每个缓冲区各自独立,常用于保存与当前文件相关的状态;
  • w:window 局部变量,每个窗口各自独立,常用于分屏场景下的窗口级状态;
  • t:tab 局部变量,每个标签页各自独立;
  • s:脚本局部变量,仅对当前脚本文件可见,是避免命名冲突、实现插件内部"私有状态"的关键手段;
  • v:Vim 预定义变量,由 Vim 自身维护,例如v:true、v:false、v:count、v:version等。

环境变量与选项的读写

除了上述普通变量,VimScript 还提供了两种特殊形式的读写接口:

  • 通过$name的模式读取或改写环境变量,例如let $PATH = $PATH . ':/usr/local/bin'可以在脚本中动态修改 PATH;
  • 通过&option的方式读写Vim 内部设置值,例如let &ignorecase = 1等价于执行:set ignorecase。

这一点与 editors/vim.txt 中大量:set常用设置命令是相通的:速查表里列出的:set number、:set ignorecase、:set hlsearch等,在脚本中都可以写作&number、&ignorecase、&hlsearch的形式参与表达式运算,例如按需保存和恢复用户的设置。

数据类型与真值语义

Number:32 位有符号整数

-123 0x10 0177

支持十进制、十六进制(0x前缀)和八进制(0前缀)三种写法,取值上限为 32 位有符号整数。

Float:浮点数

需要编译 Vim 时带有+float特性支持,否则浮点字面量会出错:

123.456 1.15e-6 -1.1e3

支持科学计数法写法。

String:NULL 结尾的 8 位无符号字符串

"ab\txx\"--" 'x-z''a,c'

双引号字符串支持转义序列(如\t、\"),单引号字符串则不进行转义,且内部的单引号通过连续两个单引号''表示字面单引号。

Funcref:函数引用

函数引用类型的变量名必须以大写字母开头,可以通过function()内建函数取得某个函数的引用,再像调用普通函数一样调用它:

:let Myfunc = function("strlen") :echo Myfunc('foobar') " Call strlen on 'foobar'. 6

List:有序列表

列表可以嵌套,支持正负索引(负数表示从末尾倒数),索引越界会报错,但可以用get()提供默认值:

:let mylist = [1, 2, ['a', 'b']] :echo mylist[0] 1 :echo mylist[2][0] a :echo mylist[-2] 2 :echo mylist[999] E684: list index out of range: 999 :echo get(mylist, 999, "THERE IS NO 1000th ELEMENT") THERE IS NO 1000th ELEMENT

上面的例子说明:mylist[-2]取倒数第二个元素(即2),而访问越界索引会抛出E684错误,因此在不确定索引是否合法时,优先使用get(list, index, default)三参数形式,它会安全地返回默认值而不是报错。

Dictionary:无序的 Key/Value 容器

字典通过花括号字面量创建,键既可以是字符串也可以是数字;取值既可以用中括号dict["key"],也可以用点号dict.key(数字键用dict.999这种形式):

:let mydict = {'blue': "#0000ff", 'foo': {999: "baz"}} :echo mydict["blue"] #0000ff :echo mydict.foo {999: "baz"} :echo mydict.foo.999 baz :let mydict.blue = "BLUE" :echo mydict.blue BLUE

字典的赋值同样支持点号语法,let mydict.blue = "BLUE"会直接修改对应键的值。

真值语义:没有布尔类型

VimScript没有布尔类型,整数0被当作假(false),其他整数被当作真(true)。字符串在比较真假前会被转换成整数,大部分字符串都会被转化为 0,只有以非零数字开头的字符串才会转化为非零:

:echo 1 . "foo" 1foo :echo 1 + "1" 2 :function! TrueFalse(arg) : return a:arg? "true" : "false" :endfunction :echo TrueFalse("foobar") false :echo TrueFalse("1000") true :echo TrueFalse("x1000") false :echo TrueFalse("1000x") true :echo TrueFalse("0") false

从结果可以清楚看到:"foobar"、"x1000"、"0"都转换为 0(假),而"1000"、"1000x"因为以非零数字开头转换为非零(真)。上面的1 . "foo"与1 + "1"还顺便演示了 VimScript 的动态弱类型特性:字符串连接运算符.会把数字隐式转换为字符串,算术运算符+会把数字字符串隐式转换为整数参与运算。

补充一点:可以调用type(varname)获取变量的类型编号;从 Vim 8.1 开始,VimScript 已经包含真正的 Boolean 类型,并提供v:true、v:false两个预定义值,供需要明确布尔语义的场合使用。

字符串比较与连接

字符串比较运算符及其含义如下:

  • <string>==<string>:字符串相等
  • <string>!=<string>:字符串不等
  • <string>=~<pattern>:匹配 pattern(正则)
  • <string>!~<pattern>:不匹配 pattern(正则)
  • <operator>#:匹配大小写
  • <operator>?:不匹配大小写(忽略大小写)

注意:设置选项ignorecase会影响==和!=的默认比较结果。为了不受该选项影响,可以在比较符号后添加?或#来明确指定是否忽略大小写——#表示严格区分大小写,?表示忽略大小写。这正是 editors/vim.txt 中:set ignorecase/:set noignorecase与脚本逻辑联动的地方:交互式搜索可能开着ignorecase,但脚本内部的关键比较应使用=~#这类带修饰符的形式来保证行为确定。

字符串连接使用.运算符,示例:

:function! TrueFalse(arg) : return a:arg? "true" : "false" :endfunction :echo TrueFalse("X start" =~ 'X$') false :echo TrueFalse("end X" =~ 'X$') true :echo TrueFalse("end x" =~# 'X$') false

=~右侧是正则表达式模式:"end X" =~ 'X$'匹配成功返回真;而"end x" =~# 'X$'由于使用了区分大小写的=~#,小写x无法匹配大写X,返回假。这组对比直观展示了正则匹配与大小写修饰符的组合用法。

控制流:条件、循环与异常

条件判断

if <expression> ... elseif <expression> ... else ... endif

注意关键字是elseif(一个词),而不是其他语言常见的else if;endif结束整个条件块。

普通循环

for <var> in <list> continue break endfor

循环体内可以使用continue跳过本轮、break跳出循环。

复杂循环:迭代解构

当列表元素本身是列表时,可以用for [var1, var2] in ...的形式在循环头直接解构多个变量:

for [var1, var2] in [[1, 2], [3, 4]] " on 1st loop, var1 = 1 and var2 = 2 " on 2nd loop, var1 = 3 and var2 = 4 endfor

这一特性在处理成对数据(如键值对、坐标点)时非常实用。

While 循环

while <expression> endwhile

异常捕获

try ... catch <pattern (optional)> " HIGHLY recommended to catch specific error. finally ... endtry

catch后的<pattern>是可选的错误匹配模式,官方建议尽量捕获具体的错误类型(例如catch /E684/),而不是笼统地吞掉所有异常;finally块无论是否发生异常都会执行,适合做资源清理。三者共同构成完整的异常处理链路:try ... catch ... finally ... endtry。

函数:定义、调用与范围操作

使用function关键字定义一个函数,使用function!覆盖一个函数已有的定义(在重复执行配置时不会报错)。函数和变量一样也有作用范围的约束,并且函数名必须以大写字母开头:

function! <Name>(arg1, arg2, etc) <function body> endfunction
  • delfunction <function>:删除一个函数;
  • call <function>:调用一个函数。函数调用前的call语句是必须的,除非该调用出现在一个表达式里(例如let x = Myfunc(1)就不需要call)。

例如,强制创建一个全局函数(使用感叹号),参数使用...这种不定长的参数形式时,a:1表示...部分的第一个参数,a:2表示第二个,如此类推;a:0用来表示...部分一共有多少个参数:

function! g:Foobar(arg1, arg2, ...) let first_argument = a:arg1 let index = 1 let variable_arg_1 = a:{index} " same as a:1 return variable_arg_1 endfunction

注意a:{index}这种"用变量作键名"的取参方式:当索引存在变量中时,可以用大括号表达式动态取到a:1、a:2等参数,这为编写参数数量不确定的通用函数提供了灵活性。

按文本范围调用函数

有一种特殊的调用函数的方式,可以指明该函数作用的文本区域是从当前缓冲区的第几行到第几行。按照1,3call Foobar()的格式调用一个函数的话,该函数会在当前文件的第一行到第三行每一行各执行一遍,在这个例子中,该函数总共被执行了三次。

如果你在函数声明的参数列表后添加一个range关键字,那函数就只会被调用一次,此时两个名为a:firstline和a:lastline的特殊变量可以用于函数内部,表示本次调用覆盖的文本起止行号。

例如,强制创建一个名为RangeSize的函数,用来显示被调用时的文本范围:

function! b:RangeSize() range echo a:lastline - a:firstline endfunction

这是 Vim 中"函数化"处理文本块(如对指定行区间做格式化、统计或批量改写)的标准手段,也是:h function中 range 特性的典型应用。

用字典模拟面向对象

Vim 没有原生的类支持,但是可以用字典模拟基本的类。为了定义类的方法,可以在函数声明时使用dict关键字,将调用它的字典在函数体内暴露为self关键字:

let MyClass = {"foo": "Foo"} function MyClass.printFoo() dict echo self.foo endfunction

这里的function MyClass.printFoo()直接把函数挂到了字典上,dict关键字使得函数体内的self指向MyClass这个字典本身。

类的实现更类似于singleton(单例):为了在 VimScript 中创建类的"实例",我们对字典使用deepcopy()方法进行深拷贝,从而得到互不影响的独立副本:

:let myinstance = deepcopy(MyClass) :call myinstance.printFoo() Foo :let myinstance.foo = "Bar" :call myinstance.printFoo() Bar

可以看到:拷贝出的myinstance能正常调用方法输出Foo;修改myinstance.foo后再次调用,输出变为Bar,而原始的MyClass不受影响——这正是实例化语义的体现。之所以必须用deepcopy()而不是直接赋值,是因为字典在 VimScript 中属于引用类型,直接赋值只会让多个"实例"共享同一份数据,失去类的隔离效果。

实战:把 VimScript 与 Ex 命令结合起来

脚本语言最终要服务于 Vim 的实际编辑工作流。结合仓库中配套的 editors/vim.txt 速查表,以下几类高频用法是理解 VimScript 之后值得立刻上手的实战方向:

  • 表达式与寄存器:插入模式下可用CTRL-R =后跟表达式插入计算结果,CTRL-R :插入上一条命令行命令,CTRL-R /插入上一次搜索关键字(见 editors/vim.txt),这些能力本质上都是对 VimScript 表达式求值的应用。
  • 在脚本中调用外部命令::call system('ls')调用外部命令但不显示返回内容,可以配合let output = system(...)把结果读进变量做进一步处理;:%!prog可以把当前缓冲区整体交给外部程序过滤(例如:%!python -m json.tool格式化 JSON),与脚本函数结合可构建出强大的文本处理管线。
  • 全局命令批量处理::g/^\s*$/d删除所有空行、:g/green/d删除所有包含 green 的行、:v/green/d删除所有不包含 green 的行(见 editors/vim.txt),这些基于模式的批量操作在脚本中同样可以作为命令字符串动态构造执行。
  • 写入与保存技巧::w !sudo tee %在权限不足时以 sudo 方式保存当前文件;:r !dir把外部命令输出捕获并插入到光标后(见 editors/vim.txt)。
  • 映射规范:按键映射时应默认使用noremap(非递归映射),只有特别需要时才使用map(见 editors/vim.txt),这是防止映射递归展开、避免插件互相干扰的重要工程实践。

后续学习路线

本篇速查表翻译自 Andrew Scala 的《Five Minute Vimscript》,目标是让你用最短的时间建立 VimScript 的整体认知。掌握本文的变量、数据类型、控制流、函数与字典"伪面向对象"之后,原文档还推荐了以下方向的进阶资源(可自行检索,恕不附外部链接):

  • Vim 中文帮助文档(usr_41 编写 Vim 脚本与 API 列表):系统讲解脚本编写与内建函数 API,配合:h eval使用效果最佳;
  • 《Vim 脚本指北》:面向实践的 VimScript 学习指南,覆盖更多进阶语法与惯用法;
  • 《Vim 脚本开发规范》:社区总结的命名、格式与最佳实践规范,适合准备长期维护插件或.vimrc的读者。

此外,editors/vim.txt 速查表末尾的 Tips 部分也提供了不少与脚本行为直接相关的经验,例如"c d x命令会自动填充寄存器"1到"9,y命令会填充"0寄存器",这些寄存器在脚本中同样可以程序化访问。建议读者一边阅读本文,一边打开 Vim 用gQ交互式环境逐条验证示例,再对照官方:h eval文档深入细节,即可快速完成从"看得懂"到"写得出"的跨越。

  • 文档

【免费下载链接】awesome-cheatsheets

超级速查表 - 编程语言、框架和开发工具的速查表,单个文件包含一切你需要知道的东西 :zap:

项目地址:https://gitcode.com/gh_mirrors/awe/awesome-cheatsheets
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询