1. 简介
TCL(Tool Command Language,工具命令语言)是一种功能强大且易于学习的 脚本语言,广泛应用于快速开发、测试自动化、过程控制及嵌入式系统等领域。
来源:TCL 最初由 John Ousterhout 于 1980 年代末在加州大学伯克利分校开发,用于创建用户界面工具。
特点:简单灵活、内置字符串处理功能强大、易于嵌入到其他应用程序中。
运行 TCL 脚本:使用 tclsh 命令来执行 TCL 脚本文件,例如 tclsh script.tcl。
1.1 语言特点
一个 TCL 脚本可以包含一个或多个命令。
命令之间必须用换行符或分号隔开。
TCL 的每一个命令包含一个或几个单词,第一个单词代表命令名,另外的单词则是这个命令的
参数,单词之间必须用空格或 TAB 键隔开。使用
#
进行注释
TCL 解释器对一个命令的求值过程分为两部分:分析和执行。
- 在分析阶段,TCL 解释器运用规 则把命令分成一个个独立的单词,同时进行必要的置换(substitution);
- 在执行阶段,TCL 解释器会把第一个单词当作命令名,并查看这个命令是否有定义,如果有定义就激活这个命令对应 的 C/C++ 过程,并把所有的单词作为参数传递给该命令过程,让命令过程进行处理。
2. 开始 TCL
2.1 变量声明与赋值
2.1.1 赋值与输出
TCL 中基本数据类型只有一种:字符串类型,tcl 的所有数据类型最终都可以表示为字符串。
赋值时无需声明变量类型。
变量声明语法:set 变量名 数据
数据类型分为:
- 字符串或数字。引号可带可不带,当字符串中有空格时,才必须加引号;数字内部存储为字符串
- 列表,用大括号(花括号)表示
- 数组
- 句柄
#!/bin/tclsh
set aa "hello world" ;# 字符串
set bb 10 ;# 字符串
set cc {red bule green} ;# 列表
set marks(english) 92 ;# 数组
set myfile [open "filename" r] ;# 句柄
unset cc marks myfile
puts $aa
puts $bb
使用 puts 来输出变量的值:puts $ 变量名
使用 unset 来删除变量:unset 变量名 1 变量名 2...
一个 TCL 的简单变量包含两个部分:名字和值。名字和值都可以是任意字符串。
例如一个名为“1323 7&*: hdgg”的变量在 TCL 中都是合法的。不过为了更好的使用置换 (substitution), 变量名最好按 C\C++ 语言中标识符的命名规则命名。 TCL 解释器在分析一个变量置换时,只把从$
符号往后直到第一个不是字母、数字或下划线的字符之间的单词符号作为要被置换的变量的名字。
为了界定变量名的开始和结束,建议使用 { }
限制变量名
TCL 中的 set 命令能生成一个变量、也能读取或改变一个变量的值。
2.1.2 更改变量
- append: 加文本
- incr:加值
#!/bin/tclsh
set aa "hello"
append aa " world"
puts $aa ;# 输出:hello world
set x1 10
incr x1 1
puts $x1 ; 输出:11
2.1.3 命令构造
tcl 使用 eval 命令用来构造和执行 TCL 脚本,其语法为:
eval command arg1 arg2...
eval
目的是构造单一脚本,将后面的部分拼接成字符串,整体解析执行
- eval 一般是非必要的,只要按照命令用法写参数,可以直接执行。
- 但是如果存在变量替换,有可能被替换参数的命令会报错,这个时候就可以使用 eval 解决这个问题
举例如下:
假如一个命令用法是:create polygon layer x1 y1 x2 y2... xn yn
#!/bin/tclsh
set coord1 "0 0 100 100"
set coord2 {0 0 100 100}
create polygon tgt 0 0 100 100 ;# 可以运行
create polygon tgt $coord1 ;# 报错
create polygon tgt $coord2 ;# 报错
上述 create polygon 会执行 报错,因为 coord 是字符串或者列表,而命令要求的输入是直接的数字或字符。解决如下:
eval create polygon tgt $coord1
eval create polygon tgt $coord2
eval 会把后面的部分整体解析执行。
2.2 置换
TCL 解释器在分析命令时,把所有的命令参数都当作字符串看待
2.2.1 变量置换 variable subtitution
变量置换由一个 $ 符号标记,变量置换会将变量代表的值进行替换。
#!/bin/tclsh
set x 10
set y $x+100
puts $y ;# 输出为:10+100
这时,y 的值还不是我们想要的值 110,而是 10+100,因为 TCL 解释器把 10+100 看成是一个字符串而不是表达式,y 要想得到值 110,还必须用命令置换,使得 TCL 会把 10+100 看成一个表达式并求值。
2.2.2 命令置换 command substitution
命令置换是由 []
括起来的 TCL 命令及其参数,命令置换会导致某一个命令的所有或部分单词被另一个命令的结果所代替。
#!/bin/tclsh
set x 10
set y [expr $x+100]
puts $y ; #输出为:110
当 TCL 解释器遇到字符’[‘时,它就会把随后的 expr 作为一个命令名,从 而激活与 expr 对应的 C/C++ 过程,并把’expr’和变量置换后得到的’10+110’传递给该命令过 程进行处理。
注意,[]
中必须是一个合法的 TCL 脚本,长度不限。
[]
中脚本的值为最后一个命令的返回值, 例如:
#!/bin/tclsh
set y [expr $x+100;set b 300]
puts $y ; #输出为:300
有了命令置换,实际上就表示命令之间是可以嵌套的,即一个命令的结果可以作为别的命令的参数。
2.2.3 反斜杠置换(backslash substitution)
TCL 语言中的反斜杠置换类似于 C 语言中反斜杠的用法,主要用于在单词符号中插入诸如换行符、空格、[、$ 等被 TCL 解释器当作特殊符号对待的字符。
反斜杠置换符 \
主要用于连接两行或者取消空格的分隔符属性,或者取消其他的特殊符号的作用
#!/bin/tclsh
set aa hello\ world; #空格为分隔符, \ 后接空格会取消空格的分割作用
set x 10
set y \$x
puts $y ; #输出为 $x
2.3 双引号和括号
TCL 解释器对双引号" "
中的各种分隔符将不作处理,但是对换行符及 $
和[]
两种置换符会照常处理。例如:
#!/bin/tclsh
set aa "hello world"
set bb "$aa nihao!"
而在花括号 { }
中,所有特殊字符都将成为普通字符,失去其特殊意义,TCL 解释器不会对其作特 殊处理。例如:
#!/bin/tclsh
set bb {$aa nihao!}
单引号 ('
)
- 作用 : 没有特殊作用 。单引号在 Tcl 的基本语法中 不被视为引号,它只是一个普通的字符。
2.4 注释
TCL 中的注释符是’#’,’#’和直到所在行结尾的所有字符都被 TCL 看作注释,TCL 解释器对注释将不作任何处理。不过,要注意的是,’#’必须出现在 TCL 解释器期望命令的第一个字符出现的地方,才被当作注释。
#!/bin/tclsh
#This is a comment
set a 100 # Not a comment
wrong # args: should be "set varName ?newValue?"
set b 101 ; # this is a comment
要在命令行后面写注释,需要用分号分隔符
第二行中’#’就不被当作注释符,因为它出现在命令的中间,TCL 解释器把它和后面的字符当作命令的参数处理,从而导致错误。而第四行的’#’就被作为注释,因为前一个命令已经用一个分号结束,TCL 解释器期望下一个命令接着出现。现在在这个位置出现’#’,随后的字符就被当作注释了。
2.6 字符串
2.6.1 常规方法
TCL 把所有的输入都当作字符串看待,所以 TCL 提供了较强的字符串操作功能,TCL 中与字符串操作有关的命令有:string、format、regexp、regsub、scan 等。
格式化输出:
format string value1 value2...
set msg [format "%s is %d years old" $name $age]
字符串比较是否相等:
string equal -nocase -length int str1 str2
。相同返回 1,不同返回 0- -nocase 的意思就是不区分大小写。
- -length int 中的 int 就是需要比较的前面几个字符。
set res [string equal "hello world" "Hello World"]
字符串查找:
string first -exact substring str start
,返回索引- 如果使用了
-exact
选项,匹配会变得严格,即只有在大小写和字符顺序都完全匹配的情况下才会返回匹配位置。如果省略-exact
,则匹配是大小写不敏感的。 - substring,需要查找的子串
- start,一个数字,指定开始查找位置
set res1 [string first "tcl" "This is a tcl example"] set res2 [string first "is" "This is a tcl example" 4] set res3 [string first "is" "This is a tcl example"]
- 如果使用了
字符串查找:
string last substring str last
,与 first 命令相似,只是返回的是从尾部开始搜索到的匹配的字符段返回索引到的字符:
string index str charIndex
,字符串的索引从 0 开始返回字符串类型:
string is class -strict -failindex varname str
。如果 string 是指定 class 中的成员就返回 1,否则返回 0。如果指定了 -strict,空字符串就返回 0,不指定则返回 1。如果指定了 -failindex varnane,那么将导致不匹配的索引储存在 varname 中,如果返回 1 则 varname 不会被赋值。string is lower "This"
返回 0class 支持以下选项:
字符串切片:
string range str first last
。返回 str 中从 first 到 last 指定索引范围内的所有字符。first 和 last 指定索引重复字符串:
string repeat str count
。返回一个把 str 重复 count 次的字符串。字符串替换:
string replace str first last newstring
。first 和 last 指定了转换的范围。全小写字符串:
string tolower str first last
。将一个字符串全部变为小写形式。first 和 last 指定了转换的范围。全大写字符串:
string totitle str first last
首字符大写:
string toupper str first last
删除字符:
string trim str chars
。去掉 chars 字符,如果没有指定 chars 字符,则去掉空白符(包括空格符、制表符、换行符、回车符)。trim 对字符串开头和结尾都操作。去掉开头或结尾:
string trimleft str chars
和string trimright str chars
还有一些其他的就不一一列举了。
2.6.2 字符串匹配
2.6.2.1 简单匹配
语法:[string match pattern str]
。如果 str 匹配 pattern 就返回 1,否则返回 0。使用的是通配风格的匹配。
- string match 不支持正则表达式,仅支持简单通配符,例如:
* ? [0-9A-Za-z]
。
举个例子:
set pattern "hello*"
set string "hello world"
if {[string match $pattern $string]} {
puts " 匹配成功 "
} else {
puts " 匹配失败 "
}
string match {a*[6-81A-Z]xyz\*} "abcd6xyz*"
# 使用大括号包括整个参数,来避免出现变量置换或者分隔符等一些问题,[6-81A-Z]表示匹配 6,7,8,1,A,B,C,D,...X,Y,Z
2.6.2.2 复杂匹配
语法:[regexp pattern string match subMatch1...]
。
- pattern:匹配规则
- string:待匹配的字符串
- match:保存匹配的正则表达式结果
- subMatch1:Submatch1 到 Submatchn 是可选的子匹配变量,用于保存子匹配模式的结果
set pattern {hello\s\w+}
set string "hello world"
if {[regexp $pattern $string match]} {
puts " 匹配成功: $match"
} else {
puts " 匹配失败 "
}
regexp {([A-Za-z]*)} "Tcl Tutorial" a b
puts "Full Match: a"; #Full Match: Tcl
puts "Sub Match1:b" ; #Sub Match1: Tcl
匹配符号和其他语言一般的正则表达式基本是一致的,下面给出一部分例子:

regexp 可用的开关列表如下:
-nocase
用于忽略大小写。-indices
存储匹配子模式的位置,而不是匹配的字符。-line
新行敏感匹配。忽略换行符后的字符。start index
设置搜索模式的起始偏移量。
2.5 数组
数组是一些元素的集合。TCL 的数组和普通计算机语言中的数组有很大的区别。在 TCL 中,不能单独声明一个数组,数组只能和数组元素一起声明。数组中,数组元素的名字包含两部分:数组名和数组中元素的名字,TCL 中数组元素的名字(下标〕可以为任何字符串。例如:
#!/bin/tclsh
set day(monday) 1
set day(tuesday) 2
第一个命令生成一个名为 day 的数组,同时在数组中生成一个名为 monday 的数组元素,并把值置为 1,第二个命令生成一个名为 tuesday 的数组元素,并把值置为 2。
monday 和 tuesday 可以理解为数组的下标。
#!/bin/tclsh
set day(monday) 1
set b $day(monday); #b 的值为 1
获取数组元素个数:
[array size variablename]
2.7 操作数
TCL 表达式的操作数通常是整数或实数。整数一般是十进制的, 但如果整数的第一个字符是 0(zero),那么 TCL 将把这个整数看作八进制的,如果前两个字符是 0x 则这个整数被看作是十 六进制的。TCL 的实数的写法与 ANSI C 中完全一样。
2.1 7.9e+12 6e4 3.
等都是合法的数字书写方式。
TCL 支持多种运算符,比如 算术运算符,关系运算符,逻辑运算符,按位运算符,三元运算符等。参阅Tcl 运算符 | Tcl/Tk 教程
TCL 支持常用的数学函数,表达式中数学函数的写法类似于 C\C++ 语言的写法。参阅Tcl - 内置函数
这里额外介绍下条件运算符
exp?:value1:value2
#!/usr/bin/tclsh
set a 10;
set b [expr $a == 1 ? 20: 30]
puts "Value of b is $b\n"
set b [expr $a == 10 ? 20: 30]
puts "Value of b is $b\n"
2.8 列表
list 这个概念在 TCL 中是用来表示集合的。TCL 中 list 是由一堆元素组成的有序集合,list 可以嵌套定义,list 每个元素可以是任意字符串,也可以是 list。下面都是 TCL 中的合法的 list:
{} // 空 list
{a b c d}
{a {b c} d} //list 可以嵌套
2.8.1 创建列表
set listName { item1 item2 item3 .. itemn }
# or
set listName [list item1 item2 item3]
# or
set listName [split "items separated by a character" split_character]
2.8.2 列表方法
concat listN1 listN2...
合并多个 list
lindex list index
返回 list 的第 index 个 (0-based) 元素
llength list
返回 list 的元素个数
linsert list index value1 value2...
插入新的元素
lreplace list first last value1 value2 ...
替换成新的元素
lrange list first last
切片
lappend varname value1 value2...
附加新元素
lsearch -exact -glob -regexp list pattern
返回匹配到的索引
- -exact、-glob、 -regexp 是三种模式匹配的技术。
- -exact 表示精确匹配;-glob 的匹配方式和 string match 命令的匹配方式相同;-regexp 表示正规表达式匹配
lsort options list
排序
- -ascii 按 ASCII 字符的顺序排序比较,默认方式
- -dictionary 按字典排序,与 -ascii 不同的地方是不考虑大小写,如果元素中有数字的话, 数字被当作整数来排序
- -integer 把 list 的元素转换成整数, 按整数排序.
- -real 把 list 的元素转换成浮点数, 按浮点数排序.
- -increasing 升序(按 ASCII 字符比较)
- -decreasing 降序(按 ASCII 字符比较)
- -command command TCL 自动利用 command 命令把每两个元素一一比较, 然后给出排序
结果。
split string splitChars
分割字符串成 list
- 如果 splitChars 没有给出, 以空格为分隔符
join list joinString
合并 list 为字符串
2.9 控制流
TCL 中的控制流和 C 语言类似,包括 if、while、for、foreach、switch、break、continue 等命令。
2.9.1 if
语法为:
if { $x>0 } {
.....
}elseif{ $x==1 } {
.....
}elseif { $x==2 } {
....
}else{
.....
}
注意,上例中’{‘一定要写在上一行,因为如果不这样,TCL 解释器会认为 if 命令在换行符处已结束,下一行会被当成新的命令,从而导致错误的结果。
2.9.2 for
语法为:
#!/bin/tclsh
for { set i 0} {$i < 10 } {incr i} {
puts "value of a: $a"
}
2.9.3 foreach
语法为:
#!/bin/tclsh
set values {0 1 2 3 4 5}
foreach item $values {
puts $item
}
还有另一种方式:成对取值
set x {}
foreach {i j} {a b c d e f} {
lappend x $j $i
}
#这时总共有三次循环,x 的值为 "b a d c f e"。
set x {}
foreach i {a b c} j {d e f g} {
lappend x $i $j
}
#这时总共有四次循环,x 的值为 "a d b e c f {} g"。
set x {}
foreach i {a b c} {j k} {d e f g} {
lappend x $i $j $k
}
#这时总共有三次循环,x 的值为 "a d e b f g c {} {}"。
2.9.4 while
语法为:
while {$i > 0} {
...
}
在循环体中,可以用 break 和 continue 命令中断循环。
其中 break 命令结束整个循环过程, 并从循环中跳出
continue 只是结束本次循环。
2.9.5 switch
switch 命令的语法为:
switch options string {pattern body pattern body …}
第一个是可选参数 options,表示进行匹配的方式。TCL 支持三种匹配方式:
-glob 方式的匹配方式和 string match 命令的匹配方式相同,缺省情况表示 -glob 方式。
-exact 方式表示的是精确匹配
-regexp 方式是正规表达式的
第二个参数 string 是要被用来作测试的值,第三个参数是括起来的一个或多个元素对。
例子:
switch $grade {
A { puts "Well done!" }
B { puts "Excellent!" }
C { puts "You passed!" }
F { puts "Better try again" }
default { puts "Invalid grade" }
}
2.10 过程 procedure
TCL 支持过程的定义和调用,在 TCL 中,过程可以看作是用 TCL 脚本实现的命令,效果与 TCL 的固有命令相似。我们可以在任何时候使用 proc 命令定义自己的过程,TCL 中的过程类似于 C 中的函数。
语法为:
proc name { 形参 1 形参 2 ...} {
...
}
举个例子:
proc add {x y } {
expr $x+$y
}
proc abs {x} {
if {$x >= 0} { return $x }
return [expr -$x]
}
proc add {val1 {val2 2} {val3 3}}{
expr $val1+$val2+$val3
}
#给 val2 和 val3 指定默认值
2.11 字典
2.11.1 字典定义
字典是将值映射到键的一种安排。 传统字典的语法如下所示:
dict set dictname key value
或者:
dict create dictname key1 value1 key2 value2 .. keyn valuen
举例说明:
set colours [dict create colour1 "black" colour2 "white"]
puts $colours
dict set colours colour3 red
dict set colours colour4 green
puts $colours
2.11.2 字典的使用
获取字典键值对个数:[dict size dictname]
字典中的所有键:[dict keys $dictname]
字典中是否存在键:[dict exists $dictname $key]
根据键获取值:[dict get $dictname $keyname]
字典中的所有值:[dict values $dictname]
字典的迭代:
set colours [dict create colour1 "black" colour2 "white"]
foreach item [dict keys $colours] {
set value [dict get $colours $item]
puts $value
}
2.12 全局变量和局部变量
对于在过程中定义的变量,因为它们只能在过程中被访问,并且当过程退出时会被自动删除,所以称为局部变量;在所有过程之外定义的变量我们称之为全局变量。TCL 中,局部变量和全局变量可以同名,两者的作用域的交集为空:局部变量的作用域是它所在的过程的内部;全局变量的作用域则不包括所有过程的内部。
如果我们想在过程内部引用一个全局变量的值,可以使用 global 命令
proc sample { x } {
global a
incr a
return [expr $a+$x]
}
全局变量 a 在过程中被访问。在过程中对 a 的改变会直接反映到全局上。如果去掉语句 global a,TCL 会出错,因为它不认识变量 a。
2.13 一些常见函数
2.13.1 系统函数
Tcl 中重要的系统函数包括,
- clock seconds − 秒函数,返回当前时间(以秒为单位)。
- clock format − format 函数,将秒格式化为日期和时间。
- clock scan − scan 函数,扫描输入字符串并将其转换为秒。
- open − 函数,用于打开文件。
- exec − 函数,用于执行系统命令。
- close − 函数,用于关闭文件。
下面给些示例:
#!/usr/bin/tclsh
#get seconds
set currentTime [clock seconds]
puts $currentTime
#get format
puts "The time is: [clock format $currentTime -format %H:%M:%S]"
puts "The date is: [clock format $currentTime -format %D]"
set date "Jun 15, 2014"
puts [clock scan $date -format {%b %d, %Y}]
puts [exec ls]
puts [exec dir]
set a [open input.txt]
puts [read $a];
puts $a
close $a
2.13.2 其他常见函数
- file 命令,用于处理文件路径,属性和操作的核心命令,支持跨平台
file join path1 path2...
路径拼接,自动适配系统分隔符file split path
将路径拆分为列表file normalize path
转换路径为绝对路径file dirname path
提取路径的目录部分file tail path
提取路径中的文件名或最后一级目录名file extension path
提取路径扩展名file rootname path
提取路径中的文件名,不含扩展file exists path
检查目录是否存在file isdirectory path
检查是否为目录file isfile path
检查是否为普通文件(非目录或符号链接)file size path
返回文件大小(字节)file mtime path
返回文件最后修改时间file copy path
返回文件类型file delete path
删除文件file rename path
重命名文件
2.14 面向对象编程
tcl 支持面向对象编程(OOP),主要通过扩展库实现。它类似于 C++ 对 C 的扩展,为 Tcl 提供了更结构化的编程方式,同时保持了 Tcl 的灵活性和简洁性。
类和对象 是 Tcl 面向对象编程的基础。
类是对象的模板,定义了变量和方法。对象是类的实例,拥有独立的数据和行为。
以下是一个简单的类定义示例:
package require Itcl
namespace import itcl::*
itcl::class Person {
variable name
variable age
constructor {name age} {
set this->name $name
set this->age $age
}
method introduce {} {
return "Hi, I am $name and I am $age years old."
}
}
# 创建对象并调用方法
set person [Person new "Alice" 25]
puts [$person introduce]
Tcl 的 OOP 还支持以下特性:
- 继承:通过 inherit 关键字实现类的继承,子类可以复用父类的变量和方法。
- 多态:子类可以重写父类的方法,实现不同的行为。
- 封装:支持 public、protected 和 private 修饰符,用于控制变量和方法的访问权限。
Tcl 使用命名空间(namespace)来组织代码,避免命名冲突。命名空间可以包含变量、过程和类。
这里不做更多介绍,熟悉 c/c++ 语言的朋友(正是笔者在下!)对 tcl 的类应该会举一反三,这些特性非常相似。而且一般 tcl 中我们也用不到继承多态封装的一些高级技术。
欢迎各位看官及技术大佬前来交流指导呀,可以邮件至 jqiange@yeah.net