第三章 Vim 常用命令
3.6* 调试命令
对任何一门语言,都有必要掌握调试技巧或手段。本节介绍 VimL 语言编程可以怎么调试 ,介绍一些自己的经验与体会。
echo 大法
对于不太庞大的程序或脚本,在关键疑点处打印消息都是简单方便的发现问题的手段,姑 且也算一种调试方法吧。
不过这明显有个问题,当程序调试完毕后,这些只为调试用的 echo
打印命令留着很碍 事呀,可能会与正常的输出混杂在一起,干扰正常结果呢。所以最好是能将正常的 echo
与调试的临时 echo
区分开来。正好,VimL 有个奇葩规定,在每行行语句之前的 :
冒号是可选的。这是为了与命令行表观上一致,然而正常的 vim 脚本一般都不会自找麻 烦多加这个冒号。但是若按语法规则,你在每行语句之前加一个冒号(甚至多个冒号)都 是没有关系的。
于是,不妨自己规范一下,将调试用的打印语句,都写成 :echo
,或者喜欢多个空格 : echo
也行。而在正常的程序输出语句中,则用整洁的无冒号 echo
版。这样,当 调试完毕,确认程序无误后,就可以用 vim 强大的编辑命令将这些调试命令都删了:
: g/:\s*echo/delete
当然,你也许并不是想彻底删除,只是想注释掉,那就可用替换命令:
: g/:\s*echo/s/:\s*echo/" echo/
当 :s
命令使用的正则表达式与前面的 :g
命令的正则表达式是一样的时候,可以简 写成 : g/:\s*echo/s//" echo/
。因为 :s//{replace}/
命令中,空模式的意图是重 复使用上次的模式(寄存器 /
的内容)。若是为达这个目的,直接用替换命令也可以 的:: %s/:\s*echo/" echo/
。不过与 :g
命令联用(先查找目标行,再替换)会更 灵活点,比如想将首列替换为注释符 "
,而不影响内缩进的 :echo
命令,则可使用 这样的替换命令:
: g/:\s*echo/s/^./"/
如果想更细致点,可以自行将 :echo
与 ::echo
用于不同场合,比如不同等级的调 试输出。
还有个问题,:echo
命令的输出是易逝的,后一批的命令(vim 的解释单元)输出会覆 盖掉前一批的命令输出。如果想保存这样的输入,有以下几种办法:
:echomsg
用这个命令替换:echo
,则输出信息会保存在消息区,以后可用:message
再次查看,当消息区的信息比较多时,可能需要翻页查看,G
跳到最后 一页,基本上就是最近的输出了。:redir
命令重定向,可以将随后的:echo
消息重定向至文件、寄存器、变量中, 当然也会同时显示在屏幕上。不再需要重定向功能时用:redir END
命令取消。:redir! > {file}
重定向到文件中,当文件已存在时,用!
强制覆盖。- ':redir @{reg}>' 重定向至寄存器,如果支持系统剪贴板,用
*
或+
表示。 :redir => {var}
重定义向至一个变量中。:redir >>
将上述命令中的>
换为>>
表示附加。
&verbosfile
将详情信息写入这个选项值指定的文件中。&verbose
选项值设定详 情信息的等级。
断点进入调试模式
Vim 也提供了正式的调试模式,那有点像允许单步执行的 Ex 模式。一般需要先设置断点 ,随后当脚本运行到断点处,就进入了调试模式。添加断点用 :breakadd
命令:
:breakadd file [lnum] {name}
在一个 vim 脚本文件中的某行加断点,行号可选。 注意如果提供行号,行号参数位于文件名之前,如果省略行号,相当于第 1 行。随后 当:source {name}
加载该脚本时,执行到那行时会暂停,进入调试模式。:breakadd func [lnum] {name}
在某函数的第几行打断点。{name}
指函数名。如 果是全局函数,那就是直接的函数名,如FuncName
。如果是脚本局部函数,如s:FuncName
,则要先找到那个脚本在当前 vim 会话的脚本号(:scriptnames
), 然后实际的函数名是<SNR>dd_FuncName
,其中dd
就是脚本号数字。如果是匿名 函数,它没有名字,就只能用其函数编号了,如:breakadd func 1 21
表示在第 21 个匿名函数的第1行处打断点。那匿名函数编号如何确定呢?如果这个函数有出错了, 在错误信息中会打印出出错函数的名字与行号,匿名函数没名字就用编号代替了。(没 有出错么?没出错为啥调试?)至于[lnum]
行号,可理解为函数体内相对于函数头 定义的相对行号,可不是该函数定义块在脚本文件中的行号。即从函数定义头按[lnum]
次j
就是函数断点处。:breakadd here
当你在编辑一个 vim 脚本文件时,相当于在当前文件的当前行加入 断点。如果你已经进入了调试模式,并且已经单步进入了某个函数,:breakadd here
也可以在当前函数的当前行加入断点,下次再次调用该函数时(或下次循环)运行到 此处时也会暂停。
当用 :breakadd
添加了一些断点后,可用 :breaklist
查看断点信息。也可用 :breakdel
删除断点。
:breakdel {nr}
按断点号删除某个断点(:breaklist
会列出断点号)。:breakdel *
删除所有断点。:breakdel file [lnum] {name}
:breakdel func [lnum] {name}
:breakdel here
这三个命令与:breakadd
相似,但是删除断点。
除了通过 :breakadd
添加断点,以期将来运行到彼处时进入调试模式外,还有另外两 种方式直接进入调试模式:
:debug {cmd}
在执行命令之前附加:debug
,就将在执行该命令时立即进入调试 模式,一般接着用s
(step in)深入调试,如果用n
(step over)可能就将整 条{cmd}
命令当作一步直接执行完了,并不能达到调试效果。vim -D {other args}
在启动 vim 时,通过-D
命令行参数,直接在加载vimrc
时就开始进入调试模式了。
调试模式
调试模式是一种特殊的 Ex 模式,除了一般的 ex 命令,还可以使用以下调试命令:
- cont (c),表示继续执行,直到遇到下一断点,或结束。
- quit (q),中断,类似
<Ctrl-C>
- interrupt (i), 也类似
<Ctrl-C>
- next (n),单步执行,类似 step over,会跳过函数调用与加载文件。
- step (s),单独执行,类似 step in,会步进函数调用或加载文件。
- finish (f),结束当前加载脚本或函数调用,回到调用处。
- backtrace (bt) 或 where,显示调用堆栈。
- frame (fr) {N} ,切换到堆栈的第 N 层,可用
+
-
表示相对层。 - up / donw, 在堆栈处上移一层(
fr +1
)或下移一层(fr -1
)。
以上这些调试命令可以尽可能缩写,只要前缀字符不冲突(小括号里也已标出最简缩写) 。直接敲回车表示重复上一次命令,这样就不必每次输入 s
或 n
命令了。
调试命令没有补全功能,只有普通 ex 命令才能补全。如果要使用与调试命令相同的普通 ex 命令,多加一个冒号,如 :next
。但是,由于在 Ex 模式,编辑窗口是不更新的( 事实上,只要调试过程稍长,vim 窗口就完全被调试信息覆盖了),很多普通 ex 命令是 没有效果后,只有在完成调试模式后重回普通模式才能反映编辑窗口的变化。
真正有价值的 ex 命令是可用 echo
命令查看变量值,并且能根据当前环境查看相应作用 域的变量值,比如在加载脚本时可查看 s:var
,运行到函数内部可看局部变量 l:var
(在函数内默认局部变量,:echo var
就相当于 :echo l:var
)。而在正常的命令行 下面,是无法查看 s:var
与 l:var
变量的。
在调试模式中,只能打印出正要执行的那行的源代码。这是典型的命令行式的调试方式, 并不能像 IDE 那般分裂出源码窗口,直接将光标定位到正在执行的行上。如果想查看完 整代码,只能用另外一个 vim 打开源文件查看了(有可能出现 *.swp
冲突问题,用只 读模式打开就好)。所以 VimL 调试的可视化程序仍稍嫌不足,希望日后还有改进。