第六章 VimL 内建函数使用
6.3 操作外部系统资源
本节介绍的函数主要着眼于访问外部资源,比如最常用便是系统文件。
文件系统相关函数
- glob() 按文件通配符搜索文件
- globpath() 在系列目录中搜索文件
- findfile() 在搜索路径中查找文件
- finddir() 在搜索路径中查找目录
glob()
函数的作用,就相当于在 linux 终端命令 ls
所能列出的文件名。它可接收 至多四个参数,只有第一个是必须的:
{wildcard}
通配符文件名模式,非正则表达式;{nosuf}
让两个选项生效,&wildignore
可忽略某些文件,&suffixes
按文件名 后缀影响结果的排序;{list}
提供该参数则返回列表类型,否则是用换行符分隔的字符串;{alllinks}
一般情况下只会找出存在的文件,对于软链接文件,则其指向的文件有 效才被包含在结果中,但若提供该参数,无效链接文件也接收。
一般第三个参数比较常用,即将结果按列表返回,以 glob(wild, 0, 1)
方式调用。
globpath()
用法是在 glob()
基础上,额外提供一个参数指定要哪些目录下搜索文 件,必选参数,且插在第一个参数位置上。这是一个以逗号分隔的目录名列表字符串,如 &rtp
的表示法。例如 globpath(&rtp, 'readme.md')
就能搜索出所有运行时目录下 的说明文档(目前许多插件安装习惯是安装在独立的运行时目录下,一般会有个 readme.md
说明文档)。
glob()
函数将返回所有匹配的文件名,但 findfile()
或 finddir()
只返回第一 个匹配的文件名,一个查找文件,一个查找目录,类似命令 :find
的作用。接收三个 参数,只有第一个必选:
{name}
文件名,必须是全名,不是通配符;{path}
在这些目录下查找文件,也是逗号分隔的目录列表,省略的话用选项&path
代替。所以实际所查到的文件名类似{first-path}/{name}
。{count}
指定返回第几个匹配的文件,而不是第一个,负数时返回所有匹配文件组成 的列表类型变量。
Vim 的 :find
命令及 gf
命令使用 &path
选项值,这叫做搜索路径,这是搜索普 通文件的;不同于 &rpt
运行时路径是搜索 vim 脚本的。搜索路径同时支持向下搜索 与向上搜索的机制,在 {path}
参数或 &path
选项中使用特殊字符达成:
- 向下搜索:
*
表示任意字符,**
表示任意子目录; - 向上搜索:
{one-path};{upto-path},{another-path}
即在一个路径(逗号分隔的) 末尾再加一个分号,接一个相当基目录({one-path}
)更上层的目录({upto-path}
) 就能从指定目录开始向上搜索,依次在其父目录搜索,直到终止目录{upto-path}
。 终止目录可省,但分号不可省,否则在该目录中就认为不需要支持向上搜索。建议不限 定终止目录时写成{base-path};/,
或{base-path};~,
一直上溯到系统根目录或 自己的家目录。 - 相对 Vim 当前路径写成单点
.
,相对当前正编辑的文件缓冲的路径写成./
。
向上搜索机制,对于搜索工程项目文件很有用。比如当你正在编辑一个源代码文件,它一 般被组织在各层子目录下,要找到项目文件就得使用向上机制了,例如 .git/
目录或 tags
文件,都一般放在项目顶层目录中。
- resolve() 解析链接文件名
- simplify() 简化文件名路径
- pathshorten() 缩写文件名的中间路径
- fnamemodify() 文件名修饰
resolve()
是处理软链接文件(linux 系统)或快捷方式(MS-Windows 的 .lnk
)的 ,将其转为实际指向的文件名。在其他系统同 simplify()
简化处理。文件名需要简化 的一个例子是包含一系列的点号与双点号,如 ./dir/.././/file/
,这可能是由其他 函数拼接而来。simplify()
简化后不改变其意义,如上例简化结果为 ./file/
。但 是 pathshorten()
只是简单地将中间路径都缩写至首字母,显然是不保证其有效意义 的。比如在默认的多标签页的名字,为节省屏幕空间就将当前编辑文件缩写目录名, ~.vim/autoload/myfile.vim
将简写为 ~/.v/a/myfile.vim
(如果觉得这比较丑,可 寻插件定制标签页栏)。
文件名修饰是指如何从一个文件名中获取其目录、全路径名、后缀名等相关的名字字符串 。函数 fnamemodify({fname}, {mods})
的第二参数就叫做修饰符,修饰符以冒号开头 带一个单字母表示不同意义,且可连续使用。主要的修饰符如:
:p
文件全路径名:h
父目录名(文件名头部,去除路径分隔符最后一部分):t
文件名尾部(一般是:h
剩余部分,纯文件名):e
文件名后缀:r
文件名主体(相对于:e
而言,不包括后缀,但可能包含父目录)
注意 fnamemodify()
不处理特殊文件名变量,需用 expand()
先展开,不过后者也 可以直接加修饰符后缀。如以下两个语句等效:
: echo fnamemodify(expand('%'), ':p:t')
: echo expand('%:p:t')
- executable() 检查是否可执行程序
- exepath() 可执行程序的全路径
- filereadable() 文件是否可读
- filewriteable() 文件是否可写
- getfperm() 获取文件权限(类
rwxrwxrwx
字符串) - getftype() 获取文件类型
- isdirectory() 检测目录是否存在
- getfsize() 获取文件字节大小(目录返回
0
) - getftime() 获取文件的最后修改时间(整数,按秒计)
这几个函数用于检查指定文件的属性,其中 getftype()
返回的字符串主要有如:
file
普通文件dir
目录link
软链接文件bdev
cdev
socket
fifo
other
等getcwd() 获取当前工作路径
- haslocaldir() 检测当前窗口是否有局部当前路径(
:lcd
)
这两个函数都可选带两个参数,指定窗口编号与标签页编号,因为取当前窗口的当前路径 。Vim 启动时,从 shell 环境中继续当前路径,这是全局当前路径,可用 :cd
命令修 改。每个窗口可有自己的局部当前路径,这用 :lcd
修改。如果从未用过 :lcd
,窗 口的局部当前路径就与全局当前路径相同。新分裂的窗口继承原窗口的当前路径。:pwd
打印的是全局当前路径,因此有可能与 getcwd()
不同。
- mkdir() 创建新目录(类似
$mkdir
) - delete() 删除文件(类似
$rm
) - rename() 重命令文件(类似
$mv
) - readfile() 读文件至一个字符串列表
- writefile() 将字符串列表写入文件
这几个文件操作函数,除了 readfile()
返回列表外,其他函数在操作成功时返回 0
,失败时返回非零错误码。其功能与相应的 linux 命令类似,不过将命令行参数改成函 数调用参数。如 mkdir('name', 'p')
类似 shell 命令 $mkdir -p name
可以自动 创建中间目录;delete()
删除非空目录时必须加参数 rf
(谨慎);rename()
重 命令文件可能覆盖已有文件无警告。当然这些操作也涉及系统权限。
读文件函数支持三个参数,readfile(fname, binary, max)
,后两个是可选的。默认 是按文本格式读入,主要会处理换行符。如果提供 {binary}
参数,按二进制格式读入 ,虽然也会根据换行符分隔为列表元素,但元素中可能再保留回车符(dos 格式的文件) ,且最后可能多加一个空元素(若文件末尾是换行符)。第三个可选参数 {max}
可指 定只读入前几行,类似 linux 命令 $head -n
,但如果 {max}
参数是负数,则只读 入末尾几行,类似命令 $tail -n
。
写文件函数要求两个参数,作为内容的字符串列表,以及文件名,还有个可选参数标记: writefile(list, fname, flags)
。标记 {flags}
若包含 b
则按二进制格式写入 ,若包含 a
则添加到原文件末尾,否则是覆盖原文件。
一般情况下,Vim 是处理文件文本的,在使用这两个读写文件函数时,没必要指定 b
二进制格式。但是按二进制格式先 readfile()
再 writefile()
确实能达到复制文 件的作用。
调用外部系统命令
在 Vim 的命令行中,可用 :!
叹号开头,调用外部系统命令。而在 VimL 脚本中,相 应功能的函数是 system()
。
- system() 执行系统命令,结果为字符串形式返回
- systemlist() 执行系统命令,结果以列表形式返回
- libcall() 调用外部库函数,结果返回字符串
- libcallnr() 调用外部库函数,结果返回数字
system(cmd, input)
将字符串 {cmd}
当作系统命令执行,返回字符串结果。如果 {cmd}
命令需要输入,则可提供可选参数 {input}
,一般也是字符串,首先写入临 时文件,再当作标准输入传给 {cmd}
。如果 {input}
是字符串列表,则以二进制 b
方式调用 writefile()
写入临时文件。
{cmd}
命令字符串不支持管道。并且为了安全与正确性起见,最好调用 shellescape()
转义特殊字符。systemlist()
用法类似,只是返回结果是字符串列 表。
libcall()
类似于 call()
的基础用法,只是调用外库(.so
或 .dll
)的函数, 故需要库名、函数名与参数列表。当然不能随意调用外部库,只能调用专为扩展 vim 的 库,那才比较安全与实用。该函数将结果返回为字符串,另一个 libcallnr()
函数返 回的是数字结果。
- hostname() 获取 vim 所在运行的系统(计算机)名字
- getpid() 获取 vim 运行的进程号 PID
- tempname() 获取可用于临时文件的文件名
在实现比较复杂的功能时,可能需要用到临时文件,用 tempname()
获得一个可用的文 件名(保证不重名)。也可以自己根据进程 PID 构建有规律的临时文件名。
日期时间函数
- localtime() 获取当前时间
- strftime() 格式化时间
- reltime() 获取相对时间
- reltimestr() 将相对时间转为字符串
- reltimefloat() 格式化相对时间转为浮点数
localtime()
用于获取当前的标准时间,即从 1970 年至今的秒数。将这样的时间转为 可读模式,用 strftime(format, time)
函数,缺省 {time}
参数时,取当前时间, 相当于先调用 localtime()
。可用时间格式 {format}
与 C 语言的同名标准函数相 同,如 strftime('%Y-%m-%d')
将返回类似 2017-11-11
的字符串。
reltime()
返回更精确的时间,具体格式与系统有关。无参数调用返回当前时间,一个 参数 reltime(start)
返回从开始时刻({start}
也应该是由该函数返回的)到现在 所经过的时间,两个参数 reltime(start, end)
返回两个时刻之间的时间。用 reltimestr()
将这样的时间转为字符串表示,reltimefloat()
转为浮点数表示,因 为字符串表示法也正像个浮点数(即秒数加小数点加毫秒数)。因其精确到毫秒,可用来 计算命令或函数执行的时间。
用户交互函数
- input() 获得用户从命令行输入的一行文本
- inputsave() 保存用户输入序列
- inputrestore() 恢复用户输入序列
- inputsecret() 按密文输入
- intputdialog() 从对话框中输入一行文本
在 VimL 脚本中与用户交互的最常用的函数是 input(提示, 默认值, 补全方法)
。提示 字符串参数必须给,可以是空字符串,也可以用 \n
表示多行提示。后面两个参数可选 。Vim 首先在命令打印提示字符串,等待用户输入一行文本,按回车返回用户刚输入的这 行文本。如果直接回车没任何输入,则返回传给函数的默认值(或空字符串)。当用户输 入时,相当于编辑命令行,所以为便于用户输入,可提供补全方法,类似自定义命令那般 。而且用户的输入也有独立的命令行历史记录。
显然,input()
函数不宜用于启动配置 vimrc 中。此外,也要避免用于映射中,因为 映射的后续键相当于用户输入,会当作 input()
的回应输入。如果一定要用于映射中 ,请在调用 input()
前后分别调用 inputsave()
与 inputrestore()
。
inputsecret()
用法一样,只是用户输入的文本不直接显示在屏幕命令行中,以星号 *
代替。此外也不支持补全,不放入历史记录中,因为这主要用于提示输入密码。
在 GUI 版本中,inputdialog()
可弹出对话框,让用户从对话框中输入,否则类似 input()
函数。
- inputlist() 让用户从一个列表中选择一项
- confirm() 也是让用户从列表中选择一项
inputlist()
接收一个字符串列表参数,Vim 将每个元素一行显示在命令行上方的消息 区,然后提示用户输入一个数字选择一项(GUI 版本可用鼠标)。注意按列表索引惯例, 0
表示选择第一项。为弥补这个反人类设计,这有个技巧:将提示文本写在列表的第一 项,后续有效选项字符串也以索引 1.
2.
之类的开始,让用户能直观地选择数字。
让用户做选择还有另一个函数 confirm()
,它可用于 GUI 版本,也可用于终端版本。 它可接收四个参数,confirm(提示,选项列表,默认选项,对话框类型)
,一般只用到 前两个。与 inputlist()
不同的是,提示文本为独立参数,且选项列表是字符串,用 回车分隔每一选项,且第一项是 1
。在每一项的字符串中,可以将 &
加在某个字符 之前,则按该字符时直接选择了项目(选择快捷键),且不像 inputlist()
那样会将 所按键显示在命令行中(因为其实这是为GUI版本设计的),也不需要多按回车确认,就 是快捷键直接选择。当然函数返回的仍是选项索引,并非快捷键字符。可选的默认选项参 数也应该是数字索引,不提供时默认 0
,算是无效选项。
- getchar() 获取用户按下的下一个键
- getcharmod() 获取用户按键时的修饰键
- feedkeys() 将一个字符串放入 vim 待响应的按序序列
getchar()
用于获取用户(或输入流)的下一个键。不同于 input()
进入命令行等 待交互,而是默默地等待获取下一输入键。相当细节很多,用到时请参考文档。因为 vim 本身的总体工作(消息)循环,就是等待用户按键,然后作出不同响应。
getcharmod()
用于获取修饰键(收到上个键时同时按下的修饰键),如 shift = 2
、ctrol = 4
、alt = 8
等。将可用修改键用二进制编码,返回一个数字就能表示哪 些修饰键被按下了。
feedkeys()
的用途就比较诡秘了。它把一个字符串放回输入流中,当作是用户的按键 输入序列。特殊按键用 "\<标记>"
表示。默认情况下,放回的这些字符键是可再 被重映射的,然而也有一些可选参数控制细节。
- browse() 打开浏览文件对话框
- browsedir() 打开目录选择对话框
这两个函数只能用于 GUI 版本,弹出标准对话框,让你选择一个文件或目录,返回所选 择的文件路径名。可以传入参数指定对话框标题及初始浏览目录。
- getfontname() 获取当前所用的字体
- getwinposx() 获取 gVim 窗口的坐标
- getwinposy() 获取 gVim 窗口的坐标
这几个函数只能用于 GUI 版本,检索 GUI 才用得到的信息。
异步通讯函数
自 Vim8 版本引入了一些全新的特性:任务(job)、定时器(timer)、通道(channel), 这都涉及异步编程,主要通过回调函数实现功能。为此也提供了一系列相关的内建 api 函数。不过本章不想罗列这些函数,毕竟需要理解相应的功能才有理解函数用法的意义。 留待后续章节专门讨论吧。