脚本::Vim进阶索引[3]
Table of Contents
Vim进阶索引[3]::脚本
在这一系列的进阶教程中我们会不时地遇到Vim脚本的内容,也许是配置文件也许是在“宏”中使用的自定义函数。虽然我假设Vim用户会随着这系列的教程,慢慢地掌握脚本的知识。但实际上很多人在学习Vim的过程中“本能地”避开Vim脚本语言的内容;习惯性地认为脚本语言是洪水猛兽。但是却没意识到他们在学习的Vim命令就是脚本语言的主要部分,并且这一部分已经足以让他们写出功能完整的脚本了。本文的目的不在于让用户看完后就掌握了Vim脚本语言,而在于引导读者对Vim脚本语言有个认识:知道怎么使用已有的知识来写脚本;知道怎么运行脚本文件;知道Vim脚本语言中还有哪些主要的概念。最理想的情况下读者从今天开始能随着这系列教程的深入自然而然地掌握Vim的脚本语言。本中提供了相关的帮助索引,用户可以用这些帮助命令找到更完整的信息。
对了这一系列教程使用的Vim版本是6.3。
实际上本文假定读者有一定的Vim基础,当然也包括掌握了一部分的命令行命令。为了能更好地掌握Vim的进阶技巧我们这篇中将涉及Vim的脚本。
:help usr_41.txt :help eval.txt
1 什么是脚本?脚本的作用是什么?
狭义的脚本指含一条或多条编辑命令的独立(脚本文件)文件。通过在编辑器中调用脚本执行在脚本中定义的一条或多条编辑命令。如无明确说明,本文的脚本指的是可在脚本中运行的一条或多条命令而不是指文件本身。当我说“写脚本”时,我指的是写出以某一特定编辑任务为目的而组织起来的一条或多条命令1。
既然你掌握了一些命令你当然会写脚本。我们一起来写一个脚本吧。将下面的命令放入一文件中,假设文件名为myscript.vim2。
" 在文件的最后一行加入签名 :$a 月下独酌 ======================== 暂伴月将影,行乐须及春。 我歌月徘徊,我舞影零乱。 醒时同交欢,醉后各分散。 永结无情游,相期邈云汉。 ======================== .
这个脚本的作用是在文件的最后一行加入签名档。现在用Vim随便打开一文件,输入
:so myscript.vim。
完成。
可以看到上面的“脚本”实际上只是一行注释和一条命令行命令,注释在这里并不是必需的3,所以只要你会命令就可以写脚本。在脚本中以`"‘开始的行都是注释。当然脚本很少会只有一条命令,我们看一下脚本还能为我们做什么?现在我们还想为我们的脚本加入日期怎么办?在脚本的最后一行加入`:$r !date‘4。
通常我们使用脚本是为了减少重复的劳动,如果前面例子中的签名你只用一次,你当然不会想为此写脚本文件。但这并不是绝对的。事实上,很难具体地说明脚本有什么作用,因为每个人都有自己独特的需要,脚本对每个人有不同的作用。总的来说:脚本赋予了一些人满足自己需要的能力。
2 两种类型的脚本
Vim中脚本有两种基本的类型即Ex脚本和一般模式(normal mode)脚本。
2.1 Ex脚本
在Ex脚本中所有的命令都被默认为Ex命令,而命令前面的:是可有可无的。我们开头的例子,就是一Ex脚本。如果在Ex脚本中使用一般模式命令时,需要使用:normal命令。这是Vi的传统脚本格式。
2.2 一般模式脚本。
与Ex脚本相反一般模式脚本。Vim在一般模式中运行这种脚本,所以脚本中的命令就如同你在一般模式中输入指令一样。Ex命令需要在前面加上:。下面是一个例子:
3G02f,lct,家庭地址^[:w
这条命令/脚本将文件中第三行的第二及第三个,之间的内容更改为“家庭地址”。分解一下就是:
3G 跳到第三行
0 跳到本行开始处
2f, 找到第二个,
l 右移一位
ct, 修改第二个,和第三个,之间的内容
家庭地址 改为“家庭地址”
^[ 按<ESC>回到一般模式。这个符号是通过输入
指令序列<Ctrl-V><ESC>产生的,
Windows是<Ctrl-Q><ESC>。表示这里按了<ESC>键。
:w 保存修改
这种脚本紧凑但不易读,也不适合写复杂的脚本,一般只在宏5中使用。
3 运行脚本
所以在继续之前我们先学会如何使用脚本。之所以在本篇的开始处就写如何运行脚本是因为:
- 你会一些命令所以你已经具备写脚本的能力了
- 运行脚本大多数情况下要比写脚本容易
- 这样你才能边根据你对Vim脚本语言掌握的程序使用脚本,而不用学完全部脚本知识后才知道怎么运行脚本。
记住,你并不需要一次掌握所有的脚本知识,而是需要多少学多少,学会多少用多少。
实际上我们一开始的时候就运行过开始的示例脚本了。这里总结一下在Vim中运行脚本的几种方式。
3.1 Vim环境
先看一下下面四个命令:
:source- 这是最传统的运行方式。读入Ex脚本文件并运行。我们一开始的例子就是一Ex脚本文件。一次只能运行一个文件。
:source!- 读入Vim的一般模式命令。注意这个!号并不表示强制运行脚本,而是读入不同的脚本。
:runtime- 从'runtimepath'设置的路径中读取脚本文件。可使用通配符或多个文件名参数,但只有第一个符合的文件被运行。
:runtime!- 跟上一命令一样但运行所有的符合文件。慎用!
一些例子:
:so! c:/normal_mode_script.vim- [Windows环境]Vim中路径可以使用/或\。运行一般模式脚本。
:so ../vscript/samp1.vim- [windows或Unix环境]运行Ex模式脚本。
:ru samp[0-9].vim- [Unix环境]在runtimepath中搜索以形如samp0.vim、samp1.vim、……、samp9.vim的脚本并运行找到的第一个脚本文件。
3.2 命令行
Vim的脚本也可以在命令行中使用,用户不需要经过运行Vim、打开文件、运行脚本这三个步骤,相反的同户同要在命令行上给出合适的参数Vim就可以自动处理。在命令行运行Vim的脚本可通过以下几种方式:
| 参数 | 作用 | 示例 |
|---|---|---|
| -s | 读入一般模式脚本 | vim -s 脚本文件名 文件 |
| -e -s -E -s |
使用vim的ex命令 | vim -e -s <脚本文件名 文件 vim -es <脚本文件名 文件 vim -E -s <脚本文件名 文件 |
现在我们可以在命令行运行本文开头的脚本了,但在如果你现在运行的话可能会发现什么也没发生。这是因为脚本中没有保存的命令,我们前还需要对脚本进行一些修改——加入命令`wq!‘6。这是修改后的脚本文件(前面的:号是多余的已经去掉了):
" 在文件的最后一行加入签名 $a 月下独酌 ======================== 暂伴月将影,行乐须及春。 我歌月徘徊,我舞影零乱。 醒时同交欢,醉后各分散。 永结无情游,相期邈云汉。 ======================== . $r !date wq!
这个简单的脚本完成的任务就是在文件的末尾加入签名和日期。现在在命令行运行试试:
$ vim -es <myscript.vim 文件.txt
通过在命令行运行脚本我们还可以批量修改文件:
for a in `ls *.txt`
do
vim -es <myscript.vim $a
done
如果脚本文件使用MS-Dos的'fileformat'可能会出现不理想的结果,最好将'fileformat'设为Unix。当使用`-e -s‘和`-E -s‘在Windows会失去响应7,不过`-s‘不会。
一般模式脚本除了自已写以外,Vim提供了两个命令行参数来“录制”命令8:
| 参数 | 作用 | 示例 |
|---|---|---|
| -w | 将操作录制起来,如果脚本文件已存在则覆盖 | vim -w script.vim 文件 |
| -W | 同上但是如果脚本文件已存在则添加到末尾 | vim -W script.vim 文件 |
现在跟我做以下实验,先找一文本文档如 f1.txt 复制为f2.txt。使用以下命令运行Vim:
gvim -w mysc.vim f1.txt
然后,进行几个简单的编辑操作并保存退出。现在f1.txt是编辑过的而f2.txt是未经编辑的。用以下命令运行vim:
gvim -s mysc.vim f2.txt
再打开f2.txt看看是不是与f1.txt进行了一样的操作了呢。现在再打mysc.vim看看,可以看到刚才的所以键盘操作都被“录”下来了。你也可以对脚本文件mysc.vim进行进一步的修改以适应实际需要。
好了到目前为止你已经完全可以使用脚本了:你已经掌握了一些命令,你也知道如何运行脚本了。当你掌握的编辑命令越来越多时,你写出来的脚本也就越强大。如果你还想学更多脚本的技巧时那就往下看吧。
4 脚本进阶
学习一门编程脚本语言最有效的方法就是动手进行实验。下面的内容中有相当多简单的例子,建议读者跟着例子做。当然你不用保存为脚本文件再运行,一般直接在Vim中输入命令就行了。如果有多条命令可以进入Ex模式运行,方法是按在一般模式中按Q——如果接Q不行的话那就按gQ,输入visual退出Ex模式。
Vim5.0以前的版本与Vi(ex)的脚本是一到多条编辑命令的集合,脚本的功能就是依序执行脚本中的各种编辑命令。那时的脚本没有变量也没流程控制等程序语言所具有的要素。
对于重复或者复杂的编辑工作Vi通常使用脚本、宏和外部工具来完成。而且一般也认为通过使用宏及外部工具对于大部分的编辑任务而言就以足够了,然而无论是宏还是脚本都只是一系列的编辑指令组成,它们无法与Vi交互并且缺乏足够的灵活性。
从Vim5开始,Vim引入了内建的“脚本语言”,其实就是加入了变量、表达式及流程控制的命令。同时Vim提供了许多的内置函数和内置变量,这些函数和变量我们在后面的教程中会慢慢接触到。通过这些编程特性用户可以写出更灵活更强大的脚本9。现在来看一下都有哪些内容。
4.1 变量
:h variables :h internal-variables
Vim的变量有两种类型,一是字串类型,一是数字类型。这没什么好解释的了,需要注意的是在我们为变量赋字串类型值时用单引号和双引号表示的字串值是不一样。在双引号字串中是可以使用转义字符\的,而单引号字串中不进行任何的转义。下面是两条命令及他们的运行结果:
:echo "|\t|" | | :echo '|\t|' |\t|
变量名。Vim中合法的变量名由下划线_、大小字母和数字构成,但变量名不能由数字开头。
赋值语句。
:help 41.2 :help :let :help :unlet
Vim中为变量赋值需要使用:let命令,要释放变量则使用:unlet命令。这是一些例子:
let a=1 let b=3+1 let c='你好' let d="blahblah" unlet c
变量前缀:
在Vim在可使用不同的变量前缀10,来区别不同变量的作用范围。
g:全局变量 s:脚本文件内部的变量 l:只在函数内部使用的变量 a:变数变量 v:预定义变量
完整列表见`:help internal-variables‘。
预定义变量是Vim为用户提供的变量,这些变量包括了各种有用的信息,如语言设置、Vim版本、当前行号……等等。这里有祥细列表`:help v:var‘
可访问的特殊变量:
&设置项 @寄存器 $环境变量
看一下一下下面的例子:
:let a="abc"- 定义一个全局变量a
:let g:a="abc"- 同上
:let l:a=a:marg- 自定义函数的局部变量。将参数marg的值传给变量a。因为只在自定义函数内部使用所以l:可以省略。
:echo v:lang- 显示预定义变量lang的值。预定义变量是只读的。
:echo &tabstop- 显示设置项'tabstop'(制表符宽度)的值。
:echo $home- 显示环境变量$home。
:echo @3- 显示数字寄存器3的值
:let &l:tabstop=4- 将'tabstop'设为本地变量,并赋值4。见`:help let-star‘与`:help :setlocal‘
4.2 表达式
:help expression-syntax :help eval
表达式是比较抽象的概念,在Vim中所有可以表示字串/数值或返回字串/数值的变量名11、函数、算术运算式、逻辑运算式都是表达式——当然还有字串/数字本身也是。而它们所表达的字串/数字就是表达式的值。下面这些都是表达式:
| 表达式 | 类型 | 值 |
|---|---|---|
| "123木头人" | 字串 | "123木头人" |
| 123 | 数值 | 123 |
| mode() | 函数 | 表示当前模式的字串 |
| abc | 变量 | 变量的值。如果变量未赋值则表达式错误。 |
| "123"==123 | 逻辑表达式 | 1。这里发生了类型转换,见:help expr6 |
| "123"=="321" | 逻辑表达式 | 0 |
| "123木头人"+3 | 算术表达式 | 126。这里发生了类型转换 |
| 32 / 2 | 算术表达式 | 16 |
| name . '你好' | 字串 | 假设name的值为'小明',则值为'小明你好' |
怎样判断一个表达式是不是合法的呢?一个技巧是使用:echo命令。:echo后接表达式会显示表达式的值当表达式不合法是则显示错误信息。
Vim支持的算术运算及逻辑运算与C语言相似。要注意的是Vim不支持浮点运算,算术运算结果的小数点将被省略。所以像7/2的结果会是3而不是3.5。Vim多了一些字串比较的运算符。其中最重要的是正则匹配的运算符:
=~ !~
另外在这些运算符后加上#表示区分大小写,而?表示不区分大小写。如:
==? 大小写不敏感 ==# 大小写敏感
在没有#或?时则,根据'ignorecase'设置项决定是否区分大小写。
详细说明请参阅Vim文档
:h expr4
4.3 流程控制
Vim中的控制语句有两种形式分别是选择语句和循环语句。它们相应的命令是:if与:while,结束命令分别是:endif与:endwhile。
它们都根据条件表达式的值来决定是否执行选择体或循环体中的命令,条件表达式的值是数值——0表示条件不成立,非0数值表示条件成立12。其中条件表达式可以是任意合法的表达式,因为条件表达式的值是数值,所以如果表达式的值是字串时会进行自动类型转换,见:help expr6。
选择语句。这是选择语句的形式:
:if 条件表达式 "Ex命令 :endif
例:
" 如果变量user_name的值为"小明"则显示“小明你好”否则不做动作。 :if user_name=='小明' | echo '小明你好' | endif
选择语句与循环语句都支持嵌套使用。除了嵌套外选择语句还有一种形式可以进行多分支的选择。下面是多分支选择语句的一种形式,表示的是当表达式为真时执行第一组命令否则转到第二组表达式,当第二组表达式为真时执行第二组命令,……当前面的表达式全为假时执行其他命令:
:if 条件表达式 " 第一组命令 :elseif 第二组条件表达式 " 第二组命令 :elseif 第三组条件表达式 " 第三组命令 " ...... :else " 其他命令 :endif
上面的:elseif及:else命令都是可选的,可视情况添加。下面再看个例子:
:if user_name=='小明' : echo '小明你好' :elseif user_name=~'^李' : echo '李先生/女士你好' :else : echo '你好' :endif
循环语句:
循环语句在形式上与选择语句相似,所不同的是当条件表达式的值为真时选择语句中的命令会被执行一次然后退出而循环语句会命令命令并回到循环的开始处重新计算表达式如果为真就再次执行……如此重复。
:while 条件表达式 " 命令 :endwhile
假设有变量user_1、user_2、… 、user_10分别存了10个不同的用户名,我们可以用这种方式将10个用户名列出来13。
:let num=1
:while num<=10
" 使用了{}模拟数组下标。:help curly-braces-names
: echo user_{num}
: let num=num+1
:endwhile
这是两个与:while相关的命令`:help :continue‘、`:help :break‘
4.4 函数
Vim中的函数可以分为两种一种是Vim提供的内置函数。一种是自定义函数。
内置函数可以认为是有特殊用法的命令。不同的是命令不能做为表达式而函数可以。Vim提供了相当多的内置函数,具体列表见:help function-list。内置函数一般提供了返回值可以在表达式的环境中使用如:
" 例一、函数line(".")的作用是返回当前行的行号。函数的结果(行号)将保存在变量line_num中。
:let line_num=line(".")
" 例二、echo接受的参数是字串表达式。而toupper()返回的正是大写字串。
:echo toupper("abc")
ABC
自定义函数是用户根据自己需要写的函数。形式如下:
:function! 函数名()
" 脚本
:return
" 脚本
:endfunction
函数名的命令规则与变量名一样,但只有大写字母开头的函数名可以从脚本外部访问。:function命令后的!号表示如果也存在同名函数则覆盖原函数——!号是可选的。:return语句不是必须的,如果没有:return语句则函数的返回值总为0。没有:return语句的函数一般不在表达式中使用,要调用这些函数的可以使用:call命令。
" 例,定义两个函数 :function! Sayhello( words ) : return a:words :endfunction :function! Sayhello2( words ) : echo a:words :endfunction
然后分别运行以下命令,观察结果看有什么不同。
:call Sayhello("abc")
:call Sayhello2("abc")
:echo Sayhello("abc")
:echo Sayhello2("abc")
4.5 零散内容
.操作符。字符串可以用.操作符连接。
" 例1,将'你 好'值给变量hello let hello = '你' . "\t" . '好' " 例2 :echo 'ab' . (3+4) ab7 " 例3,不支持浮点运算+自动类型转换 :echo 123.4 1234 :echo 9/4.4 24 " 例4 :let name="小明" :let addr="龙门客栈" :echo "姓名:\t".name."\r地址:\t".addr 姓名: 小明 地址: 龙门客栈
[]操作符。虽然Vim不直接支持数组,但允许通过[]析取字串中的某个字母(Vim7已经加入了数组的支持)。这跟C语言也是一样的,例:
:echo 'ab'[ 0 ] a :echo 'ab'[1] b :echo 1234[2] 3 :let a="abcd" | echo a[0] . a[2] ac " 一个中文有两个字节因而 :echo '中文'[0].'中文'[1] 中
`|‘
连接多条命令这样就可以在同一行中执行多条命令——实际上我们前面已经多次用过这种技巧了。注意有些命令以|为参数因而不能使用|来分隔,祥细列表见`:help :bar‘
`:exec‘
以表达式的结果为命令运行。这条命令经常做为一些复杂技巧的一部分,建议用户详细了解这条命令。
let cmd='echo' | exec cmd . ' "abc"' abc
至此Vim脚本的主要内容已经讲完了。这些内容目前还比较零散,我们会在以后的教程中将今天学的这些知识一点一点拼起来。
5 小结
对没有编程基础的人来讲学习一门编程语言尤其是通用编程语言最难的有两点其一是使用流程控制语句和各种表达式。二是他们学到的东西不能在日常生活中运用,这让他们很容易就将辛苦学来的知识忘得一干二净。好在在Vim中你很容易就可以将所学的脚本语言知识用上——只要你会用命令就能用脚本。
Footnotes
[1] 事实上命令与脚本是一体两面,当你在编辑器作使用命令它就是命令,当你把命令以某种方式保存起来以便再次使用时它就成了脚本。
[2] 文件名也可以是中文,但也许用英文文件名会更符合一般人的阅读习惯。另外Vim脚本文件一般是以Vim为后缀名,但实际上可以使用任意后缀名
[3] 但写注释是个好习惯,至少可以练习打字。
[4] Windows的用户需要在date后面加入选项`/t‘。另外用Vim内置的strftime()也可以达到同样的目的,但!date显然是更好也更简便的方法——除非你需要跨平台运行
[5] Vi中的宏一般是指以map命令定义的指令
[6] `q‘对Vim来说不是必需的,不过多打一个字母也不费什么事
[7] Cygwin中可以正常运行
[8] 你可能已经想到了,这跟q命令的作用相近。不同的是使用这两个参数录制的命令将写入到文件中而不是寄存器中
[9] 一个副作用是用户可能会倾向于以Vim来完成所有任务,而不考虑是否有更好的或现在的方式。
[10] 就是命名空间,不过我想抽象的词还是少用点
[11] 必须是已经赋过值的变量名
[12] 其实就是逻辑值,0为假,非0为真。但Vim中逻辑值不是独立的数据类型
[13] Vim7之前不能使用数组,但是可以用模拟的方式。这就是一个模拟数组的方式。
![hq00e[a]126.com](http://static.flickr.com/56/120355805_7079a475f9_m.jpg)



写得非常好,很喜欢
Comment by ggs — 2006, April 23 @ 22:32
谢谢!
Comment by hq00e — 2006, April 27 @ 18:03
Vim进阶教程
非常不错的系列教程由hq00e出品Vim中的模式行::Vim进阶索引[1]折叠::Vim进阶索引[2][UR …
Trackback by Learning linux…… — 2006, May 11 @ 18:10