Tcl编程简介
2010-10-17 11:47:06 阿炯

Tcl(最早称为“工具命令语言”"Tool Command Language", 但是目前已经不是这个含义,不过我们仍然称呼它为TCL)是一种脚本语言,由John Ousterhout创建。其编程范型支持:多泛型、面向对象、函数式、过程式、事件驱动、指令式。



TCL语言堪称一个多面手,尤其诞生于其基石——Tk库,更是让Python、Perl、Haskell等诸多编程语言受益,将GUI设计的重任委以重任。曾经,TCL以其灵活性在工程软件的扩展领域大放异彩,然而,随着互联网的蓬勃发展和工具语言的强势崛起,TCL的光芒似乎一度被遮掩。

实际上,TCL无所不能,犹如瑞士军刀,能够应对各种复杂任务。关键在于TCL的核心理念——“一切皆字符串”。这个理念颠覆了我们对传统编程的认知。在TCL的世界里,列表并非孤立的存在,它们可以像字符串一样被处理和操作;同样,字符串也能摇身一变,成为包含丰富数据的列表。这种灵活性使得TCL在处理文本、数据转换和复杂逻辑上得心应手。它在现代编程领域依然保持着独特的竞争力。无论是自动化任务的实现,还是跨领域的数据处理,TCL都能以它的强大和灵活,让你的工作如虎添翼。所以,对于那些寻求高效、便捷解决方案的开发者来说,TCL无疑是值得深入探索的宝藏语言。

Tcl/Tk 是一种简明、高效、可移植性好的编程语言,在信息产业领域具有广泛的应用。TCL很好学,功能很强大,经常被用于快速原型开发、脚本编程、GUI和测试等方面,采用BSD协议授权。TCL念作“踢叩” "tickle".,其特性包括:
任何东西都是一条命令,包括语法结构(for、if等),以波兰表示法书写。
命令通常可变。
任何事物都可以重新定义和重载。
所有的数据类型都可以看作字符串,包括源代码。
拥有完全动态、基于类的对象系统 TclOO,支持包括元类、过滤器和mixin在内的高级功能。
强大的事件系统,提供事件驱动给套接字和文件。基于时间或者用户定义的事件也可以。
默认的变量作用域是词法作用域,但 uplevel 和 upvar 允许过程与封闭的函数作用域交互。
所有的内置命令会在误用时产生错误消息。
很容易用 C、C++ 或者 Java 扩展。
解释语言,支持字节码。
完全的 Unicode(3.1)支持,1999 年首次发布。
跨平台。支持 Win32、UNIX、Linux、Mac 等。
和GUI开发包 Tk 紧密集成。
代码紧凑,易于维护。

存在多种发行版:
Batteries-Included 版本,如 ActiveState ActiveTcl
tclkit,一种单文件运行时环境,仅有 1 兆大小
starpack,脚本/程序的单文件的可执行文件,派生自 tclkit 技术
freewrapTCLSH 将 Tcl 脚本打包为单个可执行二进制文件。


旧版 Tcl 没有内置面向对象功能,因此许多 OO 库以扩展形式涌现出来,如 incr Tcl 和 XOTcl,甚至存在纯脚本编写的 OO 包,如 Snit 和 STOOOP(simple Tcl-only object-oriented programming),8.6 版本在内核中提供了 OO 功能。Safe-Tcl 是功能受限的 Tcl 子集。文件系统访问受限,任意系统命令禁止执行。它使用双解释器模型,在“不可信解释器”中运行不可信脚本中的代码。由 Nathaniel Borenstein 和 Marshall Rose 设计,借以在电子邮件中包含活动信息,当支持 application/safe-tcl 与 multipart-enabled-mail 时,Safe-Tcl 即可包含于电子邮件中。Safe-Tcl 功能已集成在标准 Tcl/Tk 发布中。

Tcl本身不提供面向对象的支持,但是语言本身很容易扩展到支持面向对象。许多C语言扩展都提供面向对象能力,包括XOTcl, Incr Tcl 等,另外SNIT扩展本身就是用TCL写的。Tcl/Tk 的发明人 John Ousterhout 教授在上世纪八十年代初在伯克利大学任教,在其教学过程中发现在集成电路 CAD 设计中,很多时间是花在编程建立测试环境上。且环境一旦发生了变化,就要重新修改代码以适应。这种费力而又低效的方法,迫使 Ousterhout 教授力图寻找一种新的编程语言,它即要有好的代码可重用性,又要简单易学,这样就促成了 Tcl (Tool Command Language) 语言的产生。使用最广泛的TCL扩展是TK,它提供了各种OS平台下的图形用户界面GUI。连强大的Python语言都不单独提供自己的GUI,而是提供接口适配到TK上。另一个流行的扩展包是Expect. Expect提供了通过终端自动执行命令的能力,例如(passwd, ftp, telnet等命令驱动的外壳)。

Tcl 最初的构想的是希望把编程按照基于组件的方法 (component approach),即与其为单个的应用程序编写成百上千行的程序代码,不如寻找一个种方法将程序分割成一个个小的,具备一定“完整”功能的,可重复使用的组件。这些小的组件小到可以基本满足一些独立的应用程序的需求,其它部分可由这些小的组件功能基础上生成。不同的组件有不同的功能,用于不同的目的。并可为其它的应用程序所利用。当然这种语言还要有良好的扩展性,以便用户为其增添新的功能模块。最后,需要用一种强的,灵活的“胶水”把这些组件“粘”合在一起, 使各个组件之间可互相“通信”,协同工作。程序设计有如拼图游戏一样,这种设计思想与后来的 Java 不谋而合。终于在 1988 年的春天, 这种强大灵活的胶水 - Tcl 语言被发明出来了。

Tcl 支持扩展包,这些扩展包提供了附加功能(像是GUI,终端程序自动化,数据库访问等),常用的扩展包有:

Tk
Tk 工具包是最流行的 Tcl 扩展,在多种操作系统上提供图形用户界面。每个 GUI 由一个或多个框架(framework)组成,每个框架内含布局管理器。Tk是一开放源代码的图形用户界面开发工具,提供许多常用的图形接口组件(像是菜单、按钮之类),具有跨平台、轻量化等特色。其始发于1991年,是以Tcl脚本语言编写的扩展,目前另有Perl、Python、Ruby、Common Lisp等多种版本,并可在Linux、Unix、Apple Macintoch、Windows等平台上运行。从Tcl/Tk 8开始,软件搭建的图形界面看起来将“与本地系统一致”。8.5版引入了一个新的主题引擎,它在字体渲染方面也有改进。该引擎最初叫Tk Tile,但现在通常被称为"themed Tk"。8.6版的更新则支持了PNG格式,以及倾斜文字。

Tk是用Tcl脚本编写跨平台图形用户界面框架。使用Tcl Shell(Tclsh),以命令package require Tk调用Tk。在图形操作系统下,Wish提供了包含Tclsh和Tk的图形窗口环境。其具有以下特性:
平台独立:与Tcl一样,Tk也是解释型的。各种平台下的Tcl实现是统一的,因此Tk程序可无需修改地移植至各种平台[9]。
可定制:Tcl中几乎所有的特性都是可以修改的,可通过初始化选项或运行时的命令修改[10]。
可保存:很多选项出存在数据库中,例如界面的颜色设置。这些选项能保存下来,在再次加载程序时读取[11]。

Tk语言绑定

通过语言绑定,以Tcl实现的Tk可在其他编程语言中调用。已有多种语言支持Tk,完整的列表见于Tk的网站。 例如Ada中的TASH模块,Haskell中的HTk,Perl(有自研或Tcl扩展),Python中的Tkinter模块,Ruby,REXX, 以及Common Lisp。在Perl中调用Tk用多种方法:Tcl::Tk模块以及Tkx模块均适用Tcl作为桥梁;而Perl/Tk直接使用Perl实现Tk。Python的Tkinter模块使用与Tcl的语言绑定实现Tk。Tk提供了多种部件。基本部件集成在toplevel部件里,作为可移动的浮动窗口,通常由操作系统管理。

Tk基本特性
button
canvas
checkbutton
combobox
entry
frame
label
labelframe
listbox
menu
menubutton
message
notebook
panedwindow
progressbar
radiobutton
scale
scrollbar
separator
sizegrip
spinbox
text
tk_optionMenu
treeview

Tk顶层部件
tk_chooseColor – 弹出一个颜色选择窗口
tk_chooseDirectory – 弹出一个文件目录选择窗口
tk_dialog – 弹出一个对话框,等待用户回应
tk_getOpenFile – 弹出一个对话框,供用户选择并打开一个文件
tk_getSaveFile – 弹出一个对话框,供用户选择目录储存文件
tk_messageBox – 弹出一个消息框,等待用户回应
tk_popup – 显示弹出菜单
toplevel – 创建并复制顶层部件

Tk图形管理器
列于toplevel的基本部件使用图形管理器管理:
place – 定位部件至给定的绝对位置
grid – 将部件按网格排列
pack – 将部件打包


Expect
Expect是另外一种非常流行的 Tcl 扩展。早期,Expect 对 Tcl 在多种领域的流行居功甚伟,如在测试领域中,时至今日 Expect 依然被大量使用于 telnet、ssh 与串口会话的重复任务自动化,即对仅有终端交互接口的程序进行编程。Tcl 是运行 Expect 的唯一方式,因此 Tcl 在此类工业领域中十分流行。

Tile/Ttk
Tile/Ttk 是风格和主题化控件集,可替代 Tk 中大多数控件,真正调用操作系统的 API 实现原生界面。这种方式提供的主题包括 Windows XP、Windows Classic、Qt 和 Aqua(Mac OS X)。主题也可使用图片 pixmap 加上一定定义构造,避免调用系统 API。以这种方式创建的主题有 Classic Tk、Step、Alt/Revitalized、Plastik 和 Keramik。Tcl 8.4 中,此包称作 Tile,在 8.5 中以 Ttk 的名字进入 Tk 核心发布。

Tix
Tix(Tk Interface eXtension)是一套开源的、用于扩充 Tcl/Tk 和 Python 应用程序功能的用户界面组件。由 Tix Project Group 维护,以 BSD 风格许可发布。

Itcl/IncrTcl
Itcl 是 Tcl 诸多对象系统中的一种,通常称为 [incr Tcl](递增 Tcl 之意,类似 C++ 之名)。

Tcllib
Tcllib 是一套纯脚本 Tcl 包,无需编译。

TclUDP
TclUDP 提供简捷的方式支持 UDP 套接字。

数据库
Tcl 数据库互联(Tcl Database Connectivity,TDBC)是 Tcl 8.6 的一部分,为 Tcl 脚本提供常用数据库的访问接口,目前驱动器支持 MySQL、ODBC、PostgreSQL 和 SQLite 数据库。更多数据库已经有了计划,同样也可使用许许多多数据库专用的扩展包访问数据库。


下面是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

解释:第一行定义了过程every, 每隔ms毫秒,就重新执行body代码。第二行创建了标签起内容由time变量决定。第3行中设置定时器,time变量从当前时间中每秒更新一次。

Tcl被广泛的用做script语言,大多数情况下,Tcl和Tk(“Tool Kit”)库同时使用,Tk是一系列令Tcl易于编写图形用户接口的命令和过程。Tcl的一个重要特性是它的扩展性。如果一个程序需要使用某些标准Tcl没有提供的功能,可以使用c语言创造一些新的Tcl命令,并很容易的融合进去。正是由于Tcl易于扩展,很多人为它编写了扩展包,并在网上共享。Tcl和其他编程语言例如c不同,它是一种解释语言而非编译语言。Tcl程序由一系列Tcl命令组成,在运行时由Tcl解释器解释运行。解释运行的一个优点是它可以自己为自己生成Tcl script。

变量和变量交换
不像c,Tcl的变量在使用前不需要声明。Tcl的变量在它首次被赋值时产生,使用set命令。变量可以用unset命令删除,虽然并不强制需要这样做。变量的值通过$符号访问,也叫变量交换。Tcl是一个典型的”弱类型定义”语言,这意味者任何类型可以存储在任何变量中。例如,同一个变量可以存储数字,日期,字符串甚至另一段Tcl script.

Example 1.1:
set foo "john"
puts "Hi my name is $foo"
Output: Hi my name is john
Example 1.2:
set month 2   
set day 3   
set year 97   
set date "$month:$day:$year"   
puts $date
Output: 2:3:97
Example 1.3:
set foo "puts hi"   
eval $foo
Output: hi
在这个例子里,变量foo存储了另外一段Tcl script.

表达式
包括数学表达式,关系表达式,通常用 expr命令。
Example 2.1:
expr 0 == 1
Output: 0
Example 2.2:
expr 1 == 1
Output: 1
两数比较,true则输出1,false输出0
Example 2.3:
expr 4 + 5
Output: 9
Example 2.4:
expr sin(2)
Output: 0.909297

命令传递
以运算结果替代Tcl命令中的部分

Example 3.1:
puts "I am [expr 10 * 2] years old, and my I.Q. is [expr 100 - 25]"
Output: I am 20 years old, and my I.Q. is 75
方括号是命令传递的标志
Example 3.2:
set my_height 6.0
puts "If I was 2 inches taller, I would be [expr $my_height + (2.0 / 12.0)] feet tall"
Output: If I was 2 inches taller, I would be 6.16667 feet tall

命令流控制
Tcl有判断流转(if-else; switch)和循环控制(while; for; foreach)

Example 4.1:
set my_planet "earth"
if {$my_planet == "earth"} {
puts "I feel right at home."
} elseif {$my_planet == "venus"} {
puts "This is not my home."
} else {
puts "I am neither from Earth, nor from Venus."
}
set temp 95
if {$temp < 80} {
puts "It's a little chilly."
} else {
puts "Warm enough for me."
}
Output:
I feel right at home.
Warm enough for me.
Example 4.2:
set num_legs 4
switch $num_legs {
2 {puts "It could be a human."}
4 {puts "It could be a cow."}
6 {puts "It could be an ant."}
8 {puts "It could be a spider."}
default {puts "It could be anything."}
}          
Output:
It could be a cow.

Example 4.3:
for {set i 0} {$i < 10} {incr i 1} {
puts "In the for loop, and i == $i"
}
Output:
In the for loop, and i == 0
In the for loop, and i == 1
In the for loop, and i == 2
In the for loop, and i == 3
In the for loop, and i == 4
In the for loop, and i == 5
In the for loop, and i == 6
In the for loop, and i == 7
In the for loop, and i == 8
In the for loop, and i == 9

Example 4.4:
set i 0
while {$i < 10} {
puts "In the while loop, and i == $i"
incr i 1
}
Output:
In the while loop, and i == 0
In the while loop, and i == 1
In the while loop, and i == 2
In the while loop, and i == 3
In the while loop, and i == 4
In the while loop, and i == 5
In the while loop, and i == 6
In the while loop, and i == 7
In the while loop, and i == 8
In the while loop, and i == 9

Example 4.5:
foreach vowel {a e i o u} {
puts "$vowel is a vowel"
}
Output:
a is a vowel
e is a vowel
i is a vowel
o is a vowel
u is a vowel

Procedures
Tcl的Procedures 和c的函数差不多. 它们有参数,它们返回值。基本定义方法是:
proc name argList body

当一个procedure被定义,它就被看做是一个命令,如同Tcl的自带命令一样,通过名字来呼叫,名字后面跟上参数。缺省的,procedure的返回值是它的最后一个命令结果。但也可以通过return命令来返回其他值。Return值可以在procedure的任何地方,一旦执行,procedure就此返回。

Example 5.1:
proc sum_proc {a b} {
return [expr $a + $b]
}
proc magnitude {
if {$num > 0} {
return $num
}
set num [expr $num * (-1)]
return $num
}
set num1 12
set num2 14
set sum [sum_proc $num1 $num2]
puts "The sum is $sum"
puts "The magnitude of 3 is [magnitude 3]"
puts "The magnitude of -2 is [magnitude -2]"
Output:
The sum is 26
The magnitude of 3 is 3
The magnitude of -2 is 2
在procedure中可以通过set创造变量,但是变量只在procedure中有效,而且一旦procedure返回,这些变量就不可访问。如果procedure需要访问主程序中的变量,就需要使用global关键字。

Example 5.2:
proc dumb_proc {} {
set myvar 4
puts "The value of the local variable is $myvar"
global myglobalvar
puts "The value of the global variable is $myglobalvar"
}
set myglobalvar 79
dumb_proc
Output:
The value of the local variable is 4
The value of the global variable is 79


Lists
Lists就好像是Tcl中的一种特殊的数组。它吧一堆东西放成一个集合,然后就像操作一个整体一样的操作它。

Example 6.1:
set simple_list "John Joe Mary Susan"
puts [lindex $simple_list 0]
puts [lindex $simple_list 2]
Output:
John
Mary
注意list的index是从0开始的

Example 6.2:
set simple_list2 "Mike Sam Heather Jennifer"
set compound_list [list $simple_list $simple_list2]
puts $compound_list
puts [llength $compound_list]
Output:
{John Joe Mary Susan} {Mike Sam Heather Jennifer}
2

Example 6.3:
set mylist "Mercury Venus Mars"
puts $mylist
set mylist [linsert $mylist 2 Earth]
puts $mylist
lappend mylist Jupiter
puts $mylist
Output:
Mercury Venus Mars
Mercury Venus Earth Mars
Mercury Venus Earth Mars Jupiter

Arrays
Tcl数组在使用前无须定义,大小也不用指定。
Example 7.1:
set myarray(0) "Zero"
set myarray(1) "One"
set myarray(2) "Two"
for {set i 0} {$i < 3} {incr i 1} {
puts $myarray($i)
}
Output:
Zero
One
Two

Example 7.2:
set person_info(name) "Fred Smith"
set person_info(age) "25"
set person_info(occupation) "Plumber"
foreach thing {name age occupation} {
puts "$thing == $person_info($thing)"
}
Output:
name == Fred Smith
age == 25
occupation == Plumber
这个例子指出数组的index不需要是数字,其他类型的数据也可以。

Example 7.3:
set person_info(name) "Fred Smith"
set person_info(age) "25"
set person_info(occupation) "Plumber"
foreach thing [array names person_info] {
puts "$thing == $person_info($thing)"
}
Output:
occupation == Plumber
age == 25
name == Fred Smith

Strings
字符串是Tcl中最常用的类型,string有很多使用参数,可以参照Tcl手册。使用方法:
string option arg arg ...

Example 8.1:
set str "This is a string"
puts "The string is: $str"
puts "The length of the string is: [string length $str]"
puts "The character at index 3 is: [string index $str 3]"
puts "The characters from index 4 through 8 are: [string range $str 4 8]"
puts "The index of the first occurrence of letter \"i\" is: [string first i $str]"
Output:
The string is: This is a string
The length of the string is: 16
The character at index 3 is: s
The characters from index 4 through 8 are: is a
The index of the first occurrence of letter "i" is: 2

Input/Output
Tcl的绝大多数输入/输出是通过puts和gets做到的。Puts命令显示在console上,gets命令从console输入上取得数据,并存储在某个变量中。
gets channelId varName
channelID可以理解为c的文件句柄,varName如果定义,输入值就赋给它,gets返回读入的字节数,否则gets直接返回输入值。

Example 9.1:
puts -nonewline "Enter your name: "
set bytesread [gets stdin name]
puts "Your name is $name, and it is $bytesread bytes long"
Output: (note that user input is shown in italics)
Enter your name: Shyam
Your name is Shyam, and it is 5 bytes long

Example 9.2:
set f [open "/tmp/myfile" "w"]
puts $f "We live in Texas. It's already 110 degrees out here."
puts $f "456"
close $f
Output: (none)
Open打开了一个 "/tmp/myfile" 作为channel. 用法是:
open name access
access参数指出打开文件的方式,”w”是读写。这时可以用puts $f把内容写入文件

Example 9.3:
set f [open "/tmp/myfile" "r"]
set line1 [gets $f]
set len_line2 [gets $f line2]
close $f
puts "line 1: $line1"
puts "line 2: $line2"
puts "Length of line 2: $len_line2"

Output:
line 1: We live in Texas. It's already 110 degrees out here.
line 2: 456
Length of line 2: 3
这个例子假设已知文件只有两行,如果不是,则需要使用循环,用eof来找到文件尾。
eval
eval命令会把它的参数直接送往解释器。

Example 10.1:
set foo "set a 22"
eval $foo
puts $a
Output:
22
单纯的执行$foo不会成功。
catch

Example 10.2:
set retval [catch {set f [open "nosuchfile" "r"]}]
if {$retval == 1} {
puts "An error occured"
}
Output: (this output occurs if there is no file named "nosuchfile" in the current directory).
An error occured

Catch 参数记录一个script的执行情况,如果返回值为1,则执行出错。用来进行错误处理。

最新版本:8.6
主要更新内容有:
修复了大量 BUG。
优化了部分功能,效率提升。
实现对 Windows 10 的支持。

参考资料:
1.https://www.tcl.tk/
2.http://www.tcl.tk/doc/
3.http://www.activestate.com/Products/ActiveTcl/



该文章最后由 阿炯 于 2024-04-19 13:15:11 更新,目前是第 2 版。