引言

做AP上层应用开发时,需要经常打包、烧写文件系统,通过终端仿真软件 SecureCRT 登录后用串口控制。俗话说,工欲善其事必先利其器,工具软件与生产力密切相关,值得仔细设置并熟悉之。搜到两篇不错的设置文章:SecureCRT 使用技巧SecureCRT好用的设置。SecureCRT 支持脚本,可以实现一定程度上的自动化操作,把一些常用操作序列封装成脚本也不错,目前用的是 SecureCRT 5.1

正文

SecureCRT 脚本的资料网上不多,自带的帮助文件中 ActiveX Scripting 章有简要描述,这里概览下

1. 最常用的 ActiveX script 引擎有 VBScript 和 JScript,都是微软的东东,所以 SecureCRT 支持这两种脚本语言。脚本的头部指定脚本与接口类型,脚本内容以 main 函数作为执行入口

# $language = "VBScript"  或  # $language = "JScript"  或  # $language = "Python"
# $interface = "1.0"Sub Main   或   function main() {   或  def main():
.....
End Sub          }                               main()

======= 07-20 补充 v6.6 内容 ========

5.1 是 2006 年的老版本了,文档描述不怎么清楚。目前更新到 6.6 版,从功能到文档都有了较大完善。帮助里提到 6.6 的 Scripting 加强,包括支持 Python 脚本,和更多的脚本传参方式,如按钮和键映射。帮助里各内置对象的使用描述更清晰,而且还附加了若干实例,不错

=====

2. SecureCRT 提供了内置对象与脚本交互,这些对象封装了属性与操作,可用来操纵程序行为。其中 crt 是顶级对象,代表了 SecureCRT 进程,其属性有 ActivePrinter(打印机),Dialog,Screen,Session(会话),Version,Window(窗口) 这些二级对象,操作有 Sleep,Quit

其中,Screen 最为常用,可以用来操作屏幕的输入输出,但要注意的是,Screen 只代表客户区的可见部分,这部分的大小可以通过会话选项里的逻辑行、列数来设定。而整个会话的输出记录是回滚区,没有对应的对象;Dialog 提供了输入框和 MessageBox

======= v6.6 新增对象 ========

相比 5.1,crt 多了些对象属性:Arguments(命令行参数),Clipboard(剪贴板),FileTransfer,操作上则添加了 tab 操作(标签页属性)和加强了错误处理(处理脚本出错但继续执行的情况),GetScriptTab 比较常用,用来获得执行脚本所在的 tab 对象。其下的二级对象也加了不少属性和操作

=====

3. Screen 的属性与操作

属性:CurrentColumn,CurrentRow(当前光标所在逻辑行),Columns,Rows(可见区中的输出行数),Synchronous(同步模式,据说为了避免某些情况下的数据丢失,建议开启)。因为逻辑行、列数限定了可见区的大小,所以当输出满屏时, CurrentRow 和 Rows 的值其实是一样的,都是最大值,不会随着屏幕输出再改变,这种设计实在是有些 egg hurt,不知能否通过设置来解决

操作:

Clear  清掉所有输出,包括回滚与提示符,直接变白板,杯具
Get  获取某个矩形区域内的输出文本,用行列数当坐标来指定;Get2 获取指定两行间的输出文本

Send 发送命令字符串(包括换行符); SendSpecial 用来发送 SecureCRT 的一些内置命令,如键映射

WaitForString 等待输出某字符串,支持超时;WaitForStrings 多个字符串中任何一个;WaitForCursor 等待光标移动,WaitForKey 等待任意键按下,除了 WaitForStrings,其它均返回布尔值
Print 打印机;ReadString 得到实时原样输出?

======= v6.6 描述 ========

object.Get2(row1, col1, row2, col2),和 Get 的参数一个样。看描述, Get 获取的的是矩形区域内的文本,空白也被包括,而 Get2 会根据换行 \r\n 来判断行文本是否已结束,换行也被包括

ReadString 非常有用,可用来获得命令的执行输出,惯用法:

1)char = crt.Screen.ReadString()   // 无参式,从输出中一次取一个字符

2)str = crt.Screen.ReadString(StringToWaitFor, TimeOutSeconds)   // 超时可省略

3)str = crt.Screen.ReadString(["home", "work"], 10)  // [stringarray],类似 WaitForStrings

=====

实例一则

目标:完成文件系统的烧写,并有简单的错误处理,以免误操作。这里用的是 JS 脚本

1 #$language = "JScript"
 2 #$interface = "1.0"
 3 
 4 var screen = crt.Screen;   //创建整屏(可见区)对象引用
 5 var dlg = crt.Dialog;
 6 var bootloader = "RedBoot> ";   //bootloader 提示符
 7 var cmdSequ = [ "ip_address -l 10.10.0.3/24 -h 10.10.0.9",
 8                 "load -r -v -b 0x80100000 rootfs.squashfs",
 9                 "fis create -b 0x80100000 -e 0x0 -l 0x800000 rootfs"
                 ];   //烧写命令序列
10 var err_regx = /(error|can't)/i;   // (正则表达式对象)错误信息匹配,忽略大小写
11 var err_LH = 2;   // 查错的最小范围(行高)
12 
13 function main()
14 {
15     var upRowL = screen.CurrentRow;   //光标所在逻辑行
16     var upColL = screen.CurrentColumn;
17     if (screen.Get(upRowL, 1, screen.CurrentRow, bootloader.length) == bootloader
18         && upColL == (bootloader.length+1))  // 若当前行有提示符且未输入
19     {
20         for (var i=0; i<cmdSequ.length; ++i) {
21             screen.Send(cmdSequ[i] + "\r");  // 执行操作
22             screen.WaitForString(bootloader); // 等待操作结束
23             upRowL = screen.CurrentRow; 
24             var retString = screen.Get2(upRowL-err_LH, upRowL);  // 按行获取输出文本
25             //dlg.MessageBox(retString);
26             if (retString.search(err_regx) > 0) {  // 若输出文本包含出错信息
27                 dlg.MessageBox("ERROR: " + cmdSequ[i] + "!");
28                 return -1;
29             }
30         }
31     } else {
32         dlg.MessageBox("ERROR: " + bootloader + " not ready!");
33     }
34 
35     return 0;
36 }

号外(upRowL - err_LH)可能会<=0,执行出错,但这种情况很少见,就这么将就了吧

本打算通过上次的输入行和当前提示符之间的输出来判断执行是否出错,但因为 screen ache & hurt 的属性设计,难以做到。考虑到出错信息一般都在输出的尾部,所以这里就偷鸡取巧了,只取了最后两行做判断

======= 07-20 修正版 ========

1 #$language = "JScript"
 2 #$interface = "1.0"
 3 
 4 var g_prompt = "RedBoot> ";   //bootloader 提示符
 5 var g_cmdSequ = [ "ip_address -l 10.10.0.3/24 -h 10.10.0.23",
 6                   "load -r -v -b 0x80100000 rootfs.squashfs",
 7                   "fis create -b 0x80100000 -e 0x0 -l 0x800000 rootfs" 
 8                 ];   //烧写命令序列
 9 var g_errRegx = /(error|can't)/i;   // 错误信息匹配,忽略大小写
10 
11 var dlg = crt.Dialog;
12 var screen = crt.Screen;
13 
14 function main()
15 {
16     var curVersion = getVersion();
17     if (curVersion >= 6.6) { 
18         objTab = crt.GetScriptTab();
19         screen = objTab.Screen;        
20     }
21     screen.Synchronous = true;
22     
23     return execCmds();
24 }
25 /* 获取软件版本 */
26 function getVersion()
27 {
28   return parseFloat(crt.Version);  // 6.6
29 }
30 /* 获取命令输出 */
31 function getCmdOutput(command, prompt)
32 {
33     screen.Send(command + '\r');
34     screen.WaitForString('\r');  // not capture the command issued
35     return screen.ReadString(prompt);
36 }
37 /* 执行命令序列 */
38 function execCmds()
39 {        
40     do {
41      var cursorMV = screen.WaitForCursor(1);
43     } while (cursorMV)  // 当光标不再移动
44 
45     for (var i=0; i<g_cmdSequ.length; ++i) {
46        var retString = getCmdOutput(g_cmdSequ[i], g_prompt);
47        if (retString.search(g_errRegx) > 0) {
48            dlg.MessageBox("ERROR: " + g_cmdSequ[i] + "!");
49            return -1;
50        }   
51     }
56     
57     return 0;
58 }

 更新说明:这里使用 readstring 来获得完整命令输出,不再根据最后两个输出行来判断;为了保证执行脚本时没有命令正被执行,这里用了1秒来判断光标是否在移动,不过我还是觉得老版的判断方式更稳妥点

 -----------------------------

其实 Linux 下还有 expect 命令,可以按照设定的方式与交互式程序进行“会话”,也是不错的选择。自动登录的小例子:

#!/usr/bin/expect -f
set timeout 30 
spawn ssh -l username 192.168.1.1 
expect "password:" 
send "ispass\r" 
interact

可参考:expect spawn、linux expect 用法小记linux expect自动登录ssh,ftp