网上收集的tcl学习资料,有的是从别人的空间转的,但因时间久了,没有记下其个人版权,若有争议,请谅解
1.1 TCL语言简介
Tcl是一种很通用的脚本语言,它几乎在所有的平台上都可以解释运行,功能强大。
TCL简单易学,功能强大。TCL经常被用于快速原型开发、脚本编程、GUI和测试等方面。
是tool command language的缩写,发音为"tickle”,实际上包含了两个部分:一个语言和一个库。
首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些交互程序如文本编辑器、调试器和shell。它有一个简单的语法和很强可扩充性,Tcl可以创建新的过程以增强其内建命令的能力。
其次,Tcl是一个库包,可以被嵌入应用程序,Tcl的库包含了一个分析器、用于执行内建命令的例程和可以使你扩充(定义新的过程)的库函数。应用程序可以产生Tcl命令并执行,命令可以由用户产生,也可以从用户接口的一个输入中读取(按钮或菜单等)。
Tcl和其他编程语言例如C不同,它是一种解释语言而非编译语言。Tcl程序由一系列Tcl命令组成,在运行时由Tcl解释器解释运行。
解释执行的语言因为解释器不需要直接同机器码打交道所以实现起来较为简单、而且便于在不同的平台上面移植,这一点从现在的编程语言解释执行的居多就能看出来
编译执行的语言因为要直接同CPU 的指令集打交道,具有很强的指令依赖性和系统依赖性,但编译后的程序执行效率要比解释语言要高的多,象现在的 Visual C/C++、Delphi 等都是很好的编译语言。
Tcl的一个重要特性是它的扩展性。如果一个程序需要使用某些标准Tcl没有提供的功能,可以使用c语言创造一些新的Tcl命令,并很容易的融合进去。
Tk是一系列令Tcl易于编写图形用户接口GUI的命令和过程。另一个流行的扩展包是Expect.,Expect提供了通过终端自动执行命令的能力,例如(passwd、ftp、telnet等命令驱动的外壳)。
tcl 是一种类C的脚本语言,相比较之下有其自身特点:
1 可移植性好。不依赖底层协议
2 支持多种平台。Tcl 是用 C 语言开发的。它现在可运行在 Unix,Windows 和 Macintosh 等各种平台上。
3 较高的执行效率
4 简单易学
5 与操作系统集成
总之,“小巧,易学,高效,跨平台执行”是 Tcl 语言特点的集中体现。
tcl 语言是一种类C的解释执行的语言,本身不依赖与机器码,所以执行效率高,同时支持多种平台,移植性也好。
tcl本身比较 容易掌握,应用范围很广。我们知道的,界面测试和测试都使用很多。
对于tcl本身不能实现的功能,通过c语言创建行命令,然后融合就可以实现,这个也就是说扩展性上也很好。常用的有TK和expect ,后者用于终端自动执行命令行。
TCL脚本框架:
连接设备部分
spawn telnet 10.255.255.240 登陆设备
expect "Username:" 期望输入用户名
send "admin/n"
expect "Password:"
send "XXXXXX/n"
expect "3750>"
send "en/n"
expect "Password:"
send "XXXXXX/n"
expect "3750#"
send "ping 192.168.100.100/n"
expect "3750#"
send "config t/n"
expect "#"
send "ip route 123.2.1.1 255.255.255.255 null0/n"
expect "#"
使用自定义函数
使用的全局变量
#global veriables
set g_dbgflag 1
set g_devip "192.168.1.222"
set g_user "root"
主程序执行
批命令执行
# $Id$
# Construct different source MAC address packets, and send them to switch.
proc src_mac_attack {mac} {
set rc [exec echo "src MAC attack packet $mac" /
| nemesis ethernet -M 00:01:02:03:04:05 -H $mac -T 0x0800 -P -]
return $rc
}
for {set i 1} {$i < 256} {incr i} {
set mac [constructMac $i]
src_mac_attack $mac
}
# $Id$
# Construct different source MAC address packets, and send them to switch.
proc src_mac_attack {mac} {
set rc [exec echo "src MAC attack packet $mac" /
| nemesis ethernet -M 00:01:02:03:04:05 -H $mac -T 0x0800 -P -]
return $rc
}
for {set i 1} {$i < 256} {incr i} {
set mac [constructMac $i]
src_mac_attack $mac
}
mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm
1.一个TCL脚本可以包含一个或多个命令,命令之间必须用换行符号或者分号隔开51Testing软件测试网{(s2g$GU
如 set a 1
k&ze8w U?290296 set a 2
*QV b3?3vj1G290296 或 set a 1;set a 251Testing软件测试网;Yw4Y5rH [
51Testing软件测试网hIPEK}
每个命令包含一个或几个单词,第一个代表命令名,另外的单词则是此命令的参数,单词间必须用空格或者TAB键隔开
YYhPsUE290296 TCL解释器对每一个命令的求值过程为:分析和执行51Testing软件测试网.P@�fF;y
分析:TCL把命令分成一个个独立的单词,同时进行必要的置换
eh@8A.N? f290296 执行:TCL将第一个单词当作命令名,并查看是否有此命令的定义,如果有就激活这个命令对应的C/C++过程,让命令过程进行处理
-@'I3i~ K2D290296
w7Lp3m(c2902962.关于置换的定义及其一些程序演示
?//&qi+D7^-aLFq29029651Testing软件测试网qsN RN i�hS4WZ#X
TCL在分析命令的时候,会把所有的命令参数作为字符串看待51Testing软件测试网hSf6kjW6~
如set a 1(此时把1赋值给a),但如果我们想把a+1赋值给b让b值为2,于是我们就写做set b a+1,这个时候我们得到的结果将不是2而是a+151Testing软件测试网z|)SB vKvyi:W$|
5k*WU ks290296 为什么呢,因为在前面中我们说过,TCL分析时候将所有命令参数做为字符串看待,所以a+1中的a实际上被当作了一个字符串,而不是变量的值了
Xi+y CZ~}290296 于是,这里就要用到TCL提供的置换功能
t l.r+u;Qr29029651Testing软件测试网#ql~H5y3{LZ{
TCL提供三种形式的置换:变量置换,命令置换和反斜杠置换。每中置换都将一个或者多个单词本身被其他的值得所代替,置换可以发生在包括命令
m$`3fm#I7{ w?290296 在内的每一个单词中,并且可以进行置换嵌套
ieX//J/Y)eEGDT(J290296
v3p+m!cte_y@#O{290296
%wN%{B?290296 2.1 变量置换
Q1q?9n&z290296 置换符号由一个$标志,他的作用是将变量的值插入到一个单词中
}h[&H�L!TOB|29029651Testing软件测试网/Z"]S7J+l;Cp9B
ex: set a 1; 51Testing软件测试网 n6Z@5wV8h#_-y*J"g/j
set b $a+1 这里的结果是1+1
ok{rx wnn2D/e29029651Testing软件测试网Et;CF,B R
2.2 命令置换
umZ.Fh&sJ290296 命令置换是由[]来标志,他的作用是将一个命令的所有或者部分单词被另外一个命令的结果所取代
5s-} }?9|BI290296
,y#Z}q3i/p V290296 ex: set a 1; 51Testing软件测试网8i&Gu){J`:s9s
set b [expr $a+1] 这里的结果是2 疑惑:expr能激活与expr对应的c/c++过程,但这些函数在那里查找?
[6sWN&o }290296
T#]B{7O-j290296 注意:如果在上例中我们去掉[],那么TCL会报错。因为在正常情况下,TCL解释器只把命令行中的第一个单词作为看作命令,其他的单词都作为普通字符串处理,看作是命令的参数。51Testing软件测试网'[7sZ5mG2V
[]中必须是一个合法的TCL脚本,长度不限。[]中脚本的值为最后一个命令的返回值 ex: set y [expr $x+100;set b 300] //y的值为300,因为set b 300的返回值为300
)BoPb{]CI'b290296 有了命令置换,实际上就表示命令之间是可以嵌套的,即一个命令的结果可以作为别的命令的参数。
G(A$g"Gm-mC29029651Testing软件测试网G'Vp]'@6N}}$aa
2.3 反斜杠置换51Testing软件测试网&D&Cx9g];AQ
TCL语言中的反斜杠置换类似于C语言中反斜杠的用法,主要用于在单词符号中插入诸如换行符、空格、[、$等被TCL解释器当作特殊符号对待的字符
{h VF;eL-XOF290296 set msg multiple/ space //msg的值为multiple space51Testing软件测试网1o}�W8h9R ^%Cz
51Testing软件测试网:{j}5x O"w p/l-r{C
3.双引号和花括号
M B1YJM,JL6?0Hb290296 51Testing软件测试网o%F0rC6TG_MG
除了使用反斜杠外,TCL提供另外两种方法来使得解释器把分隔符和置换符等特殊字符当作普通字符,而不作特殊处理51Testing软件测试网o0bB&@lR2N
但他们也有一点不同
W]�F2~[0UD290296 一是:TCL解释器对双引号中的各种分隔符将不作处理,但是对换行符 及$和[]两种置换符会照常处理51Testing软件测试网GU ]#q2amC
ex: set x 100 结果是100
QVa3RC&J~290296 set y "$x ddd" 结果是 100 ddd51Testing软件测试网-ZW bd2cod
二是:在花括号中,所有特殊字符都将成为普通字符,失去其特殊意义,TCL解释器不会对其作特殊处理51Testing软件测试网3V:N//!}0?&y
%set y {/n$x [expr 10+100]} 结果是/n$x [expr 10+100]
以下资料摘自http://baike.baidu.com/view/459615.htm
Tcl(最早称为“工具命令语言”"Tool Command Language", 但是目前已经不是这个含义,不过我们仍然称呼它为TCL)是一种脚本语言。 由John Ousterhout创建。 TCL很好学,功能很强大。TCL经常被用于 快速原型开发,脚本编程,GUI和测试等方面。TCL念作“踢叩” "tickle". Tcl的特性包括:
* 任何东西都是一条命令,包括语法结构(for, if等)。
* 任何事物都可以重新定义和重载。
* 所有的数据类型都可以看作字符串。
* 语法规则相当简单
* 提供事件驱动给Socket和文件。基于时间或者用户定义的事件也可以。
* 动态的域定义。
* 很容易用C,C++,或者Java扩展。
* 解释语言,代码能够动态的改变。
* 完全的Unicode支持。
* 平台无关。Win32,UNIX,Mac上都可以跑。
* 和Windows的GUI紧密集成。Tk
* 代码紧凑,易于维护。
TCL本身不提供面向对象的支持。但是语言本身很容易扩展到支持面向对象。许多C语言扩展都提供面向对象能力,包括XOTcl, Incr Tcl 等。另外SNIT扩展本身就是用TCL写的。
使用最广泛的TCL扩展是TK。 TK提供了各种OS平台下的图形用户界面GUI。连强大的Python语言都不单独提供自己的GUI,而是提供接口适配到TK上。另一个流行的扩展包是Expect. Expect提供了通过终端自动执行命令的能力,例如(passwd,ftp,telnet等命令驱动的外壳).
下面是TCL程序的例子:
#!/bin/sh
# next line restarts using tclsh in path /
exec tclsh ${1+"$@"}
# echo server that can handle multiple
# simultaneous connections.
proc newConnection { sock addr port } {
# client connections will be handled in
# line-buffered, non-blocking mode
fconfigure $sock -blocking no -buffering line
# call handleData when socket is readable
fileevent $sock readable [ list handleData $sock ]
}
proc handleData {
puts $sock [ gets $sock ]
if { [ eof $sock ] } {
close $sock
}
}
# handle all connections to port given
# as argument when server was invoked
# by calling newConnection
set port [ lindex $argv 0 ]
socket -server newConnection $port
# enter the event loop by waiting
# on a dummy variable that is otherwise
# unused.
vwait forever
另外一个TK的例子 (来自 A simple A/D clock) 它使用了定时器时间,3行就显示了一个时钟。
proc every {ms body} {eval $body; after $ms [info level 0]}
pack [label .clock -textvar time]
every 1000 {set ::time [clock format [clock sec] -format %H:%M:%S]} ;# RS
___________________________________________________________
相关网站:
1.http://www.tcl.tk/doc/
2.http://www.tclchina.com/
3.http://www.tcl-tk.net/
4.http://www.activestate.com/Products/ActiveTcl/
5.http://www.purl.org/NET/Tcl-FAQ/
6.http://mini.net/tcl/
7.http://www.neosoft.com/tcl/
8.http://citeseer.org/cs?q=Tcl+Tk
简介
Tcl是一种很通用的脚本语言,它几乎在所有的平台上都可以解释运行,功能强大。是tool command language的缩写,发音为 "tickle”, 实际上包含了两个部分:一个语言和一个库。
首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一 些互交程序如文本编辑器、调试器和shell。它有一个简单的语法 和很强可扩充性,Tcl可以创建新的过程以增强其内建命令的能力。
其次,Tcl是一个库包,可以被嵌入应用程序,Tcl的库包含了一个分析器、用于执行内建命令的例程和可以使你扩充(定义新的过程)的库函数。应用程序可以产生Tcl命令并执行,命令可以由用户产生,也可以从用户接口的一个输入中读取(按钮或菜单等)。但Tcl库收到命令后将它分解并执行内建的命令,经常会产生递归的调用。
下面简单介绍以下txl的语法规则:
解释器
在Tcl的数据结构中的核心是Tcl_Interp.一个解释器包含了一套命令,一组变量和一些用于描述状态的东西。每一个 Tcl命令是在特定的Tcl_Interp中运行的,基于Tcl的应用程序可以同时拥有几个Tcl_Interp。Tcl_Interp是一个轻量级的结构,可以快速的新建和删除。
数据类型
Tcl只支持一种数据结构:字符串(string)。所有的命令,命令的所有的参数,命令的结果,所有的变量都是字符串。请牢记这一点,所有的东西都是字符串。 这是它比较有特点的方面字符串有三种形式:命令(command), 表达式(expresion)和表(list)。
Basic Command Syntax 基本语法
Tcl有类似于shell和lisp的语法,当然也有许多的不同。一 条Tcl的命令串包含了一条或多条命令用换行符或分号来隔开,而每一条命令包含了一个域(field)的集合,域使用空白分开的,第一个域是一个命令的名字,其它的是作为参数来传给它。
例如:
set a 22 //相当于C中的 a=22 a是一个变量这条命令分为三个域:1: set 2: a 3: 22 set使用于设置变量的值的命令,a、20 作为参数来传给它,a使它要操作的变量名,22是要付给的a值。
Tcl的命令名可以是内置的命令也可以是用户建的新命令,如果是用户用户建的新命令应用程序中用函数Tcl_CreateCommand来创建。所有的参数作为字符串来传递,命令自己会按其所需来解释的参数的。命令的名字必须被打全,但 Tcl解释器找不到一同名的命令时会用 unknown命令来代替。
在很多场合下,unknown 会在库目录中搜寻,找到一个的话,会自动生成一个Tcl命令并调用它。unknown经常完成缩略的命令名的执行。但最好不要使用。
注释
和shell很象,第一个字母是"#"的Tcl字符串是注释。
其他细节规则
Grouping arguments with double-quotes 用双引号来集群参数,目的在于使用有空白的参数。
例如:
set a "this string contains whitespace"
如够一个参数一双引号来开始,该参数会一直到下一个双引号才结束。其中可以有换行符和分号。
Variable substitution with $ 用美元符进行变量替换说白了就是引用该变量。
如:
set a hello
set b $a // b = "hello" 实际上传给set命令的参数
//是b,"hello"
set c a // b = "a"
Command substitution with brackets 命令子替换(用方括号)
例如:
set a [set b "hello"]
实现执行 set b "hello" 并用其结果来替换源命令 中的方括号部分,产生一条新命令
set a "hello" //"hello" 为 set b "hello" 的返回值
最终的结果是b="hello" a="hello"
当命令的一个子域以方括号开始以方括号结束,表示要进行一个命令子替换。并执行该子命令,用其结果来替换原命令中的方括号部分。方括号中的部分都被视为Tcl命令。
如下一个复杂一点的例子:
set a xyz[set b "abc"].[set c "def"]
//return xyzabcdef
Backslash substitution 转移符替换
转移符时间不可打印字符或由它数意义的字符插入进来。这一概念与C语言中的一样。
Backspace (0x8).
f Form feed (0xc).
Newline (0xa).
Carriage-return (0xd).
Tab (0x9).
v Vertical tab (0xb).
{ Left brace (`{").
} Right brace (`}").
[ Open bracket (`[").
] Close bracket (`]").
$ Dollar sign (`$").
sp Space (` "): does not terminate argument.
; Semicolon: does not terminate command.
" Double-quote.
Grouping arguments with braces 用花扩括号来集群参数
用花扩括号来集群参数与用双引号来集群参数的区别在于:用花扩括号来集群参数其中的三种上述的子替换不被执行。而且可以嵌套。
例如:
set a {xyz a {b c d}}//set收到俩个参数 a "xyz a {b c d}"
eval {
set a 22
set b 33
}//eval收到一个参数 "set a 22
set b 33"
命令综述
1.一个命令就是一个字符串(string)。
2.命令是用换行符或分号来分隔的。
3.一个命令由许多的域组成。第一个于是命令名,其它的域作为参数来传递。
4.域通常是有空白(Tab横向制表健 Space空格)来分开的。
5.双引号可以使一个参数包括换行符或分号。三种子替换仍然发生。
6.花括号类似于双引号,只是不进行三总体换。
7.系统只进行一层子替换,机制替换的结果不会再去做子替换。而且子替换可以在任何一个域进行。
8.如果第一个非控字符是`#", 这一行的所有东西都是注释。
表达式
对字符串的一种解释是表达式。几个命令将其参数按表达式处理,如:expr、for 和 if,并调用Tcl表达式处理器(Tcl_ExprLong, Tcl_ExprBoolean等)来处理它们。其中的运算符与C语言的很相似。
!
逻辑非
* / % + -
<< >>
左移 右移 只能用于整数。
< > <= >= == !=
逻辑比较
& ^ |
位运算 和 异或 或
&&' '
逻辑"和" "或"
x ? y : z
If-then-else 与C的一样
Tcl 中的逻辑真为1,逻辑假为0。
一些例子:
5 / 4.0
5 / ( [string length "abcd"] + 0.0 )
计算字符串的长度 转化为浮点数来计算
"0x03" > "2"
"0y" < "0x12"
都返回 1
set a 1
expr $a+2
expr 1+2
都返回 3
列表
字符串的另一种解释为列表。一个列表是类似于结果的一个字 符串包含了用空白分开的很多域。例如 "Al Sue Anne John" 是 一个有四个元素的例表,在列表中换行父被视为分隔符。例如:
b c {d e {f g h}} 是一个有三个元素的列表 b 、c 和 {d e {f g h}}。
Tcl的命令 concat, foreach, lappend, lindex, linsert,list, llength, lrange,lreplace, lsearch, 和 lsort 可以使你对列表操作。
正则表达式
Tcl 提供了两个用于正则表达式的命令 regexp 和 regsub。 这里的正则表导师实际上是扩展的正则表达式,与 egrep 相一致。
支持 ^ $ . + ? > < () | []
命令结果
每一条命令有俩个结果:一个退出值和一个字符串。退出值标志着命令是否正确执行,字符串给出附加信息。有效的返回制定议在`tcl.h", 如下:
TCL_OK
命令正确执行,字符串给出了命令的返回值。
TCL_ERROR
表示有一个错误发生,字符串给出了错误的描述。全局变量 errorInfo 包含了人类可读的错误描述,全局变量errorCode 机器使用的错误信息。
TCL_RETURN
表示 return 命令被调用,当前的命令(通常是一个函数)必须立刻返回,字符串包含了返回值。
TCL_BREAK
表示break已经被调用,最近的巡环必须立刻返回并跳出。字符串应该是空的。
TCL_CONTINUE
表示continue已经被调用,最近的巡环必须立刻返回不跳出。字符串应该是空的。
Tcl编程者一般需要关心退出值。当Tcl解释器发现错误发生后会立刻停止执行。
Procedures 函数
Tcl 允许你通过proc命令来扩充命令(定义新的命令),定义之后可以向其它的内建命令一样使用。
例如:
proc pf {str} {
puts $str
}
pf "hello world"
这里有一个初学者不注意的地方,上述的定义一定要写成那样子。而不能向下面那样写:
proc pf {str}
{
puts $str
}
因为proc实际上也只不过是一条命令,是一换行符或分号来结束的,用集群参数来传递函数体。proc的定义如下:
proc name args tclcommand
Variables: scalars and arrays
变量:标量和向量(即数组)
向量就是数组,而标量是没有下表的变量。
我们用C来类比:
int i; // i 是标量
int j[10]; // j 是向量
变量不需要定义,使用的时候会自动的被创建。Tcl支持两种
变量:标量和向量
举个例子来说明吧,
set i 100
set j(0) 10
set k(1,3) 20
i是标量,j是向量。
引用的时候:
$i
$j(0)
$k(1,3)
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
TCL搭建测试环境
第一步:
获取TCL8.3的安装包并进行安装(安装时一定要选取lib库)
第二步:
利用VC建立Win32 Console Application工程,工程名为Test
第三步:
向工程里添加被测代码 *.h和*.cpp
第四步:
添加TCL扩展指令代码
#include "stdafx.h"
#include "CounterTest.h"
#include "tcl.h"
#include "test.h"
int TclEx_Instrution(ClientData clientData,Tcl_Interp * interp,int argc, char* argv[])
{
return TCL_OK;
}
第六步:
定义TCL解释器:在Test.Cpp中定义解释器
//定义解释器
Tcl_Interp* MyInterp;
第七步:
创建并初始化TCL解释器
//创建tcl解释器
MyInterp = Tcl_CreateInterp();
//初始化tcl解释器
Tcl_Inin(MyInterp);
第八步:
向解释器注册扩展指令(自定义的外部TCL扩展指令)
Tcl_CreateCommand(MyInterp,"Instrution",TclEx_Counter,NULL,NULL);
第九步:
执行外部TCL脚本(通过函数Tcl_EvalFile()执行TCL脚本)
int rCode;
char sscrīpt[255];
//CString sscrīpt;
while(1)
{
//通过嵌入集成测试框架的Tcl解释器MyInterp,运行外部传入的tcl脚本
printf("请输入要执行的TCL脚本文件名:/n");
scanf("%s",&sscrīpt);
rCode = Tcl_EvalFile(MyInterp,(char *)sscrīpt );
if (TCL_OK != rCode )
{
printf("There are errors in your Tcl File/n");
}
else
{
printf("Testing Succeed!/n");
}
第十步:
添加Tcl头文件和库文件,并设置相应的头文件和库文件的路径
在VC下[Project]->[Add To Project]->[File]菜单中添加tcl.h和tcl.lib
在VC[Tool]->[Option]->[Directories]菜单中分别设置头文件和库文件的路径
第十步:
实现扩展指令
扩展指令的编写思路如下:
第一步:
检查参数个数
第二步:
将参数分别解析出来,赋值给传入的参数和预期的输出结果
第三步:
调用被测函数
第四步:
进行结果比较,输出比较结果
好了,大功告成。TCL测试框架就搭建好了,运行一下,试试!
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
本贴主要收集一些我看到过的TCL/EXPECT方面的资源,以及关于自动测试方面的资源。
TCL语言中文网:http://www.tclchina.com/
关于TCL语言的中文网站,里面有不少资料,还有TCL和EXPECT的论坛。
寒蝉退士的Bolg:http://mhss.cublog.cn/
里面有很多作者翻译的文档、手册等,其中有几篇是关于TCL的。
Tcl Developer Xchange:http://www.tcl.tk/
熟悉英文的朋友可以看一下。
Tcl FAQ:http://psg.com/~joem/tcl/faq.html
TCL的FAQ,也许有帮助。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/easwy/archive/2006/05/29/760880.aspx
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
Tcl 的堆叠框架(stack frame)概念
copyfrom : http://www.tclchina.com/article/chinese/stackframe.htm
内容很不错,收藏.
我试着去深入 Tcl 语言内部, 如果你看不懂这篇文章, 一定是我的概念模糊, 表达能力不好. 敬请见谅. 并请指正我的错误.
在 Tcl 中,Tcl 支持使用命名空间 (namespace) 的概念, namespace 的概念是不同指令与参数的集合, 你可以在不同的 namespace 中使用名字相同的程序或是变量而不至于造成混淆. 每次 Tcl 的直译器(Interpreter) 碰到一个新的程序 (procdure) 或是新的命名空间宣告 (Ex: "namespace eval" , "namesapce inscope"), Interpreter 就会把一个新的 "call frame" 塞进解译器自己的堆栈(call stack) (你可以参考 Tcl 的原始码, 找到相关的Tcl_PushCallFrame, Tcl_PopCallFrame, TclGetFrame. Source code is the best document)
我们不要挖的太深, 先看看表面的东西. 用实例来说明会容易消化点. :)
#!/usr/bin/tcl
set i 0
proc loop {} {
global i
puts "loop 的 level: [info level]";
if { $i < 256 } {
incr i;
loop;
}
}
puts "预设的 global namesapce level: [info level]"
loop
这支脚本中我们使用 [ info level ] 取得目前的 level 层次. Tcl 中的 global 命名空间是所有的 Tcl 程序中预设的. 因此他是的层次是 "0". 每次loop 这支脚本被呼叫的时候, 他的层次就会被加一, 这完全是因为要作命名空间区隔的关系. 这个机制在你使用一个命名空间的时候也会产生作用.
#!/usr/bin/tcl
namespace eval foo {
namespace export bar
proc bar {} {
puts [info level]
}
}
foo::bar
那么这样的机制跟 uplevel 有甚么关系呢? uplevel 是用来在不同的 "stack frame" 中执行脚本用的. (我们 knowledge based 中的 tcl 文件翻译的太怪了? "在一个不同的环境下执行某个scrīpt" :p )你可以使用 uplevel 在不同层次的命名空间动态切换.
#!/usr/bin/tcl
namespace eval foo {
namespace export bar
proc bar {} {
uplevel 1 { set name "Kid"; }
set name "blah...";
}
}
set name "Rex Tsai"
puts $name
foo::bar
puts $name
执行后, 你可以发现 name 被改为 Kid.这是因为 uplevel 上到高一层的命名空间. 并修改了 name 这个变量. 请注意, 其中的 set name "blah..." 跟uplevel 中的 name 是不同两个世界的唷!
另外一个有趣的指令是 upvar,upvar 可以让你产生一个链接连到别的堆栈框架. 他是使用 pass by reference 的方式连结, 因此如果你直接更动了变量,就会直接更动原本的变量. (请参考 Tcl 的原始码, MakeUpvar Tcl_UpVar Tcl_UpVar2 )
#!/usr/bin/tcl
namespace eval foo {
namespace export bar
proc bar {} {
upvar 1 name j # 或是 upvar #0 name j
set j "Kid"
}
}
set name "Rex Tsai"
puts $name
foo::bar
puts $name
upvar 与 uplevel 都必须指定你想使用的命名空间层级, 如果不指定, 他会使用上一层, 默认值是 1. 另外, 也可以使用抽象的层级号码, 他的方式是以 #开头, 后面接一个层级数字. 假设我们有这样的一只脚本 #!/usr/bin/tcl
proc foobar {} { puts "foobar:/t[info level]"; foo; };
proc foo {} { puts "foo:/t[info level]"; bar; };
proc bar {} { puts "bar:/t[info level]" };
puts "global:/t[info level]"
foobar
其实他的层级号码是像这样的.
global: 0
foobar: 1
foo: 2
bar: 3
因此预设的 global namesapce 的抽象层级号码是 0 , foobar 抽象层级号码是 1. 如果我们使用指定号码的方式存取其它堆栈框架, 这些数字应该为相对应的, 例如在 bar 中想存取 foobar层级, foobar 是 bar 的上面两层, 因此应该使用 uplevel 2 { body; } 来撰写. 懂了吗?
说了那么多, 到底这个玩意能作些甚么, 这里举出一个范例. 在 Tcl 中, 并没有像 C 一样 static variables 的量类型. 这里是一个利用 uplevel 与upvar 实作 static variables 的 procedure. 看得懂, 你就出师了. :p
#!/usr/bin/tcl
proc static {args} {
set procName [lindex [info level [expr [info level] -1]] 0]
foreach varName $args {
uplevel 1 "upvar #0 staticvars($procName:$varName) $varName"
}
}
proc foo {} {
static name
if { ! [info exist name] } {
set name "Rex Tsai"
}
puts $name
}
foo;foo
By the way, 你还可以使用多重解译器来作隔离命名空间, 利用 safeTcl 模块使用多个编译器对象, 维持完全隔离的命名空间, 维护安全功能. 如果各位有兴趣, 这个东西我们可以再多做讨论.
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
Tcl单元测试环境搭建指南
第一步 :
获取 tcl8.3 的安装包,安装到 c:/ 目录下,注意:在安装的过程中需要选择头文件和库文件。
第二步 :
利用 Visual C++ 建立一个基于对话框的工程,工程的名字为 utsample
第一步:
获取tcl8.3的安装包,安装到c:/目录下,注意:在安装的过程中需要选择头文件和库文件。
第二步:
利用Visual C++建立一个基于对话框的工程,工程的名字为utsample
第三步:
参照下图,创建utsample.h文件.
第四步:
1)把下面的代码拷贝到utsample.h中
#include "tcl.h"
#define RET_ERR false
#define RET_OK true
#define MAX_WORD_LEN 32
int GetWordFromStr(char *pStr,char *pDestStr,int iPos);
int Tcl_EXGetWord(ClientData clientData,
Tcl_Interp * interp,
int argc, char* argv[]);
int Tcl_AppInit(Tcl_Interp *interp);
2)把下面代码拷贝到utsample.cpp中
#include "stdafx.h"
#include "utsample.h"
#include "memory.h"
int main(int argc, char* argv[])
{
Tcl_Main(argc, argv,Tcl_AppInit);
return 0;
}
/*GetWordFromStr函数的扩展指令函数*/
int Tcl_EXGetWord(ClientData clientData,
Tcl_Interp * interp,
int argc, char* argv[])
{
int iPos;
bool rCode;
char pDestStr[32];
memset(pDestStr,0,32);
if(3 != argc)
{
printf("the paras number is wrong/n");
return TCL_OK;
}
if (TCL_OK != Tcl_GetInt(interp,argv[2],&iPos))
{
printf("the para 2 is wrong/n");
return TCL_OK;
}
rCode = GetWordFromStr(argv[1],pDestStr,iPos);
if(rCode == RET_OK)
{
printf("The Destine string is %s/n",pDestStr);
}
else
{
printf("It's fail to get the string!/n");
}
return TCL_OK;
}
/*tcl解释器和扩展指令的初始化函数*/
int Tcl_AppInit(Tcl_Interp *interp)
{
/*创建interp解释器可以识别的扩展指令,指令的名字是GetWord,执行该指令,直接调用Tcl_EXGetWord扩展指令函数,通过传递参数,可以执行单元测试用例*/
Tcl_CreateCommand(interp, "GetWord",
Tcl_EXGetWord, (ClientData)NULL,
(Tcl_CmdDeleteProc *)NULL);
return TCL_OK;
}
3)把被测试函数GetWordFromStr的函数体拷贝到utsample.cpp中
第五步:
添加tcl头文件和库文件,并设置相应的头文件和库文件路径
1)如下图所示,采用如下方法添加tcl83包中的tcl.h和tcl83.lib
2)在[tool…]->[Option…]->[Directory….]菜单中,设置tcl83包的头文件路径:
3)在[tool…]->[Option…]->[Directory….]菜单中,设置tcl83包的库文件路径:
第六步:
编译通过后,运行,出现控制台程序,输入GetWord “wo ai zhong guo” 3
则出现如下的结果界面
备注:
在嵌入式系统,如果不希望使用上述控制台程序的方式,可以自己构造tcl解释器,然后初始化解释器,然后利用解释器创建扩展指令,并且用自己创建的解释器运行tcl脚本。相关的代码见下面:
/*定义tcl解释器*/
Tcl_Interp* MyInterp;
/*调用tcl的内部函数,创建解释器*/
MyInterp = Tcl_CreateInterp();
/*调用tcl的内部函数,初始化tcl解释器*/
Tcl_Init(MyInterp);
//通过那嵌入集成测试框架的Tcl解释器MyInterp,运行tcl脚本E:/test.tcl
rCode = Tcl_EvalFile(MyInterp,"E:/test.tcl" );
if (TCL_OK != rCode )
{
AfxMessageBox("There are errors in your Tcl File");
}
创建扩展指令的方式参见本文前面的描述
第三步:
参照下图,创建 utsample.h 文件 .
第四步:
1 )把下面的代码拷贝到 utsample.h 中
#include "tcl.h"
#define RET_ERR false
#define RET_OK true
#define MAX_WORD_LEN 32
int GetWordFromStr(char *pStr,char *pDestStr,int iPos);
int Tcl_EXGetWord(ClientData clientData,
Tcl_Interp * interp,
int argc, char* argv[]);
int Tcl_AppInit(Tcl_Interp *interp);
2 )把下面代码拷贝到 utsample.cpp 中
#include "stdafx.h"
#include "utsample.h"
#include "memory.h"
int main(int argc, char* argv[])
{
Tcl_Main(argc, argv,Tcl_AppInit);
return 0;
}
/*GetWordFromStr 函数的扩展指令函数 */
int Tcl_EXGetWord(ClientData clientData,
Tcl_Interp * interp,
int argc, char* argv[])
{
int iPos;
bool rCode;
char pDestStr[32];
memset(pDestStr,0,32);
if(3 != argc)
{
printf("the paras number is wrong/n");
return TCL_OK;
}
if (TCL_OK != Tcl_GetInt(interp,argv[2],&iPos))
{
printf("the para 2 is wrong/n");
return TCL_OK;
}
rCode = GetWordFromStr(argv[1],pDestStr,iPos);
if(rCode == RET_OK)
{
printf("The Destine string is %s/n",pDestStr);
}
else
{
printf("It's fail to get the string!/n");
}
return TCL_OK;
}
/*tcl 解释器和扩展指令的初始化函数 */
int Tcl_AppInit(Tcl_Interp *interp)
{
/* 创建 interp 解释器可以识别的扩展指令,指令的名字是 GetWord ,执行该指令,直接调用 Tcl_EXGetWord 扩展指令函数,通过传递参数,可以执行单元测试用例 */
Tcl_CreateCommand(interp, "GetWord",
Tcl_EXGetWord,(ClientData)NULL,
(Tcl_CmdDeleteProc *)NULL);
return TCL_OK;
}
3 )把被测试函数 GetWordFromStr 的函数体拷贝到 utsample.cpp 中
第五步:
添加 tcl 头文件和库文件,并设置相应的头文件和库文件路径
1 )如下图所示,采用如下方法添加 tcl83 包中的 tcl.h 和 tcl83.lib
2 )在 [tool…]->[Option…]->[Directory….] 菜单中,设置 tcl83 包的头文件路径:
3 )在 [tool…]->[Option…]->[Directory….] 菜单中,设置 tcl83 包的库文件路径:
第六步:
编译通过后,运行,出现控制台程序,输入 GetWord “wo ai zhong guo” 3
则出现如下的结果界面
备注:
在嵌入式系统,如果不希望使用上述控制台程序的方式,可以自己构造 tcl 解释器,然后初始化解释器,然后利用解释器创建扩展指令,并且用自己创建的解释器运行 tcl 脚本。相关的代码见下面:
/* 定义 tcl 解释器 */
Tcl_Interp* MyInterp;
/* 调用 tcl 的内部函数,创建解释器 */
MyInterp = Tcl_CreateInterp();
/* 调用 tcl 的内部函数,初始化 tcl 解释器 */
Tcl_Init(MyInterp);
// 通过那嵌入集成测试框架的 Tcl 解释器 MyInterp ,运行 tcl 脚本 E:/test.tcl
rCode = Tcl_EvalFile(MyInterp,"E:/test.tcl" );
if (TCL_OK != rCode )
{
AfxMessageBox("There are errors in your Tcl File");
}
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
使用TCL脚本读取配置文件
发布时间: 2007-4-04 13:40 作者: 叶晖 兰海 来源: 51testing
字体: 小 中 大 | 上一篇 下一篇 | 打印 | 我要投稿 | 每周一问,答贴有奖
摘 要:unix下使用TCL脚本读取配置文件;错误处理.
关键词:TCL、配置文件、unix
一.应用范围
在实际工作中,TCL脚本对于一些简单的工作裨益甚大。通常编写脚本都有一定的模式,首先从配置文件读入配置项,初始化变量,然后进行处理,最后输出,在程序的运行过程中需要把一些信息写入日志文件,而调试信息写入日志文件和直接输出到屏幕都可以。
使用任何一种脚本,通常也都会根据实际情况建立起自己的函数库,而这个函数库中对配置文件配置项的读取无疑是非常基本而重要的。
这篇文章的本意是引导刚接触TCL脚本的朋友尽快上手,所以有些细节的说明文字比较细。
二.程序讲解
构建一个配置文件,尽可能的接近实际应用:
文件名:config.ini
文件内容:
#
#这是注释行
#
[]
key1=value1 #注释
[section1]
key2=value2
[section2]
key1=value2
[section1]
key1=xxxxxx #这是最后的返回
[section1]
key1=value2
现在开始对程序的说明:
;#-------------------------------------------------------------------------
;#功能:从配置文件中读取配置项
;#输入:1. configFile:配置文件名称
;# 2. Section :段名称
;# 3. Key :关键字
;# 4. Comment :注释符,缺省为井号
;# 5. Equal :关键字和值的分隔符,缺省为等号
;#输出:1. Value :相应的值
;#-------------------------------------------------------------------------
1.过程的定义:
格式:proc name args body
要点:
Ø 参数列表使用花括号引起;
Ø 变量没有类型;
Ø 变量之间使用空格间隔;
Ø 如果参数有缺省值,使用花括号引起,并赋值
proc getConfig { configFile Section Key {Comment "#"} {Equal "="}} {
set Value "" ;#记录过程返回的值
set FindSection 0 ;#记录是否找到了section
2.错误的处理
格式:catch script ?varName?
功能:执行script,如果成功返回TCL_OK(0),否则返回TCL_ERROR(1),提示结果存在varName中。比如下面如果打开文件成功,errMsg返回的就是类似file3这样的字符串,此时err返回的是TCL_OK,就是零;如果文件不存在,errMsg返回的类似:couldn't open "config1.ini": no such file or directory,此时err返回TCL_ERROR,就是一。
3.文件的读写
格式:openfile fileName ? access ? permission
讲解:
Ø fileName是文件名称;
Ø access是存取模式,可以为r, r+, w, w+, a, a+六种模式,r、r+和a三种模式文件必须已经存在,其他三种模式文件不存在就创建一个。本文用的r模式,所以文件不存在会提示错误,而不是自动建立一个;
Ø permission是权限,举例说明:
有权限为:000 000 000:第一组三位为user权限;第二组三位为同组其他用户的权限;第三组三位为其他组所有人的权限。每个三位的权限依次代表读,写,执行。如果有相应的权限就设置为一,没有设置为0。然后三位为组转成十进制数。
Ø 文件打开后就可以使用其文件id,使用完后记得关闭文件
;#打开配置文件
set err [catch {set fileid [open $configFile r]} errMsg]
if {$err == 1} {
puts "errMsg : $errMsg"
return $Value
}
;#成功打开文件后,一行一行的加以分析
set rowid 0 ;#记录当前行数,程序调试时打印调试信息使用的
seek $fileid 0 start ;#定位到文件头
while {[eof $fileid] != 1} { ;#读取文件内容
4.变量的自动增长使用命令incr,也可以使用set rowid [expr $rowid + 1],显然前者更简捷
incr rowid ;#记录行数,从一开始
;#读出一行
gets $fileid line
5.先期处理行,因为注释有两种情况,行中的注释和整行注释,先去掉注释,然后去左右空格就可以得到真正需要的内容;如果先去空格,再去注释,由于行中的注释和内容之间有空格,这样最后得到的内容在去掉行中注释后,会在后面留下一些空格。所以需要先去掉注释,后去掉空格。
6.得到一个字符串在另一个字符串中首先出现的位置,使用函数string first
格式:string first string1 string2
讲解:返回string1在string2中第一次出现的位置;如果string1不在string2中,返回-1
7.下面有一个细节是初学者经常犯错的地方,那就是:} else {,这里必须严格的花括号,空格,else,空格,花括号,不能把花括号写到上一行或者下一行。
8.返回一个字符串的子串使用string range函数
格式:string range string1 frompos topos
讲解:取string1的从frompos到topos之间的字符串,注意这里字符串下标是从零开始的,所以用string length string1得到的字符串的长度比字符串最后一个字符的位置值要大一。如果取一个字符串中的某个字符就可以使用函数string index string1 pos。
9.expr和unix shell中一样,是进行数学运算的函数。
;#先去掉注释,再去掉两端的空格
set commentpos [string first $Comment $line] ;#得到注释符号的位置
if { $commentpos == 0 } {
;#行以注释符号开头,忽略掉该行
} else {
if { $commentpos != -1 } { ;#行中有注释符号,去掉注释
set line [string range $line 0 [expr $commentpos-1]]
}
set line [string trim $line] ;#去掉两端的空格
;# puts "$rowid : line : $line"
10.在tcl脚本中,循环中有break命令跳出循环,continue跳到循环的开头。不过因为括号的使用,其实这里写不写continue都没有问题。
;#如果是空就继续循环
if { $line == "" } {
;#回循环
continue
} else {
;#先找section
set linelen [string length $line] ;#字符串长度
set lastpos [expr $linelen - 1 ] ;#字符串最后的位置
;# puts "$rowid :len: $linelen lastpos: $lastpos"
if { [string index $line 0] == "/[" && [string index $line $lastpos] == "/]" } {
;#如果是查找的section,修改标志位;如果不是相应的section,需要将标志重新赋值
if { [string range $line 1 [expr $lastpos - 1 ]] == $Section } {
;# puts "$rowid: find section : $Section"
set FindSection 1
} else {
set FindSection 0
}
} else {
;#已经找到了section才继续找key
if { $FindSection == 1 } {
set equalpos [string first $Equal $line] ;#得到等号的位置
if { $equalpos != -1} {
;#如果就是找寻的key,结束循环
if { [string range $line 0 [expr $equalpos - 1]] == $Key } {
puts "$rowid: find key"
set Value [string range $line [expr $equalpos + 1] [string length $line]]
puts "$rowid: find value: $Value"
break
}
} else {
;#回循环
}
} else {
;#回循环
}
}
}
}
}
;#关闭文件
close $fileid
return $Value
}
set val ""
;#测试正常情况下
set val [getConfig "config.ini" "section1" "key1"]
puts "val : $val"
;#测试文件不存在的情况下
set val [getConfig "config1.ini" "section1" "key1"]
puts "val : $val"
该程序在unix环境下调试通过。
三.伪代码
为了便于理解程序,特写了下面的伪代码:
打开配置文件
IF出错THEN
过程结束
END IF
文件打开成功,定位到文件头
WHILE没有到文件尾
读出一行
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
TCL/EXPECT自动化测试脚本实例一 --- telnet到目标机器
文章出处:网络 作者: 发布时间:2007-02-26
这是一个简单的TCL/EXPECT脚本,完成telnet到远程设备的功能。通过这个例子,大家可以看到使用TCL/EXPECT脚本构造测试程序,是非常简洁的。
从今天开始,陆续把我所写的一些自动测试脚本贴上来,希望对初学者有所帮助。由于目前没有找好合适的服务器存放代码,所以代码先贴在blog的正文中,以后再提供完整的代码下载。
我的自动化测试脚本运行在debian linux下,使用/usr/bin/expect进行解释执行。为了简化处理,把一些常用的功能编写成函数,放在commonLib.exp文件中,其它脚本文件可以使用source commonLib.exp命令引用这些函数。
下面的函数完成telenet到目标机器并login。从其实现上大家可以看到tcl/expect编写测试脚本的简洁。
这个函数带有三个参数,分别是目标机器的IP地址ipaddr,登录用户名user和登录密码,telenet的端口号采用默认的23端口。
函数中使用了三个全局变量,g_prompt,g_usrPrompt和g_pwdPrompt,分别表示登录后的命令提示符,提示用户名输入的提示符,以及提示密码输入的提示符,这三个全局变量定义在global.exp中。之所以采用全局变量,是因为这些值使用比较广泛,但在不同设备中都不相同。使用全局变量可以方便修改。
代码如下:
#************************************************
# telnet login routine
#
# @PARAMS
# ipaddr - remote device ip address
# user - user name to login in
# passwd - login password
#
# @RETURN
# spawn_id if login success, otherwise 0
#************************************************
proc login {ipaddr user passwd} {
global g_prompt g_usrPrompt g_pwdPrompt
spawn telnet $ipaddr
expect {
"$g_usrPrompt" {
exp_send "$user/r/n"
exp_continue
}
"$g_pwdPrompt" {
exp_send "$passwd/r/n"
exp_continue
}
-ex "$g_prompt" {
dbgLog "Login Successful/n"
return $spawn_id
}
timeout {
send_user "timeout"
return 0
}
}
}
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
TCL/EXPECT自动化测试脚本实例六 --- SNMP community长度测试
文章出处:网络 作者: 发布时间:2007-02-26
本文通过一个测试SNMP community最大长度的脚本,介绍自动测试SNMP agent的方法。
下面通过一个测试SNMP community最大长度的脚本,介绍一下net-snmp工具。
net-snmp是一组基于命令行的snmp manager工具,可以在命令行下进行snmp get, snmp set, snmp walk等操作,支持snmp v1/v2c/v3。原来的名字叫做ucd-snmp,也已经被移植到windows NT上。
它的主页在http://net-snmp.sourceforge.net/
由于它可以在命令行下进行SNMP操作,所以可以和TCL/expect很好的结合,完成自动化测试的功能。
下面的脚本(snmp.exp),不断的增加SNMP community,长度从1到256,每增加一个community,就调用snmp-get来进行SNMP get操作,如果get成功,说明此community有效;反之,就说明community已经超出了设备支持的最大长度。
这个脚本使用前面讲到的test.exp调用,调用方法是:
./test.exp -ssnmp.exp script
对它稍加修改,也可以直接在命令行中调用,此处不再赘述。
代码如下:
# $Id$
proc snmpCommTest {comm} {
global g_devip
spawn snmpget -c $comm -v 2c -r 2 $g_devip system.sysUpTime.0
expect {
"system.sysUpTime.0*" {
return 1
}
"*Timeout*" {
return 0
}
}
return 1
}
set spawn_id [login $g_devip $g_user $g_passwd]
if {$spawn_id == 0} {
errLog "login error/n"
return 0
}
set cmdCommAdd "create snmp community %s rw/n"
set cmdCommDel "delete snmp community %s/n"
set cmdHostAdd "create snmp host ip 192.168.1.2 community %s/n"
set cmdHostDel "delete snmp host ip 192.168.1.2 community %s/n"
set comm ""
for {set i 1} {$i < 256} {incr i} {
set comm "a$comm"
set cmd [format $cmdCommAdd $comm]
exp_send $cmd
expect {
"Error*" {
errLog "create comm len $i error"
continue
}
timeout {
errLog "create comm len $i timeout"
continue
}
"Entry Created"
}
set cmd [format $cmdHostAdd $comm]
exp_send $cmd
expect {
"Error*" {
errLog "create host error"
continue
}
timeout {
errLog "create host timeout"
continue
}
"Entry Created"
}
set rc [snmpCommTest $comm]
if {$rc == 0} {
errLog "community len $i failed"
}
set cmd [format $cmdHostDel $comm]
exp_send $cmd
expect "Entry Deleted"
set cmd [format $cmdCommDel $comm]
exp_send $cmd
expect "Entry Deleted"
}
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
TCL/EXPECT自动化测试脚本实例二 --- 主程序
文章出处:网络 作者: 发布时间:2007-02-26
这里介绍了测试主程序,主程序采用一种灵活的机制,方便增加新的测试项目,也可以执行一系列命令,或者执行指定的脚本程序。
现在介绍一下测试主程序: test.exp。
为了方便加入新的测试项目,主程序采用了一种灵活的机制,它根据需要通过source命令调用相应的子测试程序。这样一来,每个测试点都可以单独放到一个文件中,然后被主程序引用。
先看一下代码:
#! /usr/bin/expect --
# $Id$
# Usage:
# ./test [-uuser] [-ppassward] [-iip_address] test_001 ...
# or ./test [-uuser] [-ppassward] [-iip_address] [-ccommand_file] cmd
# or ./test [-uuser] [-ppassward] [-iip_address] [-sscript_file] script
source global.exp
source commonLib.exp
# initialize variables
set cmdFile ""
set tList $argv
set execScript ""
# process options
set endOptIndex -1
foreach arg $argv {
if {![string match "-/[a-zA-Z]*" $arg]} {
break
}
# inc end option index
incr endOptIndex
# get option flag and option value
set optFlg [string range $arg 1 1]
set optVal [string range $arg 2 end]
dbgLog "$optFlg $optVal"
if {$optVal == ""} {
dbgLog "option value is null: -$optFlg"
return -1
}
switch $optFlg {
"u" {
set g_user $optVal
dbgLog "user: $g_user"
}
"p" {
set g_passwd $optVal
dbgLog "password: $g_passwd"
}
"i" {
set g_devip $optVal
dbgLog "devip: $g_devip"
}
"c" {
set cmdFile $optVal
dbgLog "cmdFile: $cmdFile"
}
"s" {
set execScript $optVal
dbgLog "execScript: $execScript"
}
default {
puts "unknown option: -$optFlg"
return -1
}
} ;# end switch
} ;# end foreach
# remove options from list
if {$endOptIndex != -1} {
set tList [lreplace $argv 0 $endOptIndex]
}
dbgLog "tList is: $tList"
# create log dir
if { ![file exist "log"] || ![file isdirectory "log"] } {
puts "please create directory /"log/""
return -1
}
# read current time
set clicks [clock clicks]
set tstr [clock format $clicks -format "%y%m%d%I%M%S"]
# open log file
log_file "log/vLog$tstr.log"
# open brief log file
set g_bLogFd [open "log/bLog.log" w]
# start testing
foreach tItem $tList {
switch $tItem {
"sys_001" { ;# test group sys_001
source snmp.exp
}
"cmd" { ;# exec cmd file
source tCmd.exp
}
"script" { ;# exec script file
if {$execScript == ""} {
puts "Please specify script name using -s option"
return -1
}
source $execScript
}
default {
puts "do you want to test /"$tItem/"/?"
}
}
}
close $g_bLogFd
在程序开始,通过source导入两个文件,其中global.exp中主要存放了一些全局变量的定义,因为这些全局变量对每台测试设备可能各不相同,所以把它们提取出来。commonLib.exp文件中存放着一些通用子程序,可供各测试程序调用。我们前面介绍过的login子程序,就放在此文件中。
接下来,分析命令行参数,首先提取出所有的选项参数,目前支持的命令行选项包括:
-u :此选项用来更改登录的用户名
-p :此选项用来更改登录的密码
-i :此选项用来更改telnet的IP地址
-c :此选项用来指明批处理文件的文件名,用法在后面描述
-s :此选项用来指明脚本文件的文件名,用法在后面描述
最后,命令行参数中所有非选项的部分,都被做为测试项,分别对这些测试项进行测试。
例如测试项test_001,会使用source命令调用snmp.exp脚本,进行snmp community方面的测试。
可以根据需要自行添加测试项目。
有两个特别的测试项名称,分别为cmd和script。
cmd测试项,会调用cmd.exp脚本,这个脚本在后面介绍,它的主要功能是执行一个文本文件里的所有命令。文本文件名由-c选项提供。
script测试项,它会调用source命令,执行$execScript脚本。可以使用-s选项为$execScript变量赋值。
这个测试脚本提供了两种日志,一种是详细的日志(vLog*),包括了telnet的所有交互过程;另外一种是简单的日志,只包含程序中使用errLog输出的信息。日志文件被放在子目录log中,其文件名中包含了脚本执行的时间,方便查找。
本脚本中使用dbgLog,以及以后将用到的errLog,都是定义在commonLib.exp文件中的子函数,代码如下:
#************************************************
# debug output routine
#
# @PARAMS
# arg - variable length arguments
#************************************************
proc dbgLog arg {
global g_dbgFlag
if {$g_dbgFlag} {
puts $arg
}
}
#************************************************
# error output routine
#
# @PARAMS
# arg - variable length arguments
#************************************************
proc errLog arg {
global g_bLogFd
global g_dbgFlag
if {$g_dbgFlag} {
puts $arg
}
if { $g_bLogFd != 0 } {
puts $g_bLogFd $arg
}
}
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
TCL/EXPECT自动化测试脚本实例三 --- 全局变量
文章出处:网络 作者: 发布时间:2007-02-26
global.exp文件的内容,只是定义一些全局变量,供其它文件使用
下面是global.exp文件的内容,只是定义一些全局变量,供其它文件使用。
# $Id$
# global variables
set g_dbgFlag 1 ;# Debug flag
set g_bLogFd 0 ;# Error Log FD
set g_devip "192.168.1.222" ;# Default device IP address
set g_prompt "$" ;# CLI prompt
set g_user "root" ;# login account name
set g_passwd "root" ;# login password
set g_usrPrompt "*ogin:" ;# login prompt
set g_pwdPrompt "*assword:" ;# login password prompt
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
TCL/EXPECT自动化测试脚本实例四 --- 批命令执行
文章出处:网络 作者: 发布时间:2007-02-26
在测试过程中,在具体测试某一个功能点时,往往需要为此进行大量的配置。为了简化测试过程,我们可以把所有的配置命令放在一个文本文件中,然后使用测试脚本来执行这些命令。这里讲的脚本,就可以用来执行指定的命令文件。此脚本被上一篇中介绍的test.exp调用。
在测试过程中,在具体测试某一个功能点时,往往需要为此进行大量的配置。为了简化测试过程,我们可以把所有的配置命令放在一个文本文件中,然后使用测试脚本来执行这些命令。这样就不需要再手工进行配置了,费时费力。
基于如上考虑,编写了下面的脚本tCmd.exp。这个脚本被我们前面介绍过的test.exp脚本调用。
# $Id$
# This file is used to execute specific commands list in a file
proc execCmdFile {cmdFile} {
global g_dbgFlag g_prompt
# enable debug
set g_dbgFlag 1
# login
set spawn_id [login $g_devip $g_user $g_passwd]
if {$spawn_id == 0} {
errLog "login $g_devip failed"
return 0
}
# open cmdFile
set cmdFd [open $cmdFile r]
while true {
# get a line
if {![getLine $cmdFd line]} {
dbgLog "reached eof"
break
}
# split the line
set ln [split $line ","]
set cmd [string trim [lindex $ln 0]]
set out [string trim [lindex $ln 1]]
if {$cmd == ""} continue
if {$out == ""} set out $g_prompt
# send cmd line
exp_send "$cmd/n"
dbgLog "send $cmd"
# expect output
dbgLog "expect $out"
expect {
timeout {
errLog "TIMEOUT: while exec /"$cmd/""
continue
}
-ex "$out" {
continue
}
} ;# end expect
}
# close cmdFile
close $cmdFd
}
# if no cmdFile, use default
if {$cmdFile == ""} {
set cmdFile "cmdFile.txt"
}
execCmdFile $cmdFile
有了这个脚本,我们可以使用"./test.exp -cinterface.txt cmd"来执行interface.txt中的命令
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
TCL/EXPECT自动化测试脚本实例五 --- 由文件中读取一行
文章出处:网络 作者: 发布时间:2007-02-26
这个函数由文件中读取一行,跳过空行和注释行。
代码见下,比较简单,就不再分析了。调用实例见前面的文章。
#************************************************
# get a line from file, skip blank lines and
# comment lines, return the reading line in
# parameter 'line'.
#
# @PARAMS
# fd - file fd
# line - var used to return the line
#
# @RETURN
# return 1 if read successfully, otherwise 0
#************************************************
proc getLine {fd line} {
upvar $line ln
# read a line from fd
while {[set lineLen [gets $fd ln]] >= 0} {
# blank line
if { $lineLen == 0 } continue
# trim whitespace
set ln [string trim $ln]
if { [string length $ln] == 0 } continue
# skip comment
if { [string index $ln 0] == "#" } continue
# success
return 1
}
return 0
}
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
http://www.51testing.com/?action-tag-tagname-tcl
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
http://www.qiusuo365.com
你我知识分享社区
lTCL的使用
求索知识分享社区
http://www.qiusuo365.com
目录
TCL 简介 .................................................................................................................. 3
I.
TCL 的语法............................................................................................................... 4
II.
A. 脚本、命令和单词符号....................................................................................... 4
B. 置换(substitution) ................................................................................................ 4
C. 双引号和花括号 ................................................................................................. 5
变量................................................................................................................... 5
III.
A. 简单变量............................................................................................................ 5
B. 数组................................................................................................................... 6
表达式 ............................................................................................................... 6
IV.
A. 数 ...................................................................................................................... 6
A. 运算符和优先级 ................................................................................................. 7
B. 数学函数............................................................................................................ 7
控制流 ...................................................................................................................... 9
V .
A. if 命令................................................................................................................ 9
B. 循环命令:while 、for、 foreach....................................................................... 9
C. switch 命令...................................................................................................... 10
过程................................................................................................................. 10
VI.
A. 过程定义...........................................................................................................11
B. 局部变量和全局变量 .........................................................................................11
C. 缺省参数和可变个数参数 ..................................................................................11
D. 引用:upvar ..................................................................................................... 12
TCL 和 C/C++语言 ........................................................................................... 12
VII.
A. 用 C/C++语言扩展 TCL 命令 ............................................................................ 13
B. 在 C/C++应用程序中嵌入 TCL................................................................... 14
C. 扩展 TCL 命令时使用自己定义的数据类型 ................................................ 15
D. TCL 命令的返回值和命令过程的返回值............................................................ 18
B. 总结................................................................................................................. 19
http://www.qiusuo365.com
TCL的使用
关键词:TCL
摘 要:本文较为详细的介绍了TCL语言。并结合自己使用TCL的经验着重介绍了C/C+
+语言的和TCL语言之间的联系。 其中有详尽的实例, 对学习和使用TCL语言有很
大的帮助。
I. TCL简介
TCL(Tool Command Language)是一种解释执行的脚本语言(Scripting
Language)。它拥有一个固有的核心命令集,同时还具有和C/C++语言类似的控制
结构:if控制、循环控制和switch控制等,并支持过程的定义和调用,对数组和
字符串等简单数据结构也提供了支持。
由于TCL的解释器是用一个C/C++语言的过程库实现的,这个库中有
丰富的用于扩展TCL命令的C/C++过程和函数,所以可以较为容易的在C/C++应
用程序中嵌入TCL,而且每个应用程序都可以根据自己的需要对TCL语言进行扩
展。我们可以针对某一特定应用领域对TCL语言的核心命令集合进行扩展,加入
适合于自己的应用领域的扩展命令,如果需要,甚至可以加入新的控制结构,
TCL解释器将把扩展命令和扩展控制结构与固有命令和固有控制结构同等看待。
扩展后的TCL语言将可以继承TCL 核心部分的所有功能,包括核心命令、控制
结构、数据类型、对过程的支持等。根据需要,我们还可以屏蔽掉TCL的某些固
有命令和固有控制结构, 一旦我们新定义的命令和控制结构与固有命令和控制结
构同名,固有命令和控制结构将被新定义的命令和控制结构所屏蔽。通过对TCL
的扩展、 继承或屏蔽, 用户用不着象平时定义一种计算机语言那样对词法、 语法、
语义、语用等各方面加以定义,就可以方便的为自己的应用领域提供一种功能完
备的脚本语言。
TCL良好的可扩展性使得它能很好地适应产品测试的需要,测试任务常
常会由于设计和需求的改变而迅速改变,往往让测试人员疲于应付。利用TCL的
可扩展性,测试人员就可以迅速继承多种新技术,并针对产品新特点迅速推出扩
展TCL命令集,以用于产品的测试中,可以较容易跟上设计需求的变化。
另外,因为TCL是一种比C/C++ 语言有着更高抽象层次的语言,使用
TCL可以在一种更高的层次上编写程序,它屏蔽掉了编写C/C++程序时必须涉及
到的一些较为烦琐的细节,可以大大地提高开发测试例的速度。而且, 我们使
用TCL语言写的测试例脚本,即使作了修改,也用不着重新编译就可以调用TCL
解释器执行。可以省却不少时间。
TCL 目前已成为自动测试中事实上的标准。
http://www.qiusuo365.com
II. TCL的语法
TCL语言的语法事实上是一些怎样对TCL命令进行分析的规则的集合。
脚本、命令和单词符号
A.
一个TCL脚本可以包含一个或多个命令。 命令之间必须用换行符或分号
隔开,下面的两个脚本都是合法的:
set a 1
set b 2
或
set a 1;set b 2
TCL的每一个命令包含一个或几个单词符号,第一个单词符号代表命
令名,另外的单词符号则是这个命令的参数,单词符号之间必须用空格或TAB
键隔开。
TCL解释器对一个命令的求值过程分为两部分:分析和执行。在分析
阶段,TCL 解释器运用规则把命令分成一个个独立的单词符号,并进行置换
(substitution)。 在执行阶段,TCL 解释器会把第一个单词符号当作命令名,并
查看这个命令是否有定义,如果有定义就激活这个命令过程,并把所有的单词符
号作为参数传递给命令过程,让命令过程进行处理。
置换(substitution)
B.
TCL解释器在分析命令时,把所有的命令参数都当作字符串看待,例如:
set x 10
set y x+100 //y的值是x+100,而不是200
上例的第二个命令中, 如果我们想使用x
x被看作字符串x+100的一部分,
的值'100' 就需要用到TCL语言中提供的置换功能。TCL提供三种形式的置换:
变量置换、命令置换和反斜杠置换。每种置换都会导致一个或多个单词符号本身
被其他的值所代替。置换可以发生在包括命令名在内的每一个单词符号中,而且
置换可以嵌套。
变量置换:它由一个$符号标记,变量置换会导致变量的值插入一个单
词符号中。例如:
y $x+100 //y的值是10+100,这里x被置换成它的值10
set
这时,y的值还不是我们想要的值110,而是10+100,因为TC解释器把
10+100看成是一个字符串而不是表达式,y要想得到值110,还必须用命令置换,
使得TCL会把10+100看成一个表达式并求值。
命令置换: 命令置换是由[]括起来的TCL命令及其参数, 命令置换会导致
某一个命令的所有或部分单词符号被另一个命令的结果所代替。例如:
//y的值是110,这里x被置换成它的值10,然后expr
set y [expr $x+100]
http://www.qiusuo365.com
//会把10+100作为表达式求值,为110
命令
注意,[]中的单词符号必须是一个合法的TCL脚本,长度不限。[]中脚
本的值为最后一个命令的返回值,例如:
set y [expr $x+100;set b 300] //y的值为300,因为set b 300的返回值为
300
反斜杠置换:类似于C中反斜杠的用法,主要用于在单词符号中插入诸
如换行符、空格、[、$等被TCL解释器当作特殊符号对待的字符。例如:
set msg multiple/ space //这里最后两个单词之间的空格不是分隔符
//msg的值为multiple space。
set msg money/ /$3333/ /nArray/ a
2]
//这个命令的执行结果为:money $3333
Array a[2]
双引号和花括号
C.
除了使用反斜杠外, TCL提供另外两种种方法来使得解释器对分隔符和
置换符等特殊字符当作普通字符,而不作特殊处理,这就要使用双引号和花括号
({})。
TCL解释器对双引号中的各种分隔符将不作处理,但是对换行符 及$
和[]两种置换符会照常处理。而在花括号中,所有特殊字符都将成为普通字符,
失去其特殊意义,TCL解释器不会对其作特殊处理。 例如:
set x 100
set y "{$x ddd" // y的值为 {100 ddd
set y {/n$x [expr 10+100]} // y的值为 /n$x [expr 10+100]
变量
III.
TCL支持两种类型的变量:简单变量和数组。
简单变量
A.
一个TCL的简单变量包含两个部分:名字和值。名字和值都可以是任
意字符串。例如一个名为 “1323 7&*: hdgg"的变量在TCL中都是合法的。不过
为了更好的使用置换(substitution), 变量名最好按C/C++语言中标识符的命名规则
命名。因为TCL解释器在分析一个变量置换时,只把位于$符号后和其后第一个
不是字母、数字或下划线的字符之间的单词符号作为要被置换的变量的名字。当
然,如果变量名中有不是字母、数字或下划线的字符,又要用置换,可以用花括
号把变量名括起来。
http://www.qiusuo365.com
也能读取或改变一个变量的值。 例如:
TCL中的set命令能生成一个变量、
set a {kdfj kjdf}
如果变量a还没有定义, 这个命令将生成 变量a, 并将其值置为kdfj kjdf,
若a已定义,就简单的把a的值置为kdfj kjdf。
set a
这个只有一个参数的set命令能获取a的当前值kdfj kjdf
数组
B.
数组是一些元素的集合。TCL的数组和普通计算机语言中的数组有很大
的区别。在TCL中,数组元素的名字包含两部分:数组名和数组中元素的名字。
例如:
set day(monday) 1
set day(tuesday) 2
第一个命令生成一个名为day的数组,同时在数组中生成一个名为
monday的元素,并把值置为1,第二个命令生成一个名为tuesday的元素,并把值
置为2。
简单变量的置换已经在前一节讨论过,这里讲一下数组元素的置换。除
了要加括号之外,数组元素的置换和简单变量类似。例:
set a monday
set day(monday) 1
set b $day(monday) //b的值为1,即day(monday)的值。
set c $day($a) //c的值为1,即day(monday)的值。
表达式
IV.
TCL中的很多命令都有一个或几个几个参数是表达式形式(如if、
while、for等)。其中最简单的一个命令就是expr,它把参数作为一个表达式进行
求值,并返回结果。
如:
expr 8+4 //结果为12
数
A.
表达式的操作数通常是整数或实数。整数一般是十进制的,但如果整数
的第一个字符是0,那么TCL将把这个整数看作八进制的,如果前两个字符是0x
则这个整数被看作是十六进制的。TCL的实数的写法与ANSI C中完全一样。如:
2.1
7.9e+12
6e4
http://www.qiusuo365.com
3.
语法形式 结果 操作数类型
负a
-a int,float
非a
!a int,float
~a int
乘
a*b int,float
除
a/b int,float
取模
a%b int
加
a+b int,float
减
a-b int,float
左移位
a< 右移位
a>>b int
小于
a 大于
a>b int,float,string
小于等于
a<=b int,float,string
大于等于
a>=b int,float,string
等于
a= =b int,float,string
不等于
a!=b int,float,string
位操作与
a&b int
位操作异或
a^b int
位操作或
a|b int
逻辑与
a&&b int,float
逻辑或
a||b int,float
选择运算
a?b:c a:int,float
运算符和优先级
A.
上面的表格中列出了TCL中用到的运算符,它们的语法形式和用法和
ANSI C中很相似。这里就不一一介绍。上表中的运算符是按优先级从高到低往
下排列的。同一格中的运算符优先级相同。
数学函数
B.
TCL支持常用的数学函数,表达式中数学函数的写法类似于C/C++语
言的写法, 数学函数的参数可以是任意表达式, 多个参数之间用逗号隔开。 例如:
expr 2* sin(2<3) //1.68294196962
注意,这些数学函数并不是命令,只能在表达式中出现。
TCL中支持的数学函数如下
Absolute value of x.
abs( x)
Arc cosine of x, in the range 0 to π.
acos( x)
http://www.qiusuo365.com
Arc sine of x, in the range -π/2 to π/2.
asin( x)
Arc tangent of x, in the range -π/2 to π/2.
atan( x)
Arc tangent of x/ y, in the range -π/2 to π/2.
atan2( x, y)
Smallest integer not less than x.
ceil( x)
Cosine of x ( x in radians).
cos( x)
Hyperbolic cosine of x.
cosh( x)
double( i) Real value equal to integer i.
e raised to the power x.
exp( x)
Largest integer not greater than x.
floor( x)
Floating-point remainder of x divided by y.
fmod( x, y)
Square root of ( x 2 + y 2 ).
hypot( x, y)
Integer value produced by truncating x.
int( x)
Natural logarithm of x.
log( x)
Base 10 logarithm of x.
log10( x)
pow( x, y) x raised to the power y.
Integer value produced by rounding x.
round( x)
Sine of x ( x in radians).
sin( x)
Hyperbolic sine of x.
sinh( x)
Square root of x.
sqrt( x)
Tangent of x ( x in radians).
tan( x)
Hyperbolic tangent of x.
tanh( x)
http://www.qiusuo365.com
控制流
V.
TCL中的控制流和C语言类似,包括if,while,for,foreach,switch和eval。
A. if命令
语法:
if test1?then?body1?elseif test2?then? body2 elseif....?else?bodyn?
TCL先把test1当作一个表达式求值,如果值非0,则把body1当作一个脚
本执行并返回所得值,否则把test2当作一个表达式求值,如果值非0,则把body2
当作一个脚本执行并返回所得值...。例如:
if {$x>0}{
.....
}elseif{$x==1} {
.....
}elseif {$x==2}{
....
}
else{
.....
}
注意,上例中'{'一定要写在上一行,因为如果不这样,TCL 解释器会认
为if命令在换行符处已结束,下一行会被当成新的命令,从而导致错误的结果。
在下面的循环命令的书写中也要注意这个问题。书写中还要注意的一个问题是if
和{之间应该有一个空格,否则TCL解释器会把'if{'作为一个整体当作一个命令
名,从而导致错误。
B. 循环命令:while 、for、 foreach
while命令有两个参数,一个表达式和一个脚本,如果表达式的值非0,就运
行脚本,直到表达式为0才停止循环,此时while命令中断并返回一个空字符串。
例如:
假设变量 a 是一个链表,下面的脚本把a 的值复制到b:
set b " "
set i [expr [llength $a] -1]
while ($i>=0){
lappend b [lindex Sa Si]
http://www.qiusuo365.com
incr i -1
}
for命令有四个参数。第一个参数是一个初始化脚本,第二个参数是一个表达
式,用来决定循环什么时候中断,第三个参数是一个重新初始化的脚本,第四个
参数也是脚本,代表循环体。下例与上例作用相同:
set b " "
for {set i [expr [llength $a] -1]} {$i>=0} {incr i -1} {
lappend b [lindex Sa Si] }
foreach命令的第一个参数是一个变量名,第二个是一个链表,第三个
是循体。每次取得链表的一个元素,都会执行循环体一次。 下例与上例作用相
同:
set b " "
forreach i $a{
set b [linsert $b 0 $i]
}
在循环体中,可以用break和continue命令中断循环。其中break命令结
束整个循环过程,并从循环中跳出,continue只是结束本次循环。
C. switch 命令
和C语言中switch语句一样,TCL中的switch语句也可以由if语句实现。
只是书写起来较为烦琐。 switch命令有两个参数,第一个参数是要被用来作测
试的值,第二个参数是包括一个或多个元素对的列表,例:
switch $x {
a -
b {incr t1}
c {incr t2}
default {incr t3}
}
其中a的后面跟一个'-'表示使用下一个模式的脚本。 default表示匹配
任意值。一旦switch命令 找到一个模式匹配,就执行相应的脚本,并返回脚本
的值,作为switch命令的返回值。
过程
VI.
TCL支持过程的定义和调用,TCL中过程可以看作是用TCL脚本实现
的命令,效果与TCL的固有命令相似。我们可以在任何时候使用proc命令定义自
己的过程,TCL中的过程类似于C中的函数。
http://www.qiusuo365.com
A. 过程定义
TCL中过程是由proc命令产生的:
例如:
proc add {x y } {expr $x+$y}
proc命令的第一个参数是你要定义的过程的名字,第二个参数是过程
的参数列表,第三个参数是一个TCL脚本,代表过程体。 proc生成一个新的命
令,可以象固有命令一样调用:
add 1 2 //3
你可以利用return命令来在你愿意的地方返回你象要的值, return命令迅
速中断过程,并把它的参数作为过程的结果。
B. 局部变量和全局变量
对于在过程中定义的变量,因为它们资料在过程中被访问,并且当过程退
出时会被自动删除,所以成为局部变量。而在任何过程之外定义的变量称为全局
变量。当某个局部变量的值和全局变量同名时,两者之间也互不相关,对其中某
一个变量的值进行改变不会涉及到另一个变量。
在一个过程中可以使用global命令来引用全局变量的值。例如命令:
global x
会使得全局变量x在过程中能被访问。这时在过程中对x的改变会直接反
映到全局变量上。
C. 缺省参数和可变个数参数
TCL还提供三种特殊的参数形式:
首先,你可以定义一个没有参数的过程,例如:
proc add {} { expr 2+3}
其次,可以定义具有缺省参数值的过程,我们可以为过程的部分或全
部参数提供缺省值,如果调用过程时未提供那些参数的值,那么过程会自动使用
缺省值赋给相应的参数。当然和C/C++中具有缺省参数值的函数一样,有缺省值
的参数只能位于参数列表的后部,在第一个具有缺省值的参数后面的参数,也只
能是具有缺省值的参数。
例如:
proc add {val1 {val2 2} {val3 3}} {
expr $val1+$val2+$val3
}
则:
add 1 //值为6
add 2 20 //值为25
http://www.qiusuo365.com
add 4 5 6 //值为15
另外,TCL的过程定义还支持可变个数的参数,如果过程的最后一个
参数是args, 那么就可以用可变个数的参数调用。 位于args以前的参数象普通参数
一样处理,但任何附加的参数都需要在过程体中作特殊处理,过程的局部变量
args将会设置为一个列表,其元素就是所有附加的变量。如果没有附加的变量,
args就设置成一个空串,下面是一个例子:
proc add { val1 args } {
set sum $val1
foreach i $args {
incr sum $i
}
return $sum
}
则:
add 2 //值为2
add 2 3 4 5 6 //值为20
D. 引用:upvar
TCL提供了一种类似于C/C++语言中的指针和引用的方法,来使得用
户可以在过程中对过程外部的变量进行访问。这时就要用到upvar命令。 upvar
命令的第一个参数是我们希望在过程中访问的变量的名字, 第二个参数是是一个
局部变量的名字, 在过程中对这个局部变量的读写就相当于对要访问的变量的读
写。下面是一个例子:
proc temp { arg } {
upvar $arg a
set a [expr $a+2]
}
proc myexp { var } {
set a 4
temp a
return $var+$a
}
则:
myexp 9 //结果为 9+6
set a 10
temp a //a的值变为12
VII. TCL和C/C++语言
因为TCL的解释器是由一个C库实现的,所以可以方便的把它连接到一
http://www.qiusuo365.com
个C/C++应用程序中。 在应用程序中, 我们可以调用TCL提供的库函数来生成TCL
解释器、求一个TCL脚本的值或扩展TCL固有命令。TCL提供了一组有用的库函
数来供用户访问TCL的变量、分析命令参数、操作TCL列表、求TCL表达式的值
等。
A. 用C/C++语言扩展TCL命令
在生成自己的扩展命令时,我们首先必须生成一个TCL解释器,然后创
建自己的命令,并把命令登记到这个解释器上,那么在这个解释器中,用户就可
以调用自己定义的扩展命令了,就象调用TCL的固有命令一样。下面是一个扩展
TCL命令sum的例子:
#include "tcl.h"
#include "stdio.h"
#include "stdlib.h"
int Tcl_AppInit(Tcl_Interp *interp);
int SumCmd( ClientData clientData,Tcl_Interp * interp,int argc, char* argv[]);
main(int argc, char *argv[])
{
Tcl_Main(argc, argv,Tcl_AppInit);
exit(0);
}
int Tcl_AppInit(Tcl_Interp *interp)
{
// Register application-specific commands.
Tcl_CreateCommand (interp, "sum", SumCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
return TCL_OK;
}
int SumCmd( ClientData clientData,Tcl_Interp * interp,int argc, char* argv[])
{
int tmp, error;
int sum=0;
if (argc!= 2)
{
interp->result = "Usage: sum ?range?";
return TCL_ERROR;
}
error = Tcl_GetInt (interp, argv[1], &tmp);
if (error != TCL_OK)
return error;
for(int i=0;i<=tmp;i++)
http://www.qiusuo365.com
sum+=i;
sprintf(interp->result, "%d", sum);
return TCL_OK;
}
编译运行上述程序,就可以得到一个类似于DOS的交互式命令窗口,在其
中你可 以执行TCL的固有命令, 当然也可以执行自己刚刚定义的sum命令, 和前
者用起来没有任何差别。 事实上,如果我们使用TCL库函数来扩展针对某一个
应用的命令集,那么就可以在这个命令窗口执行其中的命令,用来解决这个应用
中的问题。
B. 在C/C++应用程序中嵌入TCL
这个例子中也实现了sum扩展命令,但是通过调用Tcl_EvalFile函数
从外部调入TCL脚本进行处理。通过这个例子,我们可以发现,如果针对某一个
特定的应用领域,我们利用TCL提供的C库扩展了TCL针对这个领域的命令,那
么我们就只要简单的编写TCL脚本,然后调用TCL解释器处理这一脚本,就可以
解决这一应用领域的问题。
例子如下:
#include "stdio.h"
#include "stdlib.h"
#include "tcl.h"
int SumCmd(ClientData clientData,Tcl_Interp *interp,int argc, char* argv[]);
int Sum_Init(Tcl_Interp* interp);
main(int argc, char *argv[])
{
Tcl_Interp *interp;
int code;
if (argc != 2)
{
fprintf(stderr, "Wrong # arguments: ");
fprintf(stderr,"should be /"%s fileName/"/n",argv[0]);
exit(1);
}
interp = Tcl_CreateInterp();
Sum_Init(interp);
code = Tcl_EvalFile(interp, argv[1]);
if (code != TCL_OK)
printf("Evalue file error!/n");
exit(0);
}
int Sum_Init(Tcl_Interp* interp)
http://www.qiusuo365.com
{
Tcl_CreateCommand(interp, "sum", SumCmd,
(ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
return TCL_OK;
}
int SumCmd( ClientData clientData,Tcl_Interp * interp,int argc, char* argv[])
{
int tmp, error;
int sum=0;
if (argc!= 2)
{
interp->result = "Usage: sum ?range?";
printf("%s",interp->result);//output the result
return TCL_ERROR;
}
error = Tcl_GetInt (interp, argv[1], &tmp);
if (error != TCL_OK)
return error;
for(int i=0;i<=tmp;i++)
sum+=i;
sprintf(interp->result, "%d", sum);
printf("%s",interp->result);//output the result
return TCL_OK;
}
编译这个程序,并提供一个TCL脚本文件,就可以调用这个程序处理这
个脚本文件。事实上,这个例子说明TCL可以被嵌入任何一个应用程序,例如,
我们可以把上面的程序稍作修改,做成一个菜单命令,并且提供一个文本编辑窗
口,那么用户就可以在编辑窗口中编辑TCL脚本文件,其中可以包括TCL固有命
令、自己的扩展命令和各种控制结构,然后使用菜单命令就可以执行这个脚本文
件,并得到结果。
C. 扩展TCL命令时使用自己定义的数据类型
在处理复杂的应用领域问题时,我们不可避免地要自己定义诸如结构
(struct)、联合(union)、类(class)等较为复杂的数据类型。如果我们要在这些应用
领域扩展TCL命令,必然会涉及到在扩展TCL命令时,怎么使用自己定义的复杂
数据类型的问题。
前面的例子中,我们已经看到,每一个命令函数的第一个参数是
ClientData类型,而用来注册自定义命令的库函数Tcl_CreateCommand,也有一个
ClientData类型的参数。 这个ClientData 类型参数指向的就是自定义命令所要处
理的对象,而且ClientData 被定义为void *类型,可以指向任何类型的对象。如
果用户想在命令中使用自己定义的数据类型,就可以利用ClientData。
http://www.qiusuo365.com
(Tcl_CmdDeleteProc *〕
Tcl_CreateCommand函数还有一个参数是
类型,这个参数指向一个回叫(callback)函数,当删除某一个命令或程序退出时,
TCL会调用对应的回叫函数来对该命令处理的数据对象进行善后处理。
Tcl_CmdDeleteProc 的定义如下:
typedef void (Tcl_CmdDeleteProc) _ANSI_ARGS_((ClientData
clientData))
可以看出,Tcl_CmdDeleteProc * 类型是指向一个带有一个ClientData类
型参数,无返回值的函数的指针,我们可以设计一个这样的回叫函数来释放自定
义命令所处理的数据类型的对象申请的存储空间。
下面将以一个简单的类(class)来说明在扩展TCL命令时怎么使用自己定
义的数据类型。
以下是例子的源程序:
#include "tcl.h"
class temp
{
public:
temp();
temp(int t){x=t;}
int getx();
void setx(int t);
protected:
int x;
};
int temp::getx()
{
return x;
}
void temp::setx(int t)
{
x=t;
}
int Tcl_InitApp(Tcl_Interp* interp);
int Get(ClientData clientdata,Tcl_Interp* interp,int argc,char* argv[]);
int Set(ClientData clientdata,Tcl_Interp* interp,int argc,char* argv[]);
void DeleteTest(ClientData clientdata);
void main(int argc,char *argv[])
{
http://www.qiusuo365.com
Tcl_Main(argc,argv,Tcl_InitApp);
return;
}
int Tcl_InitApp(Tcl_Interp* interp)
{
//if(Tcl_Init(interp)==TCL_ERROR)
// return TCL_ERROR;
temp* pTemp=new temp(2);
Tcl_CreateCommand (interp,"myget",Get,
(ClientData)pTemp,(Tcl_CmdDeleteProc *)DeleteTest);
Tcl_CreateCommand (interp,"myset",Set,
(ClientData)pTemp,(Tcl_CmdDeleteProc *)DeleteTest);
interp->result="Init Error";
return TCL_OK;
}
int Get(ClientData clientdata,Tcl_Interp* interp,int argc,char* argv[])
{
if(argc!=1)
{
interp->result="usage:get";
return TCL_ERROR;
}
temp* pTemp= (temp*)clientdata;
sprintf(interp->result,"%d",pTemp->getx());
return TCL_OK;
}
int Set(ClientData clientdata,Tcl_Interp* interp,int argc,char* argv[])
{
if(argc!=2)
{
interp->result="usage:set ?arg?";
return TCL_ERROR;
}
int iTmp;
int error=Tcl_GetInt(interp,argv[1],&iTmp);
if(error!=TCL_OK)
return error;
temp* pTemp= (temp*)clientdata;
pTemp->setx(iTmp);
sprintf(interp->result,"%d",pTemp->getx());
http://www.qiusuo365.com
return TCL_OK;
}
void DeleteTest(ClientData clientdata)
{
static int i;
if(i>=1) return;
temp* pTemp=(temp*)clientdata;
delete pTemp;
i++;
}
当然,我们也可以使用全局变量的形式,而不利用ClientData类型的参
数来传递自定义数据类型的对象,但这样会导致程序的结构较差,可读性和可重
用性降低。
D. TCL命令的返回值和命令过程的返回值
TCL命令的返回值总是一个字符串,存储在interp->result中。而一个
定义命令的过程的返回值是一个整数,可以是 TCL_ERROR 、 TCL_OK 、
TCL_BREAK 、TCL_CONTINUE、TCL_RETURN,这些返回值是TCL为了方
便实现控制流和对脚本文件的求值进行控制而引入的。 通过设置命令过程的返回
值,我们可以控制TCL脚本的执行过程,也可以实现自己的控制流。例如TCL中
的while命令就是这样实现的。
int WhileCmd(ClientData clientData, Tcl_Interp *interp,
int argc, char *argv[])
{
int bool;
int code;
if (argc != 3)
{
interp->result = "wrong # args";
return TCL_ERROR;
}
while (1)
{
Tcl_ResetResult(interp);
if (Tcl_ExprBoolean(interp, argv[1], &bool)!= TCL_OK)
{
return TCL_ERROR;
}
if (bool == 0)
{
return TCL_OK;
http://www.qiusuo365.com
}
code = Tcl_Eval(interp, argv[2]);
if (code == TCL_CONTINUE)
{
continue;
}
else if (code == TCL_BREAK)
{
return TCL_OK;
}
else if (code != TCL_OK)
{
return code;
}
}
我们在自己设计TCL扩展命令时,也应该注意设置TCL命令过程的返回
值以控制TCL命令的执行过程。例如:如果我们希望当某一个命令有错时,整个
脚本还能继续往下执行, 那么我们可以把那个命令过程的返回值总设为TCL_OK。
B. 总结
本文介绍了TCL语言的使用,其中有很多是我学习、使用TCL的经验,应该比
较易懂。本文基本上介绍了TCL的各个方面,特别对使用C/C++语言扩展TCL命
令作了详尽的描述,参照本文的例子,用户完全可以写出自己的TCL扩展命令。
希望这篇文章能对推广在测试部使用TCL起一些推动作用。
TCL 中提供了对列表(list)和字符串的丰富的命令,限于篇幅,本文没有介绍。不过
其中很多命令都很容易理解,查阅参考书就能很快明白。
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn
http://www.qiusuo365.com
你我知识分享社区
l TCL系列讲座
求索知识分享社区
http://www.qiusuo365.com
TCL系列之一:不费吹灰之力,建立自己的TCL应用程序
在开始这一切之前, 你需要安装TCL。 chenxush18960/public,password:test下有TCL
的最新版本8.2.3,以及TCL、TK的源码,还有一些介绍TCL、TK的参考书。运
行安装程序后,在安装目录下会有bin,include,lib等目录,其中包含了我们用C
语言编写自己的TCL扩展时要用到的动态连接库、头文件和库文件。
我们可以利用TCL为我们提供的库函数,毫不费力的建立自己的TCL应用程序,
而且只需两行代码:
#include "tcl.h"
void main(int argc,char* argv[])
{
Tcl_Main(argc,argv,Tcl_Init);
return;
}
把库文件tcl82.lib添加到工程中,编译运行这个程序,你会发现你自己
建立了一个和你安装的TCL823
完全一样的交互时命令行执行程序!在其中你可以使用各种TCL的固有命令,如
set,expr等。
Tcl_Main库函数中涉及到很复杂的函数调用和处理, 主要完成以下几个工
作:
生成TCL解释器
调用Tcl_Init初始化应用
检查命令行参数,若有对命令行参数进行求值,否则进入一个命令解释循
环。
下回题目:建立自己的TCL扩展命令
TCL系列之二:建立自己的TCL扩展命令
TCL具有良好的可扩展性,我们可以利用它提供的库函数较为方便的根据自己的应用的需要编写
TCL扩展命令,为了尽快
切入主题,下面的代码实现一个简单的TCL扩展命令,这个命令简单的实现两个整数相加:
int AddCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char * argv[])
http://www.qiusuo365.com
{
if(argc!=3)
{
interp->result="Useage Error! should be : add int1 int2";
return TCL_ERROR;
}
int i,j;
if(TCL_OK!=Tcl_GetInt(interp,argv[1],&i))
{
sprintf(interp->result,"Expect integer but got %s",argv[1]);
return TCL_ERROR;
}
if(TCL_OK!=Tcl_GetInt(interp,argv[2],&j))
sprintf(interp->result,"Expect integer but got %s",argv[2]);
return TCL_ERROR;
}
i=i+j;
sprintf(interp->result,"%d",i);
return TCL_OK;
}
这个函数中第一个参数clientdata暂时可以不予理会,interp参数执行一个TCL解释器,argc
和argv和我们编写C语言程序时
main函数的两个参数的含义基本一样,argc代表命令的参数个数(包括命令名自身〕,argv是一
个字符串数组记录各个参数的字
符形式。
因为命令的形式是add int1 int2,所以首先判断参数个数是否是三个,不是就报错。因为第
二个参数argv[1]是字符形式,
所以调用Tcl_GetInt函数从argv[1]中取得整数形式的值,如果失败就保错,第三个参数的值用
同样的方法取得,两者
相加,结果以字符形式存入interp->result中。
以上只是给出了命令实现的过程, 要使命令能被TCL解释器解释执行, 还需要把命令注册到TCL
解释器中,这个过程一般是在应用
的初始化函数中完成:
int Tcl_InitApp(Tcl_Interp* interp)
{
Tcl_Init(interp);
Tcl_CreateCommand(interp,"add",AddCmd,NULL,NULL);
return TCL_OK;
}
程序第二行注册了add命令, 这样每次在解释器interp中激活add, 解释器就会把相应的参数传
http://www.qiusuo365.com
给AddCmd,并执行。如果我们扩展
了很多TCL命令,所有命令的注册工作都可以在Tcl_InitApp中进行。
接着我们给出程序的主体部分,只需几句代码:
#include "tcl.h"
void main(int argc,char* argv[])
{
Tcl_Main(argc,argv,Tcl_InitApp);
return;
}
把以上各个部分拷贝到一个C文件中,并把库文件tcl82.lib添加到工程中,编译运行这个程序。
现在你就建立了一个带有自己的扩展命令“add”的TCL,add可以象所有固有
的TCL命令一样执行。在程序的运行界面上你可以敲入:
add 2 3
得到结果:5
敲入:
add 2.1 3
会显示:expect integer but got 2.1
细心的读者会追根究底,如上面提到的clientdata到底是什么,还有Tcl_CreateCommand的各个
参数都是什么含义。这可以在文档
《TCL库函数介绍(一)、(二)》中找到,以后还会有专门的主题来解说。
欲知后事如何,且听下回分解。
TCL系列之三:建立自己的TCL扩展命令 (续)
每一个使用C/C++语言编写的TCL命令过程必须有相同的形式,在tcl.h文件中对TCL命令过程
Tcl_CmdProc的定义是这样的:
typedef int Tcl_CmdProc(ClientData clientdata,Tcl_Interp* interp,int argc,char*
argv[]);
上一篇文章中的AddCmd过程就符合上面的定义:第一个参数为ClientData 类型,第二个参数指向
一个解释器,最后两个参数为
参数的个数及其字符形式.
库函数Tcl_CreateCommand负责把每一个使用C/C++语言编写的上述形式的TCL命令过程向TCL解
释器注册,它的原型为:
Tcl_CreateCommand(Tcl_Interp* interp,char* cmdName,Tcl_CmdProc*
cmdProc,ClientData clientdata,
Tcl_CmdDeleteProc* deleteProc)
第一个参数是一个解释器指针,
http://www.qiusuo365.com
第二个参数是你想给自己的TCL命令取的名字,
第三个参数是一个函数指针,指向我们自己用C/C++语言编写的TCL命令过程,
一旦注册成功,TCL解释器将把命令过程cmdProc和命令名cmdName严格对应起来,一旦你在TCL中
激活了命令名cmdName,TCL解释器就会自动调用命令过程cmdProc.例如:TCL解释器遇到命令add,
就会调用AddCmd命令过程.
第四个参数是用来传递用户自己定义的数据对象的,TCL记录这个参数指向的对象,并把它传递给
命令过程cmdProc的第一个参数.利用这个参数我们可以方便的实现在多个命令过程中对同一个
数据对象进行操作.而且,使用这个参数可以避免每次调用一次命令就分配一次内存,调用结束时
又释放,从而提高系统效率.
ClientData类型在TCL的定义为 typedef void *ClientData; 它可以指向任何类型.
最后一个参数是一个函数指针,它指向一个回调函数,当我们从解释器interp中删除命令cmdName
时,函数deleteProc就会被激活,deleteProc必须有同一的形式,在TCL中Tcl_CmdDeleteProc的定
义为:
typedef void (Tcl_CmdDeleteProc) (ClientData clientData);
我们可以利用这个回调函数中释放clientData占用的内存.
在较为复杂的TCL扩展中,我们将不可避免的要用到Tcl_CreateCommand的最后两个参数.
下一篇文章我们将利用clientdata和deleteProc这两个参数实现需要利用自己定义的类的命令,
从中我们将得到很多启发.
TCL系列之四:在扩展TCL命令时,利用自己定义的复杂数据结构
下面的例子展示了一个利用自己定义的一个类来扩展TCL命令的例子,这个例子虽然简单,但具
有普遍意义。在这个例子中利用了函数Tcl_CreateCommand的参数ClientData来传递数据对象,
利用回调函数参数deleteProc来释放数据对象。
#include "Tcl.h"
class Sample
{
private:
int x;
public:
Sample(){ x=0;}
Sample(Sample& sa){ x=sa.x;}
void Set(int _x);
int Get();
};
http://www.qiusuo365.com
int Sample::Get()
{
return x;
}
void Sample::Set(int _x)
{
x=_x;
}
int Tcl_InitApp(Tcl_Interp* interp);
int MyGetCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char * argv[]);
int MySetCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char * argv[]);
void DeleteProc(ClientData clientdata);
void main(int argc,char* argv[])
{
Tcl_Main(argc,argv,Tcl_InitApp);
return;
}
int Tcl_InitApp(Tcl_Interp* interp)
{
Tcl_Init(interp);
Sample* pSam=new Sample;
Tcl_CreateCommand(interp,"myget",MyGetCmd,(ClientData)pSam,(Tcl_CmdDeletePr
oc* )DeleteProc);
Tcl_CreateCommand(interp,"myset",MySetCmd,(ClientData)pSam,(Tcl_CmdDeletePr
oc* )DeleteProc);
return TCL_OK;
}
int MyGetCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char * argv[])
{
if(argc!=1)
{
interp->result="Usage error!";
return TCL_ERROR;
}
Sample* pSam=(Sample* )clientdata;
if(!pSam)
{
http://www.qiusuo365.com
return TCL_ERROR;
}
sprintf(interp->result,"%d",pSam->Get());
return TCL_OK;
}
int MySetCmd(ClientData clientdata,Tcl_Interp* interp,int argc,char * argv[])
{
if(argc!=2)
{
interp->result="Usage error!";
return TCL_ERROR;
}
Sample* pSam=(Sample* )clientdata;
if(!pSam)
{
return TCL_ERROR;
}
int i;
if(TCL_OK!=Tcl_GetInt(interp,argv[1],&i))
{
sprintf(interp->result,"Expect integer but got %s",argv[1]);
return TCL_ERROR;
}
pSam->Set(i);
sprintf(interp->result,"%d",i);
return TCL_OK;
}
void DeleteProc(ClientData clientdata)
{
int static i=0;
if(i!=0)
return;
delete (char*)clientdata;
i=1;
}
编译运行这个例子,交替敲入命令myget和myset可以反映出对数据对象pSam的操作导致的变化。
对这个例子的解释和由此给我们的有益的启示将在明天给出。
http://www.qiusuo365.com
TCL系列之五:在扩展TCL命令时,利用自己定义的复杂数据结构(续)
在昨天的例子中,我们在Tcl_InitApp中生成了一个对象*pSam,然后在调用
Tcl_CreateCommand函数注册命令时,把这个对象和两个扩展命令myget和myset
关联起来,TCL解释器将保存这个对象,并在激活这两个命令时把它作为第一个参
数传递给实现这两个命令的C过程.在命令过程中我们只需用强制转换把这个参
数转换成原来的类型.
函数DeleteProc当我们删除myget或myset命令时会被激活,这时与这两个命
令相关的对象*pSam的空间被释放.调试运行这个程序,在控制台中敲入
rename myget {}
这个命令从解释器中删除myget命令,我们会发现DeleteProc函数会被自动调用,
敲入 rename myset {}
DeleteProc函数也会被自动调用.
这个例子向我们展示了利用我们自己定义的类来扩展TCL命令的方法.这一
方法具有普遍的意义.
我们在用C++的面向对象的方法开发一个针对某个领域的程序时,往往是把
把这个领域中的对象抽象、 封装成一个或多个类(或结构).然后通过对这些类(或
结构)的对象实例的操作而形成一个应用程序.在作TCL扩展命令时我们可以沿用
这一方法.事实上当我们的一组TCL命令需要对同一个对象进行操作时,为了让程
序结构良好,利用上述例子所展示的方法是很好的一种选择,而多个命令需要对
同一个对象进行操作的情况在稍微复杂一点的应用中都是不可避免的.
另外,利用这种方法我们可以做到最大限度的软件重用.我们在扩展TCL命令
中,可以充分利用我们以前作过的工作.我们可以把以前用C++开发的类搬过来,
然后按照TCL的要求把类的一些成员函数改写成对应的TCL命令.这个工作并不怎
么复杂,但是我们缺可以由此得到一种功能完备的针对某个领域应用的脚本语言.
事实上,按照这种方法,我已经利用针对代理测试编写的的一个C++类,实现了用
于代理测试的脚本语言.
http://www.qiusuo365.com
TCL系列之六:在C/C++应用程序中嵌入TCL
TCL被广泛应用的一个原因是它具有良好的可嵌入性,我们可以轻易的把TCL嵌入
到我们的一个C/C++应用程序中.TCL的可嵌入性使得我们可以为每一个应用程序
提供一个功能完备的TCL扩展语言.
下面介绍在一个C/C++程序中嵌入TCL的方法.
假设我们已经用VC6生成了一个应用程序,并且加入一个菜单项TCL,OnTcl是菜单
项TCL的消息响应函数:
void CMyExView::OnTcl()
{
Tcl_Interp* interp=Tcl_CreateInterp();
if(Tcl_InitApp(interp))
{
AfxMessageBox("TCL initialize Error!");
return;
}
CFileDialog
dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY|OFN_FILEMUSTEXIST,
"Tcl Files(*.tcl)|*.tcl|All Files(*.*)|*.*||");
if(IDOK!=dlg.DoModal())
return;
}
CString strFilePath=dlg.GetPathName();
//evaluate the TCL description file
if(TCL_OK!=Tcl_EvalFile(interp,(char*)(const
char*)strFilePath))
{
AfxMessageBox("There are errors in your Tcl File");
Tcl_DeleteInterp(interp);
return;
}
Tcl_DeleteInterp(interp);
}
我们可以把系列之三的文章中的扩展命令add的实现部分集成到这个程序中,那
么我们可以编译一个这样的脚本:
http://www.qiusuo365.com
set a 2
set b 3
add ¥a ¥b
如果我们把系列之四中的myget和myset命令的实现集成进来,就可以编写与这两
个命令有关的脚本来执行.
点击菜单项TCL,就可以打开一个编辑好的TCL文件并执行,遗憾的是我们不能看
到执行结果.不过我们可以通过单步调试来跟踪命令的执行过程.在实际应用中,
如果想看到命令执行结果,可以在我们的扩展命令过程中加入对命令执行结果重
定向的语句.
TCL系列之八: 最常用的TCL命令
我个人认为,学习一种计算机语言最直接的方法是先用起来,实际工作中遇到问
题就去查阅资料.一开始就接触一大堆烦琐的语言的语法定义和设计思想只会让
人茫然不知所措.当然,在我们使用一种语言到达一定程度时,我们绝对有必要了
解包括语言的设计思想在内的语言的各个部分,这样可以深入了解一种语言,使
用起来也会得心应手.
这篇文章就介绍一些常用的命令,让我们先把TCL用起来.同时也为下一篇文
章打下基础.因为后面的文章要用到这些内容.
1.set 形式为 : set varname ?value?
这个命令是一个最基本的命令, 两
个问号之间的参数表示这个可省.set命令可以声明一个新变量或对一个已存在
的变量赋值,也可以查询一个变量的值.如:
% set a 2 //如果变量a不存在,声明a,并赋值为2.如果变量a已存在,把a的
赋为2
2
% set a //查询变量a的值
2
% set a 10
10
% set a //查询变量a的值
10
% set b a
a
% set b
http://www.qiusuo365.com
a //b的值为a
% set b ¥a
10
% set b
10 //b的值为10
2.puts 完整的形式为:puts ?-nonewline? ?channelId? string 目前我们只要
知道最简单的形式
puts string 这个命令往标准输出设备上输出string 的内容.例如:
% puts adgaldg
adgaldg
% puts "lkadg;ladg"
lkadgladg
4.expr 形式为 :expr arg ?arg arg...? 这个命令把右边的参数当作数学表达
式求值.如:
% expr 3
3
% expr 3+4
7
% expr 5 + 6
11
% expr 2+abs(-4)
6
4.source 这里只介绍形式: source filename 这个命令把文件filename作为一
个脚本文件执行.
例如,如果我们在c:/tcl下有一个文件example.tcl.内容为
set a 2
set b a
puts ¥b
set b ¥a
puts ¥b
set b [expr ¥a+4]
puts ¥b
我们可以这样执行这个文件
% source c:/tcl/example.tcl ///注意这里不是用'/',而是'/'
a
2
6
http://www.qiusuo365.com
TCL系列之九: 置换(SUBTITUTION)
1.变量置换 (variable substitution)
先看一个例子:
% set a 2
2
% set b a
a
% set b
a
在这个例子中,我们期望把变量a的值付给变量b,即期望b的值也是2.但是,事与
愿违.b的值实际上是a. 造成这种结果的原因是:TCL解释器在分析每一个TCL语
句时,把第一个单词当成命令名,而把后面的单词当成命令的参数并予以分析,把
分析结果以字符形式把参数值传递给命令过程.上面的例子中,TCL解释器对a进
行分析时.并不知道a是一个变量,因而把它当成普通字符串.
如果我们想把a的值付给b,我们就必须提醒TCL解释器,a是一个变量,我们期
望的是a的值,而不是字符串a.这时我们只需在a的前面加一个变量置换符¥,TCL
解释器在分析时就知道¥后面的单词是一个变量,因此就用a的值'2'来置换a,于
是传给命令处理过程的是a的值'2'而不是字符串'a'.所以这时b的值就变成了2.
例:
% set b ¥a
2
%set b
2
2.命令置换(command substitution)
有了前面对变量置换的详细介绍,命令置换也就变得很自然.因为TCL解释器只
是把每个语句的第一个单词当成命令名.当在语句中间出现命令名时,TCL把它当
成普通字符串.
% set c expr
expr
% set c
expr
如果我们希望某个命令的结果作为另一个命令的参数,我们可以把这个命令及其
参数用'[]'括起来,TCL解释器遇到'[',就会把随后的单词解释成命令名,并调用
相应的命令过程.例如:
% set c expr 2+3
wrong # args: should be "set varName ?newValue?"
% set c [expr 2+3]
5
% set c
5
http://www.qiusuo365.com
3.反斜杠置换(backslash substitution)
前面两种置换会导致一个问题:如果在TCL的命令参数中出现¥或[,而我们此
时并不希望置换发生怎么办?答案是使用反斜杠置换.另外,反斜杠置换还可以在
参数中插入空格和换行符等.例如:
% set b /¥a
¥a
% set b /[expr/ 2+3]
[expr 2+3]
% set b expr/ 2+3
expr 2+3
% set b yes/nno
yes
no
下面是TCL支持的所有反斜杠置换:
Backslash Sequence Replaced By
/a Audible alert (0x7)
/b Backspace (0x8)
/f Form feed (0xc)
/n Newline (0xa)
/r Carriage return (0xd)
/t Tab (0x9)
/v Vertical tab (0xb)
/ddd Octal value given by ddd
(one, two, or three d's)
/xhh Hex value given by hh
(any number of h's)
/ newline space A single space character.
TCL系列之十: 置换 (续)
下面的两个主题和置换有一定关系
4.双引号(quotes (""))
在TCL中,双引号中的所有字符被当成单个字符串,即使中间有空格也是这样,
例如:
% puts "hello world!"
hello world!
但是,如果没有引号的话,被空格隔开的单词就被当成多个字符串,例:
% puts hello world!
can not find channel named "hello"
http://www.qiusuo365.com
在双引号中,命令置换、 变量置换和反斜杠置换照样起作用,这可以给我们作一些
字符串
的输出时提供方便,例如:
% set a 4
4
% puts "the value of a is :¥a"
the value of a is :4
% puts "the value of expression 3+4*5 is [expr 3+4*5]"
the value of expression 3+4*5 is 23
% puts "Hello!/nWorld!"
Hello!
World!
5.花括号(braces({}))
花括号可以起与双引号相同的作用,同时可以屏蔽掉所有的置换.
% puts {the value of a is :¥a}
the value of a is :¥a
% puts {the value of expression 3+4*5 is [expr 3+4*5]}
the value of expression 3+4*5 is [expr 3+4*5]
% puts {Hello!/nWorld!}
Hello!/nWorld!
TCL系列之十一:TCL语法(一)------变量
TCL支持两种类型的变量:简单变量和数组。
1.简单变量
一个TCL的简单变量包含两个部分:名字和值。名字和值都可以是任意
字符串。例如一个名为 “1323 7&*: hdgg"的变量在TCL中都是合法的。不过为
了更好的使用变量置换(substitution),变量名最好按C/C++语言中标识符的命
名规则命名。因为TCL解释器在分析一个变量置换时,只把位于$符号后和其后
第一个不是字母、 数字或下划线的字符之间的单词符号作为要被置换的变量的名
字。例如:
% set a 2
2
set a.1 4
4
% set b ¥a.1
2.1
http://www.qiusuo365.com
在最后一个命令行,我们希望把变量a.1的值付给b, 但是TCL解释器在分析
时只把¥符号之后直到第一个
不是字母、 数字或下划线的字符(这里是'.')之间的单词符号(这里是'a')当作要
被置换的变量的名字,所以
TCL解释器把a置换成2,然后把字符串“2.1”付给变量b。
当然,如果变量名中有不是字母、数字或下划线的字符,又要用置换,可以
用花括号把变量名括起来。例如:
%set b ¥{a.1}
4
TCL中的set命令能生成一个变量、也能读取或改变一个变量的值。例如:
%set a {kdfj kjdf}
如果变量a还没有定义,这个命令将生成 变量a,并将其值置为kdfj kjdf,
若a已定义,就简单的把a的值置为kdfj kjdf。
%set a
这个只有一个参数的set命令能获取a的当前值“kdfj kjdf”
2.数组
数组是一些元素的集合。 TCL的数组和普通计算机语言中的数组有很大的
区别。在TCL中,不能单独声明一个数组,数组只能和数组元素一起声明。数组
中,数组元素的名字包含两部分:数组名和数组中元素的名字,TCL中数组元素
的名字(下标〕可以为任何字符串。例如:
%set day(monday) 1
1
%set day(tuesday) 2
2
第一个命令生成一个名为day的数组, 同时在数组中生成一个名为monday
的元素,并把值置为1,第二个命令生成一个名为tuesday的元素,并把值置为2。
简单变量的置换已经在前面讨论过,这里讲一下数组元素的置换。除了
要加括号之外,数组元素的置换和简单变量类似。例:
%set a monday
monday
%set day(monday) 1
1
%set day(tuesday) 2
2
%set b ¥day(monday) //b的值为1,即day(monday)的值。
1
%set c ¥day(¥a) //c的值为1,即day(monday)的值。
1
3.相关的命令
3.1 set
http://www.qiusuo365.com
这个命令已有详细介绍。
3.2 unset
这个命令从解释器中删除变量,它后面可以有任意多个参数,每个参数是
一个变量名,可以是简单变量,也可以是数组或数组元素。例如:
% unset a b day(monday)
上面的语句中删除了变量a、 b和数组元素day(monday), 但是数组day并没有删除,
其他元素还存在,要删除整个数组,只需给出数组的名字。
%puts ¥day(monday)
can't read "day(monday)": no such element in array
% puts ¥day(tuesday)
2
%unset day
% puts ¥day(tuesday)
can't read "day(tuesday)": no such variable
%
3.3 append和incr
这两个命令提供了改变变量的值的简单手段。
append命令把文本加到一个变量的后面,例如:
% set txt hello
hello
% append txt "! How are you"
hello! How are you
incr命令把一个变量值加上一个整数。 incr要求变量原来的值和新加的值都必须
是整数。
%set b a
a
% incr b
expected integer but got "a"
%set b 2
2
%incr b 3
5
TCL系列之十二:TCL语法(二)------表达式
TCL中的表达式类似于ANSI的表达式.表达式由操作数和操作符构成,下
面分别介绍:
1.数
TCL表达式的操作数通常是整数或实数。整数一般是十进制的,但如
http://www.qiusuo365.com
果整数的第一个字符是0(zero),那么TCL将把这个整数看作八进制的,如
果前两个字符是0x则这个整数被看作是十六进制的。TCL的实数的写法与
ANSI C中完全一样。如:
2.1
7.9e+12
6e4
3.
2.运算符和优先级
下面的表格中列出了TCL中用到的运算符,它们的语法形式和用法跟
ANSI C中很相似。这里就不一一介绍。上表中的运算符是按优先级从高到
低往下排列的。同一格中的运算符优先级相同。
语法形式 结果 操作数类型
-a 负a int,float
!a 非a int,float
̄a int
a*b 乘 int,float
a/b 除 int,float
a%b 取模 int
a+b 加 int,float
a-b 减 int,float
a< a>>b 右移位 int
a a>b 大于 int,float,string
a<=b 小于等于 int,float,string
a>=b 大于等于 int,float,string
a= =b 等于 int,float,string
a!=b 不等于 int,float,string
a&b 位操作与 int
a^b 位操作异或 int
a|b 位操作或 int
a&&b 逻辑与 int,float
a||b 逻辑或 int,float
a?b:c 选择运算 a:int,float
3.数学函数
http://www.qiusuo365.com
TCL支持常用的数学函数,表达式中数学函数的写法类似于C/C++语
言的写法,数学函数的参数可以是任意表达式,多个参数之间用逗号隔开。
例如:
% expr 2* sin(2<3)
1.68294196962
需要注意的是,这些数学函数并不是命令,只能在表达式中出现才有意
义。
TCL中支持的数学函数如下
abs( x) Absolute value of x.
acos( x) Arc cosine of x, in the range 0 to π.
asin( x) Arc sine of x, in the range -π/2 to π/2.
atan( x) Arc tange nt of x, in the range -π/2 to π/2.
atan2( x, y) Arc tangent of x/ y, in the range -π/2 to π/2.
ceil( x) Smallest integer not less than x.
cos( x) Cosine of x ( x in radians).
cosh( x) Hyperbolic cosine of x.
double( i) Real value equal to integer i.
exp( x) e raised to the power x.
floor( x) Largest integer not greater than x.
fmod( x, y) Floating-point remainder of x divided by y.
hypot( x, y) Square root of ( x 2 + y 2 ).
int( x) Integer value produced by truncating x.
log( x) Natural logarithm of x.
log10( x) Base 10 logarithm of x.
pow( x, y) x raised to the power y.
round( x) Integer value produced by rounding x.
sin( x) Sine of x ( x in radians).
sinh( x) Hyperbolic sine of x.
sqrt( x) Square root of x.
tan( x) Tangent of x ( x in radians).
tanh( x) Hyperbolic tangent of x.
TCL中有很多命令都以表达式作为参数,最典型的是expr命令,还有
if,while,for等循环控制命令,这将在后面的章节中讲到.
chenxusheng 18960 作于 03-09 16:15
分类: TCL
http://www.qiusuo365.com
TCL中的控制流和C语言类似,包括
if,while,for,foreach,switch,break,continue。
1.if命令
语法: if test1?then?body1?elseif test2?then? body2
elseif....?else?bodyn?
TCL先把test1当作一个表达式求值,如果值非0,则把body1当作一个脚
本执行并返回所得值, 否则把test2当作一个表达式求值, 如果值非0, 则把body2
当作一个脚本执行并返回所得值...。例如:
if { ¥x>0 }{
.....
}elseif{ ¥x==1 } {
.....
}elseif { ¥x==2 }{
....
}else{
.....
}
注意,上例中'{'一定要写在上一行,因为如果不这样,TCL 解释器会认
为if命令在换行符处已结束,下一行会被当成新的命令,从而导致错误的结果。
在下面的循环命令的书写中也要注意这个问题。 书写中还要注意的一个问题是if
和{之间应该有一个空格,否则TCL解释器会把'if{'作为一个整体当作一个命令
名,从而导致错误。
2.循环命令:while 、for、 foreach
while命令的语法为: while test body
参数test是一个表达式,body是一个脚本,如果表达式的值非0,就运行脚
本,直到表达式为0才停止循环,此时while命令中断并返回一个空字符串。
例如:
假设变量 a 是一个链表,下面的脚本把a 的值复制到b:
set b " "
set i [expr [llength ¥a] -1]
while {¥i>=0}{
lappend b [lindex Sa Si]
incr i -1
http://www.qiusuo365.com
}
for命令的语法为: for init test reinit body
参数init是一个初始化脚本,第二个参数是test一个表达式,用来决定循环什么
时候中断,第三个参数reinit是一个重新初始化的脚本,第四个参数body也是脚
本,代表循环体。下例与上例作用相同:
set b " "
for {set i [expr [llength ¥a] -1]} {¥i>=0} {incr i -1} {
lappend b [lindex Sa Si] }
foreach命令的语法为: foreach varName list body
第一个参数varName是一个变量,第二个参数list 是一个链表,第三个参数body
是循体。每次取得链表的一个元素,都会执行循环体一次。 下例与上例作用相
同:
set b " "
forreach i ¥a{
set b [linsert ¥b 0 ¥i]
}
在循环体中,可以用break和continue命令中断循环。其中break命令结
束整个循环过程,并从循环中跳出,continue只是结束本次循环。
3.switch 命令
和C语言中switch语句一样,TCL中的switch语句也可以由if语句实现。
只是书写起来较为烦琐。 switch命令的语法为: switch ? options? string
{ pattern body ? pattern body ...?}
第一个是可选参数options,表示进行匹配的方式(TCL支持三种匹配方式缺省情
况表示glob方式,这留到后面再讲),第二个参数string 是要被用来作测试的值,
第三个参数是括起来的一个或多个元素对,例:
switch ¥x {
a -
b {incr t1}
c {incr t2}
default {incr t3}
}
其中a的后面跟一个'-'表示使用和下一个模式相同的脚本。default表示
http://www.qiusuo365.com
匹配任意值。一旦switch命令 找到一个模式匹配,就执行相应的脚本,并返回
脚本的值,作为switch命令的返回值。
因为TCL中把所有的值当作字符串,所以在模式匹配的时是使用的字符串匹
配,TCL的字符串匹配功能很强,可以使用通配符'*'、'?'. 在后面将会详细讲解
TCL中的字符串匹配问题.
TCL系列之十四:过程
TCL支持过程的定义和调用, 在TCL中,过程可以看作是用TCL脚本实现的命令,
效果与TCL的固有命令相似。 我们可以在任何时候使用proc命令定义自己的过程,
TCL中的过程类似于C中的函数。
1.过程定义
TCL中过程是由proc命令产生的:
例如:
% proc add {x y } {expr ¥x+¥y}
proc命令的第一个参数是你要定义的过程的名字, 第二个参数是过程的参
数列表,参数之间用空格隔开,第三个参数是一个TCL脚本,代表过程体。 proc
生成一个新的命令,可以象固有命令一样调用:
% add 1 2
3
在定义过程时,你可以利用return命令来在你愿意的地方返回你想要的值.
return命令迅速中断过程,并把它的参数作为过程的结果。例如:
% proc abs {x} {
if {¥x >= 0} { return ¥x }
return [expr -¥x]
}
2.局部变量和全局变量
对于在过程中定义的变量,因为它们只能在过程中被访问,并且当过程退
出时会被自动删除,所以称为局部变量;在所有过程之外定义的变量我们称之为
http://www.qiusuo365.com
全局变量。TCL中,局部变量和全局变量可以同名,两者的作用域的交集为空:局
部变量的作用域是它所在的过程内部;全局变量的作用域不包括过程内部。这一
点和C语言有很大的不同.
如果我们想在过程内部引用一个全局变量的值,可以使用global命令。例
如:
% set a 4
4
% proc sample { x } {
global a
incr a
return [expr ¥a+¥x]
}
% sample 3
8
%set a
5
全局变量a在过程中被访问。在过程中对a的改变会直接反映到全局上。如果
去掉语句global a,
TCL会出错,因为它不认识a.
TCL系列之十五:过程(续)
3.缺省参数和可变个数参数
TCL还提供三种特殊的参数形式:
首先,你可以定义一个没有参数的过程,例如:
proc add {} { expr 2+3}
其次,可以定义具有缺省参数值的过程,我们可以为过程的部分或全部
参数提供缺省值,如果调用过程时未提供那些参数的值,那么过程会自动使用缺
省值赋给相应的参数。和C/C++中具有缺省参数值的函数一样,有缺省值的参数
http://www.qiusuo365.com
只能位于参数列表的后部,即在第一个具有缺省值的参数后面的所有参数,都只
能是具有缺省值的参数。
例如:
proc add {val1 {val2 2} {val3 3}}{
expr ¥val1+¥val2+¥val3
}
则:
add 1 //值为6
add 2 20 //值为25
add 4 5 6 //值为15
另外,TCL的过程定义还支持可变个数的参数,如果过程的最后一个参数是
args, 那么就表示这个过程支持可变个数的参数调用。调用时,位于args以前的
参数象普通参数一样处理,但任何附加的参数都需要在过程体中作特殊处理,过
程的局部变量args将会设置为一个列表,其元素就是所有附加的变量。如果没有
附加的变量,args就设置成一个空串,下面是一个例子:
proc add { val1 args } {
set sum ¥val1
foreach i ¥args {
incr sum ¥i
}
return ¥sum
}
则:
add 2 //值为2
add 2 3 4 5 6 //值为20
4.引用:upvar
TCL提供了一种类似于C/C++语言中的指针和引用的方法,来使得用户
可以在过程中对过程外部的变量进行访问。这时就要用到upvar命令。 upvar命
令的第一个参数是我们希望以引用方式访问的参数的名字, 第二个参数是这个过
程中的局部变量的名字,一旦使用了upvar 命令把这个局部变量和参数绑定,那
http://www.qiusuo365.com
么在过程中对这个局部变量的读写就相当于对那个参数所代表的调用者(caller)
的局部变量的读写。下面是一个例子:
% proc temp { arg } {
upvar ¥arg b
set b [expr ¥b+2]
}
% proc myexp { var }
set a 4
temp a
return [expr ¥var+¥a]
}
则:
% myexp 7
13
这个例子中,upvar 把¥arg(实际上是过程myexp中的变量a)和过程变量temp中的
变量b绑定,对b的读写就相当于对a的读写
TCL系列之十六: 用TCL模拟超级终端
前一段时间,在和王辉和刘宇的交流中,得知他们希望能利用TCL的串口功能.
所以崔鹏和我作了一个利用TCL模仿超级终端的实验,并和一台路由器实现了交
互.虽然利用TCL需要敲一些额外的字符,但是因为TCL可以编程的特性,还可以有
任意时间的时延,所以还是很有意义的.下面给出我们的应用结果.
%set com [open com1 w+]
file9a7a80
说明:以读写方式打开COM1,并把TCL分配的channel号(这里是file9a7a80)赋给
变量com,在TCL中,
可以用open命令打开文件,串口,管道,和socket,TCL为每个打开的文件,串口,管
道,和socket分配
一个channel号,并等同看待,可以往这些channel中读写数据.
%fconfigure ¥com -mode 9600,n,8,1
说明:设置串口的参数.
%fconfigure ¥com -blocking 0 -buffering none -buffersize 100000
说明:fconfigureb命令用于设置channel的属性,这里是把¥com设置成非阻塞模
式,而且每往¥com写入内容时,
不作缓存,直接发送出去,这个channel的缓存也设置为100000个字节.关于非阻
塞模式是怎么回事,可以看TCL的帮助或和我联系.
%puts ¥com ?
http://www.qiusuo365.com
?
%read ¥com
...... //这里显示返回的内容,前面会多一个"?"号.
这里,我们会发现使用TCL,不如超级终端方便的地方,因为要作一些额外的字符
输出.用下面的方法可以在
敲入命令后让TCL直接显示结果:
%puts ¥com ?;after 1000;read ¥com
.......//这里显示返回的内容,前面会多一个"?"号.
说明: after 1000, 意思是延迟1000毫秒.
我们可以把我们要作的测试脚本保存在一个文件中,然后用source命令引入TCL
中执行,例如:
set com [open com1 w+];
fconfigure ¥com -mode 9600,n,8,1;
fconfigure ¥com -blocking 0 -buffering none -buffersize 100000;
puts ¥com ?;
puts ¥com help;
after 1000;
read ¥com;
如果这个文件的路径是c:/example.txt.我们可以在TCL中敲入:
%source c:/example.txt
.....//这里显示返回的内容
_
nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn