LV02-vimscript-04-变量与表达式.md
本文主要是vimscript一些基础知识和操作的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
| Windows | windows11 | 
| Ubuntu | Ubuntu16.04的64位版本 | 
| VMware® Workstation 16 Pro | 16.2.3 build-19376536 | 
点击查看本文参考资料
| 参考方向 | 参考原文 | 
| Learn vimscript the Hard Way | https://learnvimscriptthehardway.stevelosh.com/ | 
| vim在线文档 | http://vimcdoc.sourceforge.net/ | 
| VIM 中文帮助 | https://vimcdoc.sourceforge.net/doc/help.html | 
| vim教程 | https://www.w3cschool.cn/vim/ | 
一、vim变量
变量一词,对于我来说并不陌生,从C语言到Shell,中间再夹杂一些Html,Javascript还有Python,自己可能只学到了些皮毛,但是各种类型的变量及其声明声明方式还应当是多少有些了解的。这里有一份官方参考文档待查收:VIM: eval   
1. 变量类型
在vim中有多达10种变量,包括了数值变量,浮点数变量,字符串变量,列表变量,字典变量,函数引用变量,特殊变量,作业变量,通道变量和bob变量。
点击查看详细变量说明
| 类型 | 说明 | 
| 数值 (Number) | 32 位或 64 位带符号整数。64 位整数只有在编译时带  +num64  特性时才有效。 示例: -123 0x10 0177 0b1011 | 
| 浮点数 (Float) | 带小数的数值。{仅当编译时加入  +float  特性才有效} 示例: 123.456 1.15e-6 -1.1e3 | 
| 字符串 (String) | NUL 结尾的 8 位无符号字符 (即字节) 的串。 示例: "ab\txx\"--" 'x-z' 'a,c' | 
| 列表 (List) | 项目的有序的序列。 示例: 示例: [1, 2, ['a', 'b']] | 
| 字典 (Dictionary) | 关联的无序数组: 每个项目包含一个键和一个值。 示例: {'blue': "#0000ff", 'red': "#ff0000"} #{blue: "#0000ff", red: "#ff0000"} | 
| 函数引用 (Funcref) | 指向一个函数的引用  Funcref。 示例: function("strlen") 可以绑定到字典或参数上,这样就类似于一个偏函数。 示例: function("Callback", [arg], myDict) | 
| 特殊 (Special) | v:false, v:true, v:none 和 v:null | 
| 作业 (Job) | 用于作业 | 
| 通道 (Channel) | 用于通道 | 
| Blob | 二进制大对象 (Binary Large Object),存储任意字符序列。 示例: 0zFF00ED015DAF 0z 是空 blob。 | 
2. 变量的定义
可以通过如下格式定义一个变量,在命令模式下执行:
| 1 | :let {variable}={expression} | 
这样即可定义一个变量同时进行赋值,变量名可以包含字符、数字和下划线,但必须以字符或是下划线开头。
【说明】在vim官方文档《VIM: eval  - expression-commands  》一节有更为详细的说明。
点击查看变量定义前缀含义
| variable | 说明 | 
| $ENVIRONMENT | 环境变量 示例,:let $MYPATH = "~/.vimrc" | 
| @register | 文本寄存器 示例,:let @a = "Hello" :echo @a | 
| &option | 选项名称,可用于设置布尔选项和键值选项 示例,:let &numberwidth = 15 :set numberwidth? :echo &numberwidth | 
| b:var_name | 局部于当前缓冲区的变量 示例,:let b:current_syntax=c | 
| w:var_name | 局部于当前窗口的变量 | 
| g:var_name | 全局变量,常用在函数内部,表明该变量是全局变量 | 
| a:var_name | 函数参数(只限在函数内部使用) | 
| v:var_name | Vim 内部预定义全局变量 | 
| l:var_name | 局部于当前函数变量 | 
| s:var_name | 局部于 s:source 的 Vim 脚本 | 
| t:var_name | 局部于当前标签页的变量 | 
【说明】
如果 viminfo   选项包含! 标志位,大写开头且不包含小写字母的全局变量被保存在 viminfo 文件里  viminfo-file   。 如果 sessionoptions    选项包含 "global",大写开头且包含至少一个小写字母的全局 变量被保存在会话文件里  session-file   。
关于v:var_name,在官方文档的《VIM: eval - vim-variable  》一节,这里列出几个常用的变量。
| 变量 | 说明 | 
| v:count | 上一次正常模式命令所指定的数量 | 
| v:count1 | 与v:count类似,所不同的是如果没有指定数量则默认值为1 | 
| v:errmsg | 上一次的错误信息 | 
| v:warningmsg | 上一次的警告信息 | 
| v:statusmsg | 最近给出的状态消息,可以设置该变量 | 
| v:version | Vim 的版本号: 主版本号乘以 100 加上副版本号,5.0 版本对应的是500,5.1 版本则是 501,只读。 | 
| v:shell_error | 上一次 Shell 命令的结果,如果为0,则命令正常执行,如果非0,则命令执行失败 | 
3. 列出变量
在命令模式下执行:
| 1 | :let | 
这将会列出所有变量的值,变量的类型在值之前给出。例如,
 
其中,<空>代表字符串,#代表数值,*代表函数引用。
4. 删除变量
定义了一个变量,用完想要销毁的话,该怎么操作呢?在命令模式下执行:
| 1 | :unl[et][!] {name} " 删除内部变量 {name} | 
这个命令用于删除内部变量 {var-name}。可以给出多个变量的名字,它们都会被删除。该名字也可以是List或 Dictionary项目。如果使用 [!],即使变量不存在也不会给出错误。
在一个 :unlet 命令里可以混合 {name} 和 ${env-name}。不对不存在的变量报错,即使没有 ! 也是如此。如果系统不支持删除环境变量,则将它清空。
5. 变量详解
5.1 数值Number
- 十进制Decimalism
在命令模式下执行:
| 1 | :echo 105 | 
这个时候,vim底部会显示数字105,这就是一个十进制的整数。
- 十六进制Hexadecimal
我们可以加0x前缀来使用十六进制数,在命令模式下执行:
| 1 | :echo 0x7a | 
这个时候,vim底部会显示数字122,这是一个十进制的整数,而我们打印的数是一个十六进制数,换算过来,0x7a = 122。
- 八进制Octal
我们可以加0前缀来使用八进制数,在命令模式下执行:
| 1 | :echo 035 | 
这个时候会发现,第一条命令显示29,这是一个标准的八进制数转换成的十进制,第三条命令显示39,因为八进制数只有0-7,vim并不认它是一个八进制数。
5.2 浮点数Float
- 直接写小数
在命令模式中执行:
| 1 | :echo 100.123 | 
会发现,在vim底部输出100.123。
- 指数表示
当数量级很大或者很小的时候,vim提供指数的表示形式,用e表示10的多少次幂,+和-表示是正指数还是负指数。
【注意】+和-是可选的,不写的时候默认是正指数;小数点前后一定要有数字,且小数点前的数字最好不要超过10。
在命令模式中执行:
| 1 | :echo 5.25e+3 " 将输出 5250.0 | 
依次执行,第一条命令显示5250.0,第二条命令显示0.00525,第三条命令显示3250.0,第四条命令等价于 1.325e4,显示13250.0。
5.3 强制转换
- 乘法
在命令模式中执行:
| 1 | :echo 2 * 2 " 将输出 4 | 
- 除法
在命令模式中执行:
| 1 | :echo 3 / 2 " 将输出 1 | 
加减法也是如此,当有浮点数参与运算的时候,参与运算的数都被强制转化为浮点数,最后的结果也会是浮点数。没有浮点数参与的时候,也不会凭空产生小数,就像3/2结果是1.5但是,vim会自动去掉余数,只显示整数。
5.4 字符串String
在vim中字符串有两种,一种使用" "包裹起来,这种字符串会对其中的转义字符(\作为标志)进行转义;另一种使用' '包裹起来,这种字符串就适合于想原样打印字符串,但内部却又偏偏包含了转义字符。
| 1 | "string" " 会对转义字符进行转义 | 
5.4.1 转义字符打印
在命令模式下执行:
| 1 | :echo "Hello\nWorld\n!" | 
运行结果如下所示:
| 1 | Hello | 
可以发现,打印结果分了三行,这是因为中间有两个换行符\n。
5.4.2 原样打印字符串
在命令模式下执行:
| 1 | :echo 'Hello\nWorld\n!' | 
运行结果如下所示:
| 1 | Hello\nWorld\n! | 
5.4.3 连接字符串
 在vim中用于连接两个字符串的符号是.。在命令模式下执行:
| 1 | :echo "Hello " . "World" . "!" | 
运行结果如下所示:
| 1 | Hello World! | 
5.4.4 截取字符串
vimscript中支持通过索引来截取字符串,格式如下:
| 1 | "12345678"[n1:n2] " n1 n2 表示首尾两个索引 | 
【注意】
(1)n1省略表示首字符索引,n2省略时默认为结尾字符索引。
(2)当[]内只有一个数字时,可以访问字符串下某一个位置的字符,即通过"string"[n]可以访问第n个字符,n从0开始,是一个正值,若为负值,则会没有报错地得到一个空字符。
(3)n1和n2可以为负值,但是,为负值的时候,-1代表最后一个字符,-2代表倒数第二个字符,以此类推。
- 访问某个字符
在命令模式下执行:
| 1 | :let str = "12345" | 
运行结果为:
| 1 | 1 5 | 
- 截取字符串
在命令模式中执行:
| 1 | :let str = "12345" | 
运行结果为:
| 1 | 123 123 45 45 | 
接下来尝试一下负索引,在命令模式中执行:
| 1 | :let str = "12345" | 
运行结果如下:
| 1 | 34 345 123 | 
5.4.5 字符串函数
 (1)求字符串长度函数strlen,在命令模式中执行:
| 1 | :echo strlen("fanhua\n") " 将输出 7 | 
然后我们会在vim底部看到,第一条命令会输出一个7,第二条命令会输出一个8。
(2)切割字符串函数split,在命令模式中执行:
| 1 | :echo split("one two three") | 
运行结果如下所示:
| 1 | ['one', 'two', 'three'] | 
可以看到,函数将字符串按空格切割成了一个列表。那想要按其他方式切割呢?在命令模式中执行
| 1 | :echo split("one two three","t") | 
运行结果如下所示:
| 1 | ['one ', 'wo ', 'hree'] | 
我们发现,按t将字符串再次分割成了一个列表,但是t字符会被去掉。
字符串切割函数split可以在第二个参数中指定以什么标志进行切割,不写的时候默认是按空格进行切割,切割后,该函数会将各部分放入一个列表进行返回。
(3)连接字符串函数join,在命令模式中执行:
| 1 | :echo join(split("123456 qq.com"), "@") | 
运行结果如下所示:
| 1 | 123456@qq.com | 
这个命令的含义就是,先将里边的字符串按空格切开,然后再将各部分之间加上@符号,再将其拼接成一个字符串。
还有很多其他的函数,有需要的时候可以来这里搜一搜:VIM: eval - functions
5.5 列表List
列表是有序的,异质的元素集合。官方文档中关于列表的介绍在这里:VIM: eval - Lists
5.5.1 创建列表
在命令模式执行:
| 1 | :let emptylist = [] | 
即可创建一个列表,列表可以为空,也可以有值,列表的项目可以是任何表达式,甚至是列表,就如第三条命令,就创建了一个列表的列表,产生了列表的嵌套。
5.5.2 列表索引
上边已经知道了如何创建一个列表,那么,想要引用列表中的值,该怎么引用呢?在列表之后的方括号中放上索引号可以访问列表项目。索引从零开始,也就是说,第一个 项目的索引值为零。
- 正值索引
接下来我们来尝试正常访问列表,在命令模式中执行:
| 1 | " 创建一个列表 | 
运行结果如下所示:
| 1 | 1 two [3, 'a'] 3 a | 
当访问列表时可按照下边格式访问,当列表嵌套列表的时候,依次加索引就可以进行访问。
| 1 | list_name[index] " 无嵌套列表 | 
- 负值索引
接下来,我们尝试一下,索引为负值会是什么情况呢?在命令模式下执行:
| 1 | " 创建一个列表 | 
运行结果如下所示:
| 1 | 1 [3, 'a'] two 3 a | 
可以发现,索引为-1时,访问到的时最后一个元素,-2是倒数第二个元素。
- 打印整个列表
有的时候,我们会需要打印出整个列表来查看列表中的数据情况,这个时候,我们只要输入列表名称即可打印出整个列表,在命令模式执行:
| 1 | " 创建一个列表 | 
运行结果如下:
| 1 | [1, 'two', [3, 'a']] | 
5.5.3 修改元素
通过以下格式进行修改或者赋值:
| 1 | :let List_name[index] = value | 
【注意】元素修改时,索引不可超限。
在命令模式下执行:
| 1 | :let mylist = [1, 2] | 
运行结果如下所示:
| 1 | [1, 'two'] | 
5.5.4 列表连接
在vim中支持将两个列表连接到一起,连接两个列表的符号为+。在命令模式下执行:
| 1 | " 创建一个列表 | 
运行结果如下所示:
| 1 | [1, 'two', 3, 'a'] [1, 'two'] | 
可以发现,两个列表连接在了一起,新的列表包含了原来两个列表中的元素。
5.5.5 子列表
vim还支持截取子列表,就是列表的一部分。子列表可以通过指定首末两个索引获得,方括号内以冒号分隔两者。格式如下:
| 1 | List_name[n1:n2] " n1 n2 表示首尾两个索引 | 
【注意】
(1)首索引n1的省略类似于用 0,末索引n2的省略类似于用 -1。 
(2)列表首末两个索引n1和n2可以为负值。
(3)n1和n2越过列表索引上界也是安全的。
在命令模式中执行:
| 1 | " 创建一个列表 | 
运行结果如下所示:
| 1 | [1, 'two'] ['two', [3, 'a']] ['two', [3, 'a']] | 
5.5.6 列表同一
如果变量 mylist1 是列表,把它赋给另一个变量 mylist2 后,两个变量将指向同一列表。因此, 对列表mylist1 的修改也同时修改了mylist2。
在命令模式中执行:
| 1 | :let mylist1 = [1, 2, 3] | 
运行结果如下:
| 1 | mylist1= [1, 9, 3] | 
单纯的想要备份列表怎么办呢?我们可以使用deepcopy()函数来创建一个列表的备份,它们将完全独立。
在命令模式中执行:
| 1 | :let mylist1 = [1, 2, 3] | 
运行结果如下:
| 1 | mylist1= [1, 2, 3] | 
5.5.7 列表解包
什么是列表解包?就是把列表中的每个元素拿一个单独的变量给存起来。
| 1 | :let [var1, var2; rest] = list | 
【说明】当前边变量数等于后边列表中元素数量的时候,不会报错,rest将成为空表,这个时候;rest也可以省略掉。
5.5.8 列表函数
- 求列表长度函数len
在命令模式中执行:
| 1 | " 创建一个列表 | 
运行结果如下:
| 1 | 3 2 | 
整个列表有三个元素,所以打印数字3,第最后一个元素为列表,列表内有两个元素,所以打印数字2。
- 列表增加元素函数add
在命令模式中执行:
| 1 | " 创建一个列表 | 
运行结果如下:
| 1 | [1, 'two', 'b'] | 
- 返回特定元素索引index
index函数返回给定项在给定列表的第一个索引,如果不在列表中则返回-1。在命令模式中执行:
| 1 | " 创建一个列表 | 
运行结果如下:
| 1 | 2 -1 | 
5.6 字典Dictionary
字典是关联数组,每个项目有一个键和一个值。官方文档中关于列表的介绍在这里:VIM: eval - Dictionaries
5.6.1 创建字典
字典通过花括号里逗号分隔的项目列表建立,每个项目包含以冒号分隔的键和值,一个键 只能出现一次。在命令模式下执行:
| 1 | :let emptydict = {} | 
【说明】
(1)键必须是字符串,用数值也可以,但它总被自动转换为字符串。所以字符串'4'和数值 4 总会找到相同的项目。
(2)值可以是任何表达式,如果值本身是字典,就可以建立嵌套的字典
(3)如果不想给每个键都加上引号,可以用 #{} 形式。这要求键只包含ASCII字母、数位、-和 _,并且这种格式下不允许存在空键。例如:
| 1 | :let mydict = #{zero: 0, one_key: 1, 333: 3} | 
5.6.2 字典索引
- 使用[]来访问
在命令模式下执行:
| 1 | " 创建一个字典 | 
运行结果如下:
| 1 | one 2 | 
- 使用.来访问
在命令模式下执行:
| 1 | " 创建一个字典 | 
运行结果如下:
| 1 | one 2 | 
- 打印整个字典
在命令模式下执行:
| 1 | " 创建一个字典 | 
运行结果如下:
| 1 | {'a': 'one', 'b': 2} | 
5.6.3 字典修改
在命令模式下执行:
| 1 | " 创建一个字典 | 
运行结果如下:
| 1 | mydict= {'a': 1, 'b': 'two'} | 
在字典中,找到相应的键后直接赋值,就可以达到修改键值的目的。
5.6.4 字典添加
在命令模式下执行:
| 1 | " 创建一个字典 | 
运行结果如下:
| 1 | mydict= {'a': 'one', 'b': 2, 'c': 'three'} | 
在字典中,可以直接添加元素,直接写入键和值即可。
5.6.5 字典移除
有的时候需要删除某个字典元素,该怎么做呢?
- remove
remove函数将移除给定字典的给定键对应的项,并返回被移除的值,不能移除不在字典中的项。在命令模式执行:
| 1 | " 创建一个字典 | 
运行结果如下:
| 1 | mydict= {'b': 2} | 
- unlet
unlet命令也能移除字典中的项,只是不返回值,并且不能移除字典中不存在的项。在命令模式中执行:
| 1 | :let mydict = {'a': 'one', 'b': 2} | 
运行结果如下:
| 1 | mydict= {'b': 2} | 
5.6.6 字典同一
与列表同一类似。在命令模式下执行:
| 1 | :let mydict1 = {'a': 'one', 'b': 2} | 
运行结果如下:
| 1 | mydict1= {'a': 1, 'b': 2} | 
当我们使用deepcopy()函数进行字典复制时,在命令模式中执行:
| 1 | :let mydict1 = {'a': 'one', 'b': 2} | 
运行结果如下:
| 1 | mydict1= {'a': 'one', 'b': 2} | 
5.6.7 字典函数
| 1 | :if has_key(dict, 'foo') " 如果 dict 有带 "foo" 键的项目则为真 | 
6. 查看变量类型
有的时候,我们需要查看变量的类型怎么办呢?vim为我们提供了一个函数type()来查看变量类型。
| 1 | type({expr}) " 返回数值,代表 {expr} 的类型 | 
点击查看返回值含义
| 返回值 | 说明 | 含有此值的 v:t_ 变量 | 
| 0 | 数值 | v:t_number | 
| 1 | 字符串 | v:t_string | 
| 2 | 函数引用 | v:t_func | 
| 3 | 列表 | v:t_list | 
| 4 | 字典 | v:t_dict | 
| 5 | 浮点数 | v:t_float | 
| 6 | 布尔值 | v:t_bool (v:false 和 v:true) | 
| 7 | None | v:t_none (v:null 和 v:none) | 
| 8 | 作业 | v:t_job | 
| 9 | 通道 | v:t_channel | 
| 10 | blob | v:t_blob | 
【说明】最好不要直接使用返回值,最好用含有此返回值的 v:t_ 变量。
二、vim表达式
学习完变量,接下来就该表达式了,这在vimscript中也是极为重要的。
另外在此说明一下,之前运行命令的时候都是在命令模式中运行,对于多行的命令运行起来就显得很繁琐,所以后边涉及到的测试脚本都将使用脚本文件test.vim来进行。
点击查看表达式优先级
【说明】表格来自于vim文档《VIM: eval - expression-syntax  》一节。
| 优先级 | 表达式 | 说明 | 
| 1(expr9) | number | 数值常数 | 
| "string" | 字符串常数,反斜杠有特殊含义 | |
| 'string' | 字符串常数,' 加倍 | |
| [expr1, ...] | List | |
| {expr1: expr1, ...} | Dictionary | |
| #{key: expr1, ...} | Dictionary | |
| &option | 选项值 | |
| (expr1) | 嵌套表达式 | |
| variable | 内部变量 | |
| va{ria}ble | 带花括号的内部变量 | |
| $VAR | 环境变量 | |
| @r | 寄存器 'r' 的值 | |
| function(expr1, ...) | 函数调用 | |
| func{ti}on(expr1, ...) | 带花括号的函数调用 | |
| {args -> expr1} | 匿名函数表达式 | |
| 2(expr8) | expr8[expr1] | 字符串里的字节或者 List 的项目 | 
| expr8[expr1 : expr1] | 字符串子串或 List 的子列表 | |
| expr8.name | Dictionary 的项目 | |
| expr8(expr1, ...) | 使用 Funcref 变量的函数调用 | |
| expr8->name(expr1, ...) | method 调用 | |
| 3(expr7) | ! expr7 | 逻辑非,把 TRUE 变为 FALSE | 
| - expr7 | 一元减法: 取反,改变数值的符号。 | |
| + expr7 | 一元加法: 原值 | |
| 4(expr6) | expr7 * expr7 ... | 数值乘法 | 
| expr7 / expr7 ... | 数值除法 | |
| expr7 % expr7 ... | 数值求余 | |
| 5(expr5) | expr6 + expr6 ... | 数值加法、列表或 blob 连接 | 
| expr6 - expr6 ... | 数值减法 | |
| expr6 . expr6 ... | 字符串连接 | |
| expr6 .. expr6 ... | 字符串连接 | |
| 6(expr4) | expr5 == expr5 | 等于 | 
| expr5 != expr5 | 不等于 | |
| expr5 > expr5 | 大于 | |
| expr5 >= expr5 | 大于等于 | |
| expr5 < expr5 | 小于 | |
| expr5 <= expr5 | 小于等于 | |
| expr5 =~ expr5 | 匹配正则表达式 | |
| expr5 !~ expr5 | 不匹配正则表达式 | |
| expr5 ==? expr5 | 等于,忽略大小写 | |
| expr5 ==# expr5 | 等于,匹配大小写 | |
| expr5 is expr5 | 相同的 List 、 Dictionary 或 Blob 实例 | |
| expr5 isnot expr5 | 不同的 List 、 Dictionary 或 Blob 实例 | |
| 7(expr3) | expr4 && expr4 ... | 逻辑与 | 
| 8(expr2) | expr3 || expr3 ... | 逻辑或 | 
| 9(expr1) | expr2 ? expr1 : expr1 | if-then-else | 
【注意】
(1)同一层的表达式从左到右进行分析。
(2)... 标明这一层上的操作可以连接,如&nu || &list && &shell == "csh"。