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"
。