6.3 操作外部系统资源

第六章 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 = 2ctrol = 4alt = 8 等。将可用修改键用二进制编码,返回一个数字就能表示哪 些修饰键被按下了。

feedkeys() 的用途就比较诡秘了。它把一个字符串放回输入流中,当作是用户的按键 输入序列。特殊按键用 "\<标记>" 表示。默认情况下,放回的这些字符键是可再 被重映射的,然而也有一些可选参数控制细节。

  • browse() 打开浏览文件对话框
  • browsedir() 打开目录选择对话框

这两个函数只能用于 GUI 版本,弹出标准对话框,让你选择一个文件或目录,返回所选 择的文件路径名。可以传入参数指定对话框标题及初始浏览目录。

  • getfontname() 获取当前所用的字体
  • getwinposx() 获取 gVim 窗口的坐标
  • getwinposy() 获取 gVim 窗口的坐标

这几个函数只能用于 GUI 版本,检索 GUI 才用得到的信息。

异步通讯函数 #

自 Vim8 版本引入了一些全新的特性:任务(job)、定时器(timer)、通道(channel), 这都涉及异步编程,主要通过回调函数实现功能。为此也提供了一系列相关的内建 api 函数。不过本章不想罗列这些函数,毕竟需要理解相应的功能才有理解函数用法的意义。 留待后续章节专门讨论吧。