tcl 语言入门

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.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!}

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.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.6 操作数

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.7 列表

list 这个概念在 TCL 中是用来表示集合的。TCL 中 list 是由一堆元素组成的有序集合,list 可以嵌套定义,list 每个元素可以是任意字符串,也可以是 list。下面都是 TCL 中的合法的 list:

{} // 空 list

{a b c d}

{a {b c} d} //list 可以嵌套

2.7.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.7.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.8 控制流

TCL 中的控制流和 C 语言类似,包括 if、while、for、foreach、switch、break、continue 等命令。

2.8.1 if

语法为:

if { $x>0 } {  
.....  
}elseif{ $x==1 } {  
.....  
}elseif { $x==2 } {  
....  
}else{  
.....  
}  

注意,上例中’{‘一定要写在上一行,因为如果不这样,TCL 解释器会认为 if 命令在换行符处已结束,下一行会被当成新的命令,从而导致错误的结果。

2.8.2 for

语法为:

#!/bin/tclsh
for { set i 0} {$i < 10 } {incr i} {
    puts "value of a: $a"
}

2.8.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.8.4 while

语法为:

while {$i > 0} {
...
}

在循环体中,可以用 break 和 continue 命令中断循环。

其中 break 命令结束整个循环过程, 并从循环中跳出

continue 只是结束本次循环。

2.8.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.9 过程 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.10 字典

2.10.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.10.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.11 全局变量和局部变量

对于在过程中定义的变量,因为它们只能在过程中被访问,并且当过程退出时会被自动删除,所以称为局部变量;在所有过程之外定义的变量我们称之为全局变量。TCL 中,局部变量和全局变量可以同名,两者的作用域的交集为空:局部变量的作用域是它所在的过程的内部;全局变量的作用域则不包括所有过程的内部。

如果我们想在过程内部引用一个全局变量的值,可以使用 global 命令

proc sample { x } {  
    global a  
    incr a  
    return [expr $a+$x]  
}  

全局变量 a 在过程中被访问。在过程中对 a 的改变会直接反映到全局上。如果去掉语句 global a,TCL 会出错,因为它不认识变量 a。

2.12 系统函数

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 面向对象编程

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 还支持以下特性:

  1. 继承:通过 inherit 关键字实现类的继承,子类可以复用父类的变量和方法。
  2. 多态:子类可以重写父类的方法,实现不同的行为。
  3. 封装:支持 publicprotectedprivate 修饰符,用于控制变量和方法的访问权限。

Tcl 使用命名空间(namespace)来组织代码,避免命名冲突。命名空间可以包含变量、过程和类。

这里不做更多介绍,熟悉 c/c++ 语言的朋友(正是笔者在下!)对 tcl 的类应该会举一反三,这些特性非常相似。而且一般 tcl 中我们也用不到继承多态封装的一些高级技术。


欢迎各位看官及技术大佬前来交流指导呀,可以邮件至 jqiange@yeah.net