第三章 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 调试的可视化程序仍稍嫌不足,希望日后还有改进。