LV02-vimscript-04-变量与表达式.md

本文主要是vimscript一些基础知识和操作的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
点击查看本文参考资料
点击查看相关文件下载
--- ---

一、vim变量

变量一词,对于我来说并不陌生,从C语言Shell,中间再夹杂一些HtmlJavascript还有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

这将会列出所有变量的值,变量的类型在值之前给出。例如,

image-20220306100412807

其中,<空>代表字符串,#代表数值,*代表函数引用。

4. 删除变量

定义了一个变量,用完想要销毁的话,该怎么操作呢?在命令模式下执行:

1
2
:unl[et][!] {name}   " 删除内部变量 {name}
:unl[et] ${env-name} " 删除环境变量 {env-name}

这个命令用于删除内部变量 {var-name}。可以给出多个变量的名字,它们都会被删除。该名字也可以是ListDictionary项目。如果使用 [!],即使变量不存在也不会给出错误。

在一个 :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
2
:echo 035
:echo 039

这个时候会发现,第一条命令显示29,这是一个标准的八进制数转换成的十进制,第三条命令显示39,因为八进制数只有0-7vim并不认它是一个八进制数。

5.2 浮点数Float

  • 直接写小数

在命令模式中执行:

1
:echo 100.123

会发现,在vim底部输出100.123

  • 指数表示

当数量级很大或者很小的时候,vim提供指数的表示形式,用e表示10的多少次幂,+-表示是正指数还是负指数。

【注意】+-是可选的,不写的时候默认是正指数;小数点前后一定要有数字,且小数点前的数字最好不要超过10

在命令模式中执行:

1
2
3
4
:echo 5.25e+3  " 将输出 5250.0
:echo 5.25e-3 " 将输出 0.00525
:echo 3.25e3 " 将输出 3250.0
:echo 13.25e3 " 将输出 13250.0

依次执行,第一条命令显示5250.0,第二条命令显示0.00525,第三条命令显示3250.0,第四条命令等价于 1.325e4,显示13250.0

5.3 强制转换

  • 乘法

在命令模式中执行:

1
2
3
4
:echo 2 * 2      " 将输出 4

:echo 2 * 2.0 " 将输出 4.0
:echo 2.0 * 2.0 " 将输出 4.0
  • 除法

在命令模式中执行:

1
2
3
4
5
:echo 3 / 2      " 将输出 1

:echo 3 / 2.0 " 将输出 1.5
:echo 3.0 / 2 " 将输出 1.5
:echo 3.0 / 2.0 " 将输出 1.5

加减法也是如此,当有浮点数参与运算的时候,参与运算的数都被强制转化为浮点数,最后的结果也会是浮点数。没有浮点数参与的时候,也不会凭空产生小数,就像3/2结果是1.5但是,vim会自动去掉余数,只显示整数。

5.4 字符串String

vim中字符串有两种,一种使用" "包裹起来,这种字符串会对其中的转义字符(\作为标志)进行转义;另一种使用' '包裹起来,这种字符串就适合于想原样打印字符串,但内部却又偏偏包含了转义字符。

1
2
"string"    " 会对转义字符进行转义
'string' " 所有字符原样输出

5.4.1 转义字符打印

在命令模式下执行:

1
:echo "Hello\nWorld\n!"

运行结果如下所示:

1
2
3
Hello 
World
!

可以发现,打印结果分了三行,这是因为中间有两个换行符\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个字符,n0开始,是一个正值,若为负值,则会没有报错地得到一个空字符

(3)n1n2可以为负值,但是,为负值的时候,-1代表最后一个字符,-2代表倒数第二个字符,以此类推。

  • 访问某个字符

在命令模式下执行:

1
2
:let str = "12345"
:echo str[0] str[-1] str[4]

运行结果为:

1
1  5
  • 截取字符串

在命令模式中执行:

1
2
:let str = "12345"
:echo str[:2] str[0:2] str[3:] str[3:4]

运行结果为:

1
123 123 45 45 

接下来尝试一下负索引,在命令模式中执行:

1
2
:let str = "12345"
:echo str[-3:-2] str[-3:] str[0:-3]

运行结果如下:

1
34 345 123

5.4.5 字符串函数

 (1)求字符串长度函数strlen,在命令模式中执行:

1
2
:echo strlen("fanhua\n") " 将输出 7
:echo strlen('fanhua\n') " 将输出 8

然后我们会在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
2
3
:let emptylist = []
:let mylist = [1, two, 3, "four"]
:let nestlist = [[11, 12], [21, 22], [31, 32]]

即可创建一个列表,列表可以为空,也可以有值,列表的项目可以是任何表达式,甚至是列表,就如第三条命令,就创建了一个列表的列表,产生了列表的嵌套。

5.5.2 列表索引

上边已经知道了如何创建一个列表,那么,想要引用列表中的值,该怎么引用呢?在列表之后的方括号中放上索引号可以访问列表项目。索引从零开始,也就是说,第一个 项目的索引值为零。

  • 正值索引

接下来我们来尝试正常访问列表,在命令模式中执行:

1
2
3
4
" 创建一个列表
:let mylist = [1, "two", [3, 'a']]
" 访问列表值
:echo mylist[0] mylist[1] mylist[2] mylist[2][0] mylist[2][1]

运行结果如下所示:

1
1 two [3, 'a'] 3 a

当访问列表时可按照下边格式访问,当列表嵌套列表的时候,依次加索引就可以进行访问。

1
2
list_name[index]              " 无嵌套列表
list_name[index1][index2]... " 有嵌套列表
  • 负值索引

接下来,我们尝试一下,索引为负值会是什么情况呢?在命令模式下执行:

1
2
3
4
" 创建一个列表
:let mylist = [1, "two", [3, 'a']]
" 访问列表值
:echo mylist[0] mylist[-1] mylist[-2] mylist[-1][0] mylist[-1][-1]

运行结果如下所示:

1
1 [3, 'a'] two 3 a

可以发现,索引为-1时,访问到的时最后一个元素,-2是倒数第二个元素。

  • 打印整个列表

有的时候,我们会需要打印出整个列表来查看列表中的数据情况,这个时候,我们只要输入列表名称即可打印出整个列表,在命令模式执行:

1
2
3
4
" 创建一个列表
:let mylist = [1, "two", [3, 'a']]
" 访问列表值
:echo mylist

运行结果如下:

1
[1, 'two', [3, 'a']]

5.5.3 修改元素

通过以下格式进行修改或者赋值:

1
:let List_name[index] = value

【注意】元素修改时,索引不可超限。

在命令模式下执行:

1
2
3
:let mylist = [1, 2]
:let mylist[1] = "two"
:echo mylist

运行结果如下所示:

1
[1, 'two']

5.5.4 列表连接

vim中支持将两个列表连接到一起,连接两个列表的符号为+。在命令模式下执行:

1
2
3
4
" 创建一个列表
:let mylist = [1, "two"]
:let longlist = mylist + [3, 'a']
:echo longlist mylist

运行结果如下所示:

1
[1, 'two', 3, 'a'] [1, 'two']

可以发现,两个列表连接在了一起,新的列表包含了原来两个列表中的元素。

5.5.5 子列表

vim还支持截取子列表,就是列表的一部分。子列表可以通过指定首末两个索引获得,方括号内以冒号分隔两者。格式如下:

1
List_name[n1:n2]   " n1 n2 表示首尾两个索引

【注意】

(1)首索引n1的省略类似于用 0,末索引n2的省略类似于用 -1

(2)列表首末两个索引n1n2可以为负值。

(3)n1n2越过列表索引上界也是安全的。

在命令模式中执行:

1
2
3
4
" 创建一个列表
:let mylist = [1, "two", [3, 'a']]
" 访问列表值
:echo mylist[:1] mylist[1:] mylist[-2:-1]

运行结果如下所示:

1
[1, 'two'] ['two', [3, 'a']] ['two', [3, 'a']]

5.5.6 列表同一

如果变量 mylist1 是列表,把它赋给另一个变量 mylist2 后,两个变量将指向同一列表。因此, 对列表mylist1 的修改也同时修改了mylist2

在命令模式中执行:

1
2
3
4
:let mylist1 = [1, 2, 3]
:let mylist2 = mylist1
:let mylist2[1] = 9
:echo "mylist1="mylist1"\nmylist2="mylist2

运行结果如下:

1
2
mylist1= [1, 9, 3] 
mylist2= [1, 9, 3]

单纯的想要备份列表怎么办呢?我们可以使用deepcopy()函数来创建一个列表的备份,它们将完全独立。

在命令模式中执行:

1
2
3
4
:let mylist1 = [1, 2, 3]
:let mylist2 = deepcopy(mylist1)
:let mylist2[1] = 9
:echo "mylist1="mylist1"\nmylist2="mylist2

运行结果如下:

1
2
mylist1= [1, 2, 3] 
mylist2= [1, 9, 3]

5.5.7 列表解包

什么是列表解包?就是把列表中的每个元素拿一个单独的变量给存起来。

1
2
3
4
5
:let [var1, var2; rest] = list
" 等价写法
:let var1 = list[0]
:let var2 = list[1]
:let rest = list[2:]

【说明】当前边变量数等于后边列表中元素数量的时候,不会报错,rest将成为空表,这个时候;rest也可以省略掉。

5.5.8 列表函数

  • 求列表长度函数len

在命令模式中执行:

1
2
3
" 创建一个列表
:let mylist = [1, "two", [3, 'a']]
:echo len(mylist) len(mylist[-1])

运行结果如下:

1
3 2

整个列表有三个元素,所以打印数字3,第最后一个元素为列表,列表内有两个元素,所以打印数字2

  • 列表增加元素函数add

在命令模式中执行:

1
2
3
4
" 创建一个列表
:let mylist = [1, "two"]
:let newlist = add(mylist, 'b')
:echo newlist

运行结果如下:

1
[1, 'two', 'b']
  • 返回特定元素索引index

index函数返回给定项在给定列表的第一个索引,如果不在列表中则返回-1。在命令模式中执行:

1
2
3
" 创建一个列表
:let mylist = [1, "two", 'b']
:echo index(mylist, 'b') index(mylist, 'a')

运行结果如下:

1
2 -1

5.6 字典Dictionary

字典是关联数组,每个项目有一个键和一个值。官方文档中关于列表的介绍在这里:VIM: eval - Dictionaries

5.6.1 创建字典

字典通过花括号里逗号分隔的项目列表建立,每个项目包含以冒号分隔的键和值,一个键 只能出现一次。在命令模式下执行:

1
2
3
:let emptydict = {}
:let mydict = {1: 'one', 2: 'two', 3: 'three'}
:let nestdict = {1: {11: 'a', 12: 'b'}, 2: {21: 'c'}}

【说明】

(1)键必须是字符串,用数值也可以,但它总被自动转换为字符串。所以字符串'4'和数值 4 总会找到相同的项目。

(2)值可以是任何表达式,如果值本身是字典,就可以建立嵌套的字典

(3)如果不想给每个键都加上引号,可以用 #{} 形式。这要求键只包含ASCII字母、数位、- _,并且这种格式下不允许存在空键。例如:

1
:let mydict = #{zero: 0, one_key: 1, 333: 3}

5.6.2 字典索引

  • 使用[]来访问

在命令模式下执行:

1
2
3
" 创建一个字典
:let mydict = {'a': 'one', 'b': 2}
:echo mydict['a'] mydict['b']

运行结果如下:

1
one 2
  • 使用.来访问

在命令模式下执行:

1
2
3
" 创建一个字典
:let mydict = {'a': 'one', 'b': 2}
:echo mydict.a mydict.b

运行结果如下:

1
one 2
  • 打印整个字典

在命令模式下执行:

1
2
3
" 创建一个字典
:let mydict = {'a': 'one', 'b': 2}
:echo mydict

运行结果如下:

1
{'a': 'one', 'b': 2}

5.6.3 字典修改

在命令模式下执行:

1
2
3
4
5
" 创建一个字典
:let mydict = {'a': 'one', 'b': 2}
:let mydict.a = 1
:let mydict['b'] = "two"
:echo "mydict="mydict

运行结果如下:

1
mydict= {'a': 1, 'b': 'two'}

在字典中,找到相应的键后直接赋值,就可以达到修改键值的目的。

5.6.4 字典添加

在命令模式下执行:

1
2
3
4
" 创建一个字典
:let mydict = {'a': 'one', 'b': 2}
:let mydict.c = "three"
:echo "mydict="mydict

运行结果如下:

1
mydict= {'a': 'one', 'b': 2, 'c': 'three'}

在字典中,可以直接添加元素,直接写入键和值即可。

5.6.5 字典移除

有的时候需要删除某个字典元素,该怎么做呢?

  • remove

remove函数将移除给定字典的给定键对应的项,并返回被移除的值,不能移除不在字典中的项。在命令模式执行:

1
2
3
4
" 创建一个字典
:let mydict = {'a': 'one', 'b': 2}
:let test = remove(mydict, 'a')
:echo "mydict="mydict"\ntest="test

运行结果如下:

1
2
mydict= {'b': 2} 
test= one
  • unlet

unlet命令也能移除字典中的项,只是不返回值,并且不能移除字典中不存在的项。在命令模式中执行:

1
2
3
:let mydict = {'a': 'one', 'b': 2}
:unlet mydict['a']
:echo "mydict="mydict

运行结果如下:

1
mydict= {'b': 2}

5.6.6 字典同一

与列表同一类似。在命令模式下执行:

1
2
3
4
:let mydict1 = {'a': 'one', 'b': 2}
:let mydict2 = mydict1
:let mydict2.a = 1
:echo "mydict1="mydict1"\nmydict2="mydict2

运行结果如下:

1
2
mydict1= {'a': 1, 'b': 2} 
mydict2= {'a': 1, 'b': 2}

当我们使用deepcopy()函数进行字典复制时,在命令模式中执行:

1
2
3
4
:let mydict1 = {'a': 'one', 'b': 2}
:let mydict2 = deepcopy(mydict1)
:let mydict2.a = 1
:echo "mydict1="mydict1"\nmydict2="mydict2

运行结果如下:

1
2
mydict1= {'a': 'one', 'b': 2} 
mydict2= {'a': 1, 'b': 2}

5.6.7 字典函数

1
2
3
4
5
6
:if has_key(dict, 'foo')        " 如果 dict 有带 "foo" 键的项目则为真
:if empty(dict) " 如果 dict 为空则为真
:let l = len(dict) " dict 项目的数量
:let big = max(dict) " dict 项目的最大值
:let small = min(dict) " dict 项目的最小值
:let xs = count(dict, 'x') " 统计 dict 里 'x' 出现的数目

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 Nonev:t_none (v:null 和 v:none)
8 作业v:t_job
9 通道v:t_channel
10blobv: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 : expr1if-then-else

【注意】

(1)同一层的表达式从左到右进行分析。

(2)... 标明这一层上的操作可以连接,如&nu || &list && &shell == "csh"