Tcl / Tk 大全
时间:2023-08-01 08:07:00
摘要: Tcl/Tk是一种简洁、高效、可移植的编程语言。广泛应用于信息产业。本文描述了TCL/TK阐述了成长历史、特点、优势和应用范围TCL/TK比较了总体结构图TCL/TK今天很流行C ,Java详细阐述了性能比较TCL/TK并介绍了语法TK的工具箱.
一.Tcl / Tk简介
.1背景
Tcl/Tk的发明人John Ousterhout20世纪80年代初,教授是伯克利大学的教授。他在教学过程中发现了集成电路CAD在设计中,很多时间都花在编程和建立测试环境上。而且,一旦环境发生变化,需要修改代码以适应。这种费力低效的方法迫使Ousterhout教授试图找到一种新的编程语言,即具有良好的代码可重用性,易于学习,从而促进Tcl (Tool Command Language)语言的产生。
Tcl最初的想法是根据组件的方法来编程(component approach),也就是说,与其为一个应用程序编写数百行程序代码,不如找到一种方法将程序分成具有一定完整功能的小组件。这些小组件很小,基本上可以满足一些独立应用程序的需求,其他部件可以在这些小组件功能的基础上生成。不同的组件有不同的功能,用于不同的目的。可用于其他应用程序。当然,这种语言应该具有良好的可扩展性,以便用户为其添加新的功能模块。最后,这些组件需要用一种强大而灵活的胶水粘在一起,这样每个组件才能相互通信,共同工作。程序设计就像拼图游戏,这种设计理念与后来相同Java不谋而合。终于在1988年春天,这种强大而灵活的胶水- Tcl语言发明了。
1.2定义
按照Ousterhout教授的定义,Tcl可嵌入的命令脚本化语言(Command Script Language)。可嵌入是指将许多应用程序有效、无缝地集成在一起。命令是指每一个Tcl句子可以理解为命令参数的形式:
命令[参数1] [参数2] [参数3] [参数4] ... [参数N]
脚本化是指Tcl为特殊任务设计。但从现在的角度来看,可以说Tcl是集C语言灵活强大的功能BASIC一般程序设计语言,语言易学,风格高效。
按照Ousterhout教授的定义,Tcl可嵌入的命令脚本化语言(Command Script Language)。可嵌入是指将许多应用程序有效、无缝地集成在一起。命令是指每一个Tcl句子可以理解为命令参数的形式:
命令[参数1] [参数2] [参数3] [参数4] ... [参数N]
脚本化是指Tcl为特殊任务设计。但从现在的角度来看,可以说Tcl是一种集C语言灵活强大的功能与BASIC一般程序设计语言,语言易学,风格高效。
t c l(读作“t i c k l e脚本语言和t k工具箱是为X Wi n d o w系统为图形用户界面创建编程环境。t c l和t k学习和使用起来非常容易,用它们构建用户界面的速度比传统的要快X Wi n d o w编程方法要快得多。它最初设计用于为交互式工具提供可重复使用的命令语言,但它的实际发展远非如此,并广泛应用于许多软件产品中。t c l / t k真正的功能是使用t c l脚本语言几乎可以完全编写复杂的图形应用程序,从而避免了使用C语言编写界面时遇到的界面编程的复杂性。
t c l / t k的正式We b站点是h t t p : / / w w w. s c r i p t i c s . c o m /。
在这里t c l平台是S c r i t p i c s公司的产品。S c r i t p i c s公司要把t c l脚本语言向主流群体推广。S c r i t p i c s公司提供技术支持和开发工具t c l和t k公开代码软件包是正确的t c l实施商业支持服务。该网站还有下载和安装t c l / t k连接最新版本。目前,最新可用t c l / t k的版本是8 .4a。
t c l可执行程序t c l,t c l s h,w i s h和t c l h e l p被Red Hat的r p m安装在目录/ u s r / b i n中t c l h e l p调出关于t c l和t k帮助信息。该系统还提供了它t c l s h和w i s h的手册页。
二.Tcl / Tk基础
t c l是类似于UNIX shell一种解释性的语言,也就是说,t c l命令先读取,然后执行。也是用来设置查看的工具箱,可以使用t c l语法创建按钮、滚动杆、对话框、窗口等G U I组件。为了运行t c l。t c l s h和w i s h与s h或c s h这样的标准UNIX shell同样,它们都允许命令从文件中交互执行或读取。在实践中,人们很少互动使用这些s h e l l,因为它们的互动能力有限。t c l和w i s h主要区别是:t c l s h只理解t c l命令,而w i s h理解t c l和t k两种命令。
2.1交互方式
本节将简要介绍tcl shell它的交互使用解释了它的一个问题。开始交互使用t c l,只须在UNIX shell在提示符下输入t c l s h(或w i s h),此时将出现以下提示符:
%
本章以百分号命令交互( % )开始。此提示符后,输入以下命令:
% echo“hello world”
输入此命令后,hello world将显示在新的提示符之后。接下来,执行以下命令:
% puts“hello world”
同样的输出结果会显示出来,但这两个命令之间有很大的区别。显示字符串为了显示字符串hello world,第一个命令运行e c h o第二个命令使用二进制文件t c l命令puts(put string)。e c h o形式的”hello world仅作交互操作t c l s h这是交互使用t c l s h和w i s h问题之一。例如,如果命令是:
echo“hello world”
放入文件h e l l o w o r l d . t c l,然后从t c l s h中执行如下s o u r c e命令:
% source helloworld.tcl
然后将获得以下错误信息:
invalid command name“echo”
此行代码将在一个UNIX shell执行这变化的命令。这只是在tcl shell工作方式不同的例子之一。
2.2非交互方式
通常,t c l s h和w i s h-通常以非交互的方式使用,也就是说,它们在U N I X的提示符( $ )脚本被调用并执行,如:
$ tclsh myprog.tcl
$ wish myprog.tcl
或者从脚本中调用它们,第一行通常与以下内容相似:
#!/usr/bin/tclsh
通常情况下,脚本的每次安装都必须修改第一行,因为w i s h或t c h s h可能位于不同的位置。为避免每次安装时编辑脚本,t c l s h推荐使用以下三行代码作为所有手册页面t c l / t k脚本的前三行:
#!/bin/sh
# the next line restarts using tclsh \
exec wish“$0”“$@”
这意味着用户只需要有自己的路径w i s h你可以用脚本。使用这种方法产生的结果可能是基于系统s h不同的版本。
t c l非交互使用的实际优点是与UNIX shell非交互使用的优点是相同的。非交互使用允许将多个命令组合在一起,只要输入脚本的名称,就可以执行脚本中的所有命令。同时,型程序的开发和调试。
四.与其它语言的比较
下面就Tcl经典的程序设计语言C ,现在时尚Java做一些比较:
C ,Tcl/Tk和Java性能比较表
操作速度
快
与C 可比
慢
调试难度
复杂
每次修改代码都需要重新编译
简单
修改后的代码可以直接运行
比较简单
修改完代码需重新编译成ByteCode,而且编译速度很慢
以上程序代码的复杂性grep为例
复杂
50行程序代码
简明
10行程序代码
比较简单
40行程序代码
占用系统资源
200MB HD
32MB Memory
3MB HD
4MB Memory
20MB HD
4MB Memory
代码可维护性
可移植性
好
一般
较好
本节将介绍t c l语法及其在脚本中的使用方法。以下部分的代码可以从脚本交互或运行。在交互模式下,输出内容之间的间距将略有不同。
TCL命令的基本结构是:
commandname arguments
这里的c o m m a n d n a m e是t c l要执行的命令,a rg u m e n t s可选变元提供给这个命令,整个行(c o m m a n d n a m e和a rg u m e n t s)叫命令。在命令之间换行( \ n )或者由分号( ; )来分隔。若一行只有一个命令,则可省略分号。以下两个命令作为说明:
set foo0
set bar 1
这两个命令可以写成两行,每行写一个;另外,它们也可以写在同一行上,例如:
set foo 0; set bar 1;
有时需要在表达式中使用另一个表达式的值。使用方括号来实现:
puts [expr 1000/4];
输出将是2 5 0。在需要时方括号也可以嵌套。
除命令外,t c l脚本中另一种类型的代码行是注释。如同在UNIX shell和P e r l中那样,注释行是以#号开头的行,例如:
#this is a comment
但是与s h e l l中不同的是,下面一行内容并不是注释
set foo 0 # initialize foo
而且它将会产生一个错误,这是因为t c l解析器总是认为一条命令应该以换行或分号结束,因此如果想要在命令所在的同一行上包括注释内容,这个命令必须以分号结束,就像下面这样:
set foo 0;#initialize foo
因此,用分号结束所有的命令通常是一种很好的做法,尽管有时分号并不是必需的。
5.3数据类型
t c l不支持诸如i n t、f l o a t、d o u b l e或c h a r之类的变量类型。这意味着,在同一程序中,一个变量可以在不同的时刻分别被设置为数值、字符或字符串。
但是在内部,t c l把所有的变量都当作字符串来看待。当需要操作变量时,t c l允许以A N S IC所能识别的任何一种方式来提供数字(实数或整数),下面列出的是可以提供给变量的有效数字值的例子:
7 4整数
0 11 2八进制,以0开头
O x 4 a十六进制,以O x开头
7 4 .实数
7 4 . 0实数
7 . 4 e 1实数
7 . 4 e+1实数
除此之外的其他值都被当作是字符串,如果把它们应用于数学表达式,则会产生错误。
t c l可以定义两种类型的变量:标量和数组。要创建标量变量并对它赋值,可利用s e t命令,例如:
set banana 1;
这一命令将创建变量b a n a n a并把它的值赋为1。要想把变量b a n a n a设置为另一个值,只须再次利用s e t,例如:
set banana "Fresh from Brazil";
此时变量b a n a n a具有的值为“Fresh from Brazil”,命令中的双引号用来通知t c l变量的值由引号中包括空格在内的所有字符组成(在本章后续章节中将讨论引用和置换)。
要输出b a n a n a的值,可利用p u t s命令:
puts $banana;
这一命令将把变量b a n a n a的值显示到标准输出(也称为S T D O U T)。在变量名前放置$用来通知t c l访问的是这一变量的值,这个规定称为变量置换,它与UNIX shell中所使用的规定类似。
注意要使用变量内容时应使用$符,而在设置或改变变量时不必使用$.
要想创建一个一维数组,可以像下面这样进行:
set fruit(O)banana;
set fruit(1)orange;
这两个命令将创建一个数组变量f r u i t,并把它的两个元素。0和1分别赋值为b a n a n a和o r a n g e。
注意计算机记数是从0,而不是从1开始。
对数组的赋值不必按照索引的顺序,例如,下面命令在数组f r u i t中创建了三个元素。
set fruit{100}peach;
set fruit{2}kiwi;
set fruit{87}pear;
t c l中的数组非常类似于把“关键字”与值联系在一起的关联数组。t c l中的数到把某一给定的字符串与另一个字符串联系起来,这使得这种数组可能具有非数字的数组下标,例如:
fruit to 100:
set fruit{banana}100
这一命令把数组f r u i t中元素b a n a n a的值设置为1 0 0,被赋的值也可以不是数字,例如:
set food(koala) eucalyptus;
set food(chipmunk) acorn;
如果想访问存放在一维数组中的值,可利用$,例如:
puts $food(koala);
这一命令将显示出存放在数组f o o d中下标为k o a l a的值,数组的下标也可以是一个变量,例如:
set animal chipmunk;
puts $food($animal);
如果在前面进行了赋值,这两个命令将输出a c o r n。
多维数组是一维数组的简单扩充,它们的设置方式如下:set myarray(1,1) 0;
这一命令把数组m y a r r a y中位于1,1处的元素的值设置为0。通过利用逗号分隔下标,你可以设置三维、四维或更多维的数组,例如:
set array(1,1,1,1,1,1)“foo”
除了设置数组的值外,t c l还提供用来获取有关数组信息的命令a r r a y,以及用来输出有关数组信息的命令p a r r a y。首先,我们来考察p a r r a y命令。如果已经提供了如下命令:
set food(koala) eucalyptus;
set food(chipmunk) acorn;
set food(panda) bamboo;
那么命令
parray food
将产生如下的输出结果:
food(chipmunk)=acorn
food(koala)=eucalyptus
food(panda)=bamboo
接下来我们来考察a r r a y命令及其变元,这个命令用来获取有关数组及其元素的信息。
a r r a y命令的基本语法如下:
array option arrayname
在本节后面将讨论它所支持的选项。
有关数组最常用的信息之一是数组的大小。如果已经提供了如下说明:
set fruit(0) banana;
set fruit(1) peach;
set fruit(2) pear;
set fruit(3) apple;
那么命令
array size fruit;
将返回4,这一数值通常在循环中非常有用。
由于数组可以具有非顺序的或非数字的下标,因此a r r a y命令提供一个用来从数组中获取元素的选项。假设数组f o o d已经像前面介绍的那样进行了定义,那么开始获取元素所须做的第一件事是利用s t a r t s e a r c h遍历数组。这是通过首先获取数组的一个搜索I D来完成的:
set food_sid [array startsearch food]
在方括号中的命令array startsearch food返回一个字符串,这一字符串是搜索标志的名字
(请参看“引用和替换”)。这个名字将在以后的引用中用到,因此需要把它赋给某个变量。在本例中,这一变量为f o o d _ s i d。
要获取f o o d数组的第一个元素(以及其后的每个元素),可以利用如下命令:
array nextelement food $food_sid;
当完成对数组的搜索时,可利用如下命令终止搜索:
array donesearch food $food_sid;
a r r a y命令的另一个选项是在遍历数组时经常用到的a n y m o r e。当在搜索中还有元素时,它将返回t r u e(也就是1),这一命令当与前面所说明的数组f o o d一起使用时,在前两次将返回1。
array anymore food $food_sid;
要清除变量(标量或数组),可以利用u n s e t命令,例如:
unset banana;
这个命令将取消b a n a n a变量。如果你利用unset $banana(假如b a n a n a已被设置为前面所显示
的值)来取代刚才的b a n a n a,那么你会得到如下一条错误信息:
can‘t unset"0": no such variable
发生错误的原因是:当把$放在变量名的前面时,在执行命令之前变量的值将被替换进去。
5.5字符串的操作
对字符串进行操作的最简单的形式是利用a p p e n d命令把多个字符和变量连接在一起。作为说明,考察如下几条命令
这些命令的输出结果为:
Begin a String with some text and add even more text to it.
你也可以利用如下命令来达到相同的结果:
但是这种做法与利用a p p e n d相比,执行起来要慢一些,这是因为a p p e n d不像s e t那样要执行字符拷贝。
为了能够执行更多高级的字符操作,t c l提供了s t r i n g命令,这一命令能够识别许多选项。
s t i n g命令的基本语法是:
string option string1 string2
这里的s t r i n g 1和s t r i n g 2可以是文字串(例如,“this is a string”),也可以是变量,o p t i o n是下列选项之一:
c o m p a r e按照词典的排序方式进行比较,根据s t r i n g 1小于、等于或大于s t r i n g 2,分别返回-1、0或
1 (类似于C库函数s t r c m p )f i r s t返回在s t r i n g 2中第一次出现s t r i n g 1的位置如果s t r i n g 1没有出现在s t r i n g 2中,则返回-1
l a s t返回在s t r i n g 2中最后一次出现s t r i n g 1的位置。如果s t r i n g 1没有出现在s t r i n g 2中,则返回-1
s t r i n g命令的如下选项将把s t r i n g 2解释为要从s t r i n g l中删除的一列字符:
t r i m从s t r i n g 1中删除开头和结尾的出现在s t r i n g 2中的字符
t r i m l e f t从s t r i n g 1中删除开头的出现在s t r i n g 2中的字符
t r i m r i g h t从s t r i n g 1中删除结尾的出现在s t r i n g 2中的字符
s t r i n g命令的如下选项只利用s t r i n g 1作为变元:
l e n g t h返回s t r i n g 1包含的字符数
t o l o w e r返回s t r i n g 1中的所有字符被转换为小写字符后的新字符串
t o u p p e r返回s t r i n g 1中的所有字符被转换为大写字符后的新字符串现在让我们来考察几个例子。首先,创建一个字符串并获取其长度:
set str "Here Is A Test String";
string length $str;
这一命令给出的长度为2 3(l e n g t h选项对空格字符也计算在内)。现在,让我们来获取在$s t r字符串中第一次和最后一次出现字符串“s t”的位置:
string first "st" $str;
string last "st" $str
这两个命令给出第一次出现“t”的值为1 3(相应于在Te s t中出现的位置),最后一次出现“s t”的值也为1 3(还是Te s t)。在S t r i n g中出现的“t”为什么不算数呢?这是因为大多数字符串比较函数是区分大小写和空格的,下面的代码将首先把$s t r转换为小写字体,然后再执行查找:
string last "st" [string tolower $str];
此时,这一命令给出的值为1 6,它相应于S t r i n g中的“s t”。最后,让我们来删除$ s t r字符串前后的空格,然后获取这一字符串的长度:
string length [string trim $str " "],
该命令的返回值为2 1,这意味着已经删除了第一个和最后一个空格。
33.2.6数字的操作
t c l提供两个操作数字变量和常数的命令:i n c r和e x p r。
t c l中的i n c r命令等价于C语言中的操作符+ =、- =、+ +和- -,它的基本语法是:
incr variable integer
这里的v a r i a b l e必须是一个整数,i n c r命令将把给定的i n t e g e r添加到v a r i a b l e上,因此减法可通过提供负整数来执行。现在让我们演示一下它的用法。首先,创建一个变量,并对这个变量执行i n c r :
set a 81;
incr a;
puts $a;
$ a此时具有的值为8 2。在缺省情况下,i n c r与+ +相同;当没有提供i n t e g e r变元时,i n c r将把1添加到所指定的变量上。现在,如果要从$ a上减3,可利用如下命令:
incr a -3
puts $a
可以看到,$ a具有的值为7 9。最后一点需要说明的是,这里的i n t e rg e r可以是某个变量的值,例如:
set a 6;
set b 9;
incr a $b;
puts $a;
此时$a具有的值为1 5。对于较复杂的数学操作,t c l提供了e x p r命令,这一命令对所有标准的ANSI C操作符有效,操作符的优先级大部分与ANSI C中的优先级相同。
当需要执行算术操作时,必须把e x p r命令放在算术操作之前,例如:
set a 20;
set b 4;
set c $a/$b;
puts $c;
上述命令产生的输出结果为:
20/4
而不是所想要的结果5。为了获得正确的答案,需要利用e x p r命令,例如:
set c [expr $a/$b];
除了标准操作符+、-、*、/之外,还可以为e x p r提供几个能够使它执行其他数学操作的选项。e x p r命令的基本语法是:
expr function number
e x p r能够识别的一些函数及其返回的值如下:
a b s ( x ) x的绝对值
r o u n d ( x ) x舍入后所得到的整数值
c o s ( x ) x的余弦( x为弧度)
c o s h ( x ) x的双曲余弦
a c o s ( x ) x的反余弦( 0到p)
s i n ( x ) x的正弦( x为弧度)
s i n h ( x ) x的双曲正弦(-p/ 2到p/ 2 )
a s i n ( x ) x的反正弦(-p/ 2到p/ 2 )
t a n ( x ) x的正切( x为弧度)
t a n h ( x ) x的双曲正切
a t a n ( x ) x的反正切(-p/ 2到p/ 2 )
e x p ( x ) e的x次幕
l o g ( x ) x的自然对数
l o g 1 0 ( x ) x的底为1 0的对数
s q r t ( x ) x的平方根
下列数学函数采用两个数字变元:
p o w ( x , y ) x的y次幂
这个函数的用法如下:
set a 2;
set b [expr pow($a,3)];
puts $b;
这些命令产生的输出结果为8 . 0,即2的3次幕的值。
5.6引用和置换
引用和置换被大量应用于与变量有关的操作。在本章的前面,我们曾提到过引用(利用双引号构成字符串)和置换的最基本形式。t c l还支持另一种类型的引用,即花括号引用,以及另一种类型的置换,即命令的置换。回顾过去,我们知道双引号的主要用途是创建具有内嵌空格的字符串,例如:
set kiwi "Fresh from New Zealand";
双引号也可以用来创建多行的字符串,例如:
set kiwi "Fresh from
New Zealand 3 for a dollar";
除创建多行字符串外,在t c l字符串中还可以使用ANSI C语言的标准转义序列,例如:
set kiwi "Fresh from New Zealand\n\t3 for a dollar";
这一命令的输出结果如下:
Fresh from New Zealand
3 for a dollar";
除此以外,在双引号之间的字符串可以应用两种类型的置换。第一种类型的置换,也就是
变量置换,在本章前面“变量”一节中已经做过说明。在利用双引号引起来的字符串中,你可以通过在变量名前添加$来访问该变量的值、因此下面的命令:
set fruit kiwi;
set place "New Zealand";
set how_many 3;
puts "$fruit,fresh from $place,$how_many for a dollar";
输出的结果为:
kiwi, fresh from New Zealand, 3 for a dollar
另一种类型的置换是命令置换。命令置换块以左括号( [ )开始,以右括号( ] )结束,例如:
set len_in 2; puts "$len_in inches is [expr $len_in*2.54] cm";
这一行代码的输出结果为:
2 inches is 5.08 cm
5 . 0 8是命令
expr $len_in*2.54
产生的结果。
由于这一命令位于括号中,因此它的返回值将被替换进去。在本例中,我们使用的是t c l命令e x p r,不过任何t c l命令都可以放在括号之中。命令置换可用于大多数命令中,而不只限于由双引号括起来的命令,例如:
set len_in 2;
set len_cm [expr $len_in*2.54];
puts "$len_in inches is $len_cm cm";
这几个命令产生的输出结果与下面一行代码相同:
set len_in 2; puts "$len_in inches is [expr $len_in*2.54] cm";
可以在t c l中使用的另一种类型的引用是花括号的引用,这种类型的引用类似于UNIX shell
中单引号的使用。花括号的引用将利用给定的字符来创建字符串集,其中不进行置换(命令置换或变量置换),并且也不对C语言的转义序列进行解释,例如:
puts "This\nis a \nmulti-line\nstring"
这一命令产生的输出结果为:
This
is a
multi-line
string
而下面的命令
puts {This\nis a \nmulti-line\nstring}
产生的输出结果为:
This\nis a \nmulti-line\nstring
要想在花括号括起来的字符串中使用制表符、换行符以及其他的特殊字符,必须按原样实
际地输入它们,例如:
puts{This
is a
multi-line
string}
这一命令将产生想要的输出结果。利用花括号括起来的字符串,其真正的用途在于把某些
具有特殊意义的字符作为值提供给变量,例如:
set price 1.00;
puts "Pears,$$price per pound";
这两个命令的输出结果为:
Pears,$1.00 per pound
然而,$ $ p r i c e有可能会引起混乱,因此最好的办法是使变量p r i c e具有的值为$ 1 . 0 0。你可以利用花括号像下面这样来达到目的:
set price {$1.00};
puts "Pears,$price per pound";
花括号引用的另一个用途是推迟求值,并用于控制结构和过程的定义。在这些情况下,当
读取整个代码块后,变量的值将被置换进去。
5.7流的控制IF和SWITCH
t c l提供几个用来控制流的命令,并且支持有关字符串和数字的所有ANSI C的标准比较操作符。本节将从i f / e l s e i f / e l s e命令开始讨论。下面显示的是一个最简单的i f语句:
if {$x<0}{
set x 10;
}
警告T C L对花括号和空格的使用是十分苛刻的。在i f,e l s e i f或e l s e语句中的开始的花括号必须要和i f,e l s e i f或e l s e在同一行中。而括号外必须是有一个空格。且e l s e或e l s e i f语句必须要在前一个if或elseif的后括号的同一行中。
这个例子在i f语句体中只有一行代码,不过你可以添加任何数量的行和子语句块。如果需
要执行其他条件的判断,那么每个判断可以像下面那样在括号中给定:
if {($x=="SJ") ($x=="LA")} {
puts "You Live in California!";
}
判断也可以像下面例子所示的那样进行嵌套:
if {(($arch=="ppc") ($arch=="intel"))&&($os!="Linux")} {
puts "Get Linux!";
}
要把else语句体添加到if语句,可以像下面这样来完成:
if {$x <= 0} {
set x 10;
} else {
set x 0;
}
你还可以根据需要添加任意多的elseif语句:
if {$x == 0} {
set x 10;
} elseif {$x == 10} {
incr x -1;
} elseif {$x == 100} {
set x 50;
} else {
set x 0;
}
在许多情况下,添加过多的elseif语句会使程序变得累赘并难以理解。为了提供表示相同
逻辑关系的一种较紧凑的方法,可以利用t c l的s w i t c h命令。s w i t c h的功能是把某一值(字符串或数字)与相应的块对应起来。当利用s w i t c h语句编写代码时,上述i f语句将变为:
switch $x {
0 {set x 10;}
10 {incr x -1;}
100 {set x 50;}
}
在缺省情况下,只有对应于匹配值的代码被执行,但如果代码块被指定为一个减号( - ),那么s w i t c h语句将进行“下放”,从而执行后面的代码块,例如:
switch $x {
0 -
10 -100
{incr x -1};
}
这一s w i t c h语句等价于下面的i f语句:
if {($x == 0) ($x == 10) ($x=100)} {
incr x -1;
}
33.2.9循环
t c l提供三个循环命令,它们分别是:
• for
• foreach
• while
此外,t c l还提供两个循环控制命令:
• break
• continue
当测试条件为t r u e时,w h i l e循环将执行它的循环体。w h i l e循环的结构是:
while {条件} {程序块}
下面的代码是一个简单的w h i l e循环,它共计要循环1 0次:
set x 0;
while {$x<10} {
incr x;
puts $x;
}
警告在for,foreach或while语句中的开始的花括号必须要和for,foreach或while在同一行中。而括号外必须是有一个空格。
f o r e a c h将在一组变元中进行循环,并且每次都将执行它的循环体。f o r e a c h循环具有如下结构:foreach variable {items} {block}
这里v a r i a b l e是变量的名字,位于i t e m s集合中的每个元素将依次赋给这个变量。下面是f o r e a c h循环的一个例子:
foreach element {o c n p li} {
switch $element {
o
-n {puts gas;}
c-p-li
{puts solid;}
}
}
在这一个例子中,i t e m集合是利用要查看的元素列表来指定的,其实这里也可以利用变量,例如:
set elements "o c n p li";
foreach element $elements {
switch $element {
o
-n {puts gas;}
c-p-li
{puts solid;}
}
}
如果给定的是一个变量而不是元素列表,则不应该使用花括号,因为花括号将被作为用于
引用的花括号来对待。f o r循环是最常用到的循环,它的结构是:
for {initialization} {condition} {increment} {body}
下面是一个计数到1 0的简单f o r循环的例子:
for {set i 0} {$i<=10} {incr i} {puts $i;}
这里所看到的初始化语句是一个十分简单的语句,事实上,f o r循环的初始化部分和增量部分可以根据需要而复杂化。现在,我们来考察循环控制命令b r e a k和c o n t i n u e。b r e a k命令用来中断循环并执行循环代码块之后的下一行代码,而c o n t i n u e命令用来跳到循环的下一次执行。c o n t i n u e命令对于读入允许使用注释行的初始化文件非常有用,如果下面的语句包含在一
个读入文件的循环中,那么所有以#开头的行都将被跳过。
if {[regexp {^#} [string trim $line]]} {continue;}
5.8文件的输入输出和文件的信息
t c l提供一个关于文件输入输出的简单而有效的方法,这一方法类似于C的标准I / O库中的方
法。对文件的输入输出而言,第一步是打开文件并获取文件句柄或文件I D,这一命令将以r方式打开文件/ e t c / p a s s w d并返回一个文件句柄,这一句柄将被赋值给变量f。
set f [open /etc/passwd r];
t c l支持如下几种文件打开方式:
r以只读方式打开文件;文件必须存在。
r +以可读可写的方式打开文件;文件必须存在。
w以可写的方式打开文件;如果文件不存在,它将被创建;如果存在,它将被修改。
w +同w一样;此外,还强调对文件可读。
a以追加文本的方式打开文件;文件不存在时将被创建。
a +同a一样;此外,还强调对文件可读。
O p e n命令也可以被重载,它能够以强于e x e c命令的控制能力来运行子进程。要想利用o p e n打开一个进程而不是一个文件,需要把文件名替换为以一个花括号括起来的字符串,这个字符串将以管道符( )开头并包含所要执行的命令。例如,下面的命令将以只读方式打开一个p s进程:
set f [open { ps } r ];
对于以这种方式打开的进程,可利用t c l的p i d命令来返回与进程相关联的文件句柄的进程I D。对于上面的例子来说,将返回归的进程I D,$ f是被打开的p s命令的文件句柄。
pid $f;
如果文件(或进程)以可读的方式打开,那么就可以利用g e t s命令从这个文件来读取信息。
为了处理文件的所有行,经常需要用到下面的w h l i e命令.
while {[gets $f line] >= 0}
这一命令能够正确运行的原因是:当到达文件结束时,g e t s命令将返回- 1。在这一例子中,g e t s命令将从文件句柄$ f中读入一行,并把这一值赋给变量l i n e。在循环体中,可以对$ l i n e进行访问和操作。
如果以可写的方式打开了文件,那么就可以利用p u t s命令把输出写到这个文件中。如果文件句柄$ f对应于一个以可写方式打开的文件,那么命令将把字符串“This is a line of text”写到这个打开的文件中。
puts $f "This is a line of text";
另一个文件输入输出命令是c l o s e,这一命令利用文件句柄作为它的参数。要关闭前面打开的文件,只须使用如下命令:
close $f;
在程序的末尾关闭所有打开的文件句柄通常是一个良好的做法;另外,当在程序中多次使
用同一个文件句柄变量时,在下个o p e n命令之前关闭它也是一种值得提倡的做法。除了对文件进行读和写外,有时也需要获取有关文件的信息。t c l提供有一个f i l e命令,该
命令可用来完成这一任务,f i l e命令的语法是:
file option filename
这里的f i l e m a m e是所要进行探测的文件名,o p t i o n是下列选项之一。下列选项将根据文件的信息返回t r u e(1)或f a l s e(O)。
e x e c u t a b l e如果当前用户对该文件可执行,则返回t r u e
e x i s t s如果文件存在,则返回t r u e
i s d i r e c t o r y如果文件是一个目录,则返回t r u e
i s f i l e如果文件是一个普通文件,则返回t r u e
o w n e d如果当前用户拥有该文件,则返回t r u e
r e a d a b l e如果当前用户对该文件可读,则返回t r u e
w r i t a b l e如果当前用户对该文件可写,则返回t r u e
下列选项返回文件的附加信息:
a t i m e返回自1 9 7 0年1月1日以来最后一次访问文件的时间(以秒表示)
m t i m e返回自1 9 7 0年1月1日以来最后一次修改文件的时间(以秒表示}
s i z e返回文件的大小(以字节表示)
r e a d l i n k当给出的文件是符号链接时,返回符号链接的值
t y p e返回给出文件类型的字符串
t c l中的过程等价于C语言中的函数。要创建过程,可利用p r o c命令,这一命令的语法是:
proc procedure_name {arguments} {body}
其中变元的数目是可变的,空的变元表由{ }来表示;b o d y可以包含任何有效的t c l语句并且长度不限。
下面所示是一个无变元的简单过程:
proc test_proc{} {puts "procedure test";}
要调用这一过程,只须给出它的名字,例如:
procedure test;
将得到输出结果。
下面是一个较实际的例子,它是一个文件输出过程,这个过程以文件名作为自己的一个变元:
proc cat {filename} {
set f [open $filename r];
while {[gets $f line]>=0} {
puts $line;
}
close $f;
}
警告在过程的开始的花括号必须要和proc的名称在同一行中。而括号外必须是有一个
空格。要想以/ e t c / p a s s w d为参数调用这一过程,可利用如下命令:
cat/etc/passwd
这一命令将显示出/ e t c / p a s s w d的内容。
在过程中可以使用的三个重要命令是r e t u r n、g l o b a l和c a t c h。g l o b a l命令用来为过程提供对全程变量的访问权;r e t u r n命令用来从过程返回值;c a t c h命令用来探测错误并返回失败值。你可以通过执行如下操作重写c a t过程,从而使它变得较为健壮:
proc cat {filename} {
set ret_code 0;
catch {
set f [open $filename r];
while {[gets $f line] >= 0 } {
puts $line;
}
close f;
set ret_code 1;
}
return $ret_code;
}
你可以通过执行如下操作重写c a t过程,从而使它变得较为健壮:
这一程序显示了命令c a t c h和r e t u r n的用法。当该过程的任何部分失败时,它将返回0(f a l s e),而当c a t执行成功时,它将返回1(t r u e)。当利用要执行的进程作为变元来调用c a t时,这一信息是很有用的。
六.工具箱
t k工具箱是一个利用t c l脚本语言来编写X Wi n d o w图形用户界面( G U I )的工具箱。t k工具箱为t c l语言添加了创建G U I组件的功能,这种G U I组件通常称为构件( w i d g e t )。本节主要讨论可用的t k构件并说明如何创建它们。
6.1构件的介绍
创建构件的基本方法是:
widget_type path option
这里的w i d g e t_t y p e是下面列出的构件类型之一,p a t h是窗口的路径名(通常以一个圆点开始,它是根窗口的名字),o p t i o n是构件能够识别的任一选项。t k工具箱定义的构件类型包括:
c a n v a s用于绘制对象
e n t r y用于单行文本的输入
f r a m e用来包含其他构件
l i s t b o x显示一组字符串并允许对其中的一个或多个字符串进行选择
m e n u显示菜单条和菜单项
t e x t显示多行文本
l a b e l显示单行静态文本
b u t t o n显示一个可点击按钮的构件
c h e c k b u t t o n显示一个可选的方框
r a d i o b u t t o n显示多个互相排斥只能单选的方框
s c a l e类似滚动条,它用来设置一个值
要想创建和操作构件,必须使用wish(windowing shell)。当需要交互地调用w i s h时,在
U N I X提示符下输入w i s h,此时将会出现如下w i s h提示符:
%
与此同时,在屏幕上将弹出一个空的窗口。这一窗口是w i s h的根窗口(称为.),以后创建的所有构件将随此窗口一起显示。
6.2创建构件
本节说明如何创建和操作构件。首先,我们来创建一个按钮:
button .button;
这一行代码用来干什么呢?现在,我们就来解释一下。由于在这一行中指定的构件类型为b u t t o n,因此也将创建一个按钮;这里设定的路径为. b u t t o n,所以t k将在根窗口(.是t k的根窗口)中创建按钮,并把它命名为b u t t o n。
现在按钮在哪儿呢?此时也只是创建了按钮,按钮并没有被立即显示。为了显示按钮,需要告诉t k如何来显示这一构件。为此,利用p a c k命令并给出所要显示的构件的路径:
pack .button;
当运行这一命令后,按钮将被显示出来,但它却是空的(参见图3 3 - 1),这正是构件的选项所要派上用场的地方。
6.3构件的选项
所有构件都可以使用标准的选项来控制它们的外观和功能。大多数构件可以识别如下选项:
-background color,-bg color构件的背景色。有效值的格式为# R R G G B B和# R R R G G G B B B,或者是/ u s r / l i b / X 11 / rg b . t x t所定义的名字之一
-foreground color,-fg color构件的前景色。有效值的格式为# R R G G B B和# R R R G G G B B B,或者是/ u s r / l i b / X 11 / rg b . t x t所定义的名字之一
-height pixels:构件的高度(以像素为单位)
-width pixels构件的宽度(以像素为单位)
-borderwidth pixels,-db pixels构件的边缘宽度(以像素为单位)
-padx pixels构件在x方向上所要求的附加空距
-pady pixels构件在y方向上所要求的附加空距
-relief type构件的3 D效果,这里的t y p e是下列字符串之一:f l a t、r a i s e d、g r o v e、
r i d g e、s u n k e n
-text string在构件中显示的字符串
-font font在构件中显示文本所使用的字体。有效的字体定义由命令xlsfonts
给出
-command command当利用构件时所执行的t c l命令,它通常是一个过程名或e x e c语句
除了上面的选项外,p a c k命令还可以识别它自己的一些选项:
-side type控制构件排放的位置。它的有效排放类型为l e f t、r i g h t、t o p或b o t t o m。例
如,l e f t表示新构件应排放在现有构件的左边
-fill type控制构件是否充满窗口所打开的空间。它的有效值为n o n e、x、y或b o t h。例
如,b o t h表示构件应充满被打开的整个空间
-expand value控制构件是否随着窗口大小的增加而扩大。这里的v a l u e或者是0或者是1,
表示构件的大小随窗口的变化而变化
6.4一个TCL./TK构件的编程示例
目前,你已经知道了有关构件和p a c k的选项,因此可以着手使用它们。构件具有的一个有趣的特性是r e l i e f,即构件的三维效果。为了了解每种r e l i e f类型的外观表现,可以利用如下命令制作一些标签:
foreach I { raised sunken flat groove ridge} {
label .$I–relief $I–text $I;
pack .$I
}
这一例子将遍历r e l i e f的所有类型,对于每种类型创建一个标签,并把每个标签的t e x t选项设置为相应r e l i e f类型的名字。在这里,有两点需要注意。第一,标签的大小不一;第二,标签的排放方式是一个放在另一个之上。这种情况是p a c k命令缺省表现形式的一个例子,p a c k命令将自动确定每个构件的大小然后把每个构件放置在前一个构件的下面。
现在,让我们来把所有的标签设置为同样的大小,并把它们并排放置,而不是一个标签压
在另一个标签之上。为达到这一目的,可以有两种方法。第一种方法是把循环改写为:
foreach I { raised sunken flat groove ridge} {
label .$I–relief $I–text $I–height 10–width 10;
pack .$I–side left
}
第二种方法是利用c o n f i g u r e选项重新配置标签,c o n f i g u r e选项的语法是:
widget configure option
在这里,你可以利用下面的循环(在创建标签之后):
foreach I { raised sunken flat groove ridge} {
.$I configure–height 10–width 10;
pack .$I–side left
}
为什么要利用c o n f i g u r e呢?
这是因为当交互运行w i s h时,如果已经提供了循环的一个版本,那么当修改并再次运行它时,将会产生下面的错误信息:
window name "raised" already exists in parent
通过这句话,w i s h告诉程序员,他的程序试图重新创建现存的构件(在这里是标签r a i s e d)。由于这一原因,所以需要使用c o n f i g u r e。事实上,任何需要修改现有构件的情况都要求使用c o n f i g u r e。
在本例中,想要使用循环新版本的唯一方法是利用d e s t r o y命令破坏现有的标签,就像下面这样:
foreach i {raised sunken flat groove ridge} {destroy .$i}
现在让我们回到这个例子。可以看出,
有两方面的问题应该解决。一方面的问题是,标签之间相距太近,因此难以区分,另一方面的问题是:窗口的大部分是空的。通过在排放标签时填充空白并通过增加它们的边框宽度,可以使它们很容易地被区分开来。为了使标签占用全部可用的空间,可以把p a c k的
f i l l选项设置为b o t h,从而使标签在x和y方向上都进行延伸,并且把e x p a n d选项设置为t r u e:
注意为了查看f i l l和e x p a n d选项的效果,你需要重设窗口的大小,并使得标签适应窗口。通过把l a b e l替换为不同类型的构件,可以很容易地对这个例子进行修改,从而使它显示出各种效果,f i l l选项设置为b o t h,从而使标签在x和y方向上都进行延伸,并且把e x p a n d选项设置为t r u e:
注意为了查看f i l l和e x p a n d选项的效果,你需要重设窗口的大小,并使得标签适应窗口。通过把l a b e l替换为不同类型的构件,可以很容易地对这个例子进行修改,从而使它显示出各种效果的标签。
七.小结
本文对利用t c l / t k编程进行了简单介绍。这里的例子显示了t c l / t k的强大功能,利用它可以在很短的时间内以少量的代码创建出用户界面。虽然本章讨论了t c l / t k的许多特性,但是还有更多的特性未讨论。我希望各位读者能以本章做为进身之阶,加入到开发t c 1 / t k应用程序的行列中去。