TCL 是 Tool Command Language 的缩写,是一种以字符串(sting)为基础的程序语言。
基本语法
TCL 脚本(或程序)是由一连串命令构成,每条命令的基本结构为:
Command argument1 argument2 ...
每一个命令包含一个或几个单词,第一个单词代表命令名,另外的单词则是这个命令的参数,单词之间必须用空格或TAB键隔开
TCL脚本的不同命令之间必须用换行符或分号隔开,如下所示的两种情况都是正确的。
set a 1
set b 2
# or
set a 1; set b 2
TCL解释器对一个命令的求值过程分为两部分:分析和执行。
在分析阶段,TCL 解释器运用规则把命令分成一个个独立的单词,同时进行必要的置换(substitution);
在执行阶段,TCL 解释器会把第一个单词当作命令名,并查看这个命令是否有定义,如果有定义就激活这个命令对应的C/C++过程,并把所有的单词作为参数传递给该命令过程,让命令过程进行处理。
注释
注释以 # 开始,一般是单独成行,如果想要放在行内,需要用分号隔开。
# This is a comment.
# or
set a 100; # This is a comment.
脚本执行
对于 EDA 工具而言,因为工具本身的用的就是 TCL,所以直接 source 就可以。
如果是自己在 terimal 直接执行 TCL 脚本,需要做如下几件事:
① 在 tcl 脚本的第一行添加 #!/usr/bin/tclsh,指定 TCL 的解释器;
② tclsh test.tcl 来执行脚本
置换(Substitution)
TCL 解释器在分析命令时,把所有的命令参数都当作字符串看待。如下示例:
% set x 10
10
% set y x+100
x+100
在上述第二个命令中,x 被看作字符串 x+100 的一部分。
如果我们想使用 x 的值 “10” ,就必须告诉TCL解释器:我们在这里期望的是变量 x 的值,而非字符 “x” 。
怎么告诉 TCL 解释器呢,这就要用到 TCL 语言中提供的置换功能。
TCL提供三种形式的置换:变量置换、命令置换和反斜杠置换。
每种置换都会导致一个或多个单词本身被其他的值所代替。置换可以发生在包括命令名在内的每一个单词中,而且置换可以嵌套。
变量置换
变量置换由一个 $ 符号标记,变量置换会导致变量的值插入一个单词中。例如:
% set y $x+100
10+100 # x 被置换为它的值10
y 的值还不是我们想要的值110,而是10+100,因为TCL解释器把10+100看成是一个字符串而不是表达式.
y 要想得到值110,还必须用命令置换,使得TCL会把 10+100 看成一个表达式并求值。
命令置换
命令置换是由[]括起来的 TCL 命令及其参数,命令置换会使某一个命令的所有或部分单词被另一个命令的结果所代替。例如
% set y [expr $x+100]
110
此时,y 的值是110。
这里当TCL解释器遇到字符 [ 时,它就会把随后的expr作为一个命令名,从而激活与 expr 对应的 C/C++ 过程,并把expr和变量置换后得到的 10+110 传递给该命令过程进行处理。
如果在上例中我们去掉[],那么 TCL 会报错。因为在正常情况下,TCL 解释器只把命令行中的第一个单词作为看作命令,其他的单词都作为普通字符串处理,看作是命令的参数。
[]中必须是一个合法的TCL脚本,长度不限。[]中脚本的值为最后一个命令的返回值。例如:
% set y [expr $x+100;set b 300]
300
这里,y 的值是 300。因为 set b 300 的返回值是 300。
有了命令置换,实际上就表示命令之间是可以嵌套的,即一个命令的结果可以作为别的命令的参数。
反斜杠置换
TCL语言中的反斜杠置换类似于 C 语言中反斜杠的用法,主要用于在单词符号中插入诸如换行,空格,[,$等被 TCL 解释器当作特殊符号对待的字符(转义)。
\也可以把普通的字母转义成特殊字符,如 \n 表示的就是换行。
例如:
% set msg multiple\ space
multiple space
% set msg money\ \$333\ \nArray\ a\[2]
money $333
Array a[2
对于第一个示例,如果没有 \ 的话,TCL 会报错,因为解释器会把这里最后两个单词之间的空格认为是分隔符,于是发现set 命令有多于两个参数,从而报错。加入了\后,空格不被当作分隔符,multiple space被认为是一个单词(word)。
对于第二个示例,需要注意的是[是特殊符号(需要置换),而]并不是特殊符号,不需要置换。
此外,\ 还有续行的功能,如下所示
% set a [expr \
2+3]
5
需要注意的是,用于续行的 \ 后面不要有空格(这是一个容易出错的地方)。
双引号和花括号
除了使用\外,TCL提供另外两种方法来使得解释器把特殊字符当作普通字符,而不作特殊处理,这就要使用双引号和花括号({})。
TCL解释器对双引号中的各种分隔符将不作处理,但是对换行符 以及 $和[]两种置换符会照常处理。例如:
% set x 100
100
% set 7 "$x ddd"
100 ddd
而在花括号中,所有特殊字符都将成为普通字符,失去其特殊意义,TCL 解释器不会对其作特殊处理。
% set y {/n$x [expr 10+100]}
/n$x [expr 10+100]
示例2:
% set a 1.2
% set y 1.3
% puts "[expr $x+$y]"
2.5
% puts "\[expr $x+$y\]"
[expr 1.2+1.3]; # [ 不再作为置换符,同时tcl也不再识别expr,被直接打印出来
% puts "\[expr \$x+\$y\]"
[expr $x+$y]; # [ 不再作为命令置换符,$也不再是变量置换符,被直接打印出来
##
% puts {\t[expr $x + $y] }
\t[expr $x + $y]
变量
简单变量
一个 TCL 的简单变量包含两个部分:名字和值。
名字和值都可以是任意的字符串。例如一个名为1323 7&*: hdgg的变量在TCL中都是合法的。
不过为了更好的使用置换,变量名最好按 C\C++ 语言中标识符的命名规则命名。
TCL解释器在分析一个变量置换时,只把从$符号往后直到第一个不是字母、数字或下划线的字符之间的单词符号作为要被置换的变量的名字。例如:
% set a 2
2
% set a.1 4
4
% set b $a.1
2.1
在最后一个命令行,我们希望把变量a.1的值赋给b,但是 TCL 解释器在分析时只把 $ 符号之后直到第一个不是字母、数字或下划线的字符(这里是.)之间的单词符号(这里是a)当作要被置换的变量的名字,所以TCL解释器把 a 置换成 2,然后把字符串 “2.1” 付给变量 b。这显然与我们的初衷不同。
如果变量中有不是字母、数字或下划线的字符,又要用到置换,可以用花括号{}把变量名括起来。例如:
% set b ${a.1}
4
TCL 中的 set 命令能生成一个变量,也能读取或者改变一个变量的值。例如:
% set a {kdfj kjdf}
kdfj kjdf
如果变量 a 还没有定义,就生成变量 a,并将其值设置为 kdfj kjdf;如果 a 已经定义,就简单的把 a 的值设置为 kdfj kjdf。
只有一个参数的 set 命令就是读取变量当前的值。如下所示:
% set a
kdfj kjdf
数组(字典)
数组是一些元素的集合。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)
1
% set c $day($a)
1
数组的遍历
使用 foreach 对数字进行遍历:
array set myArray {a 1 b 2 c 3}
foreach {key value} [array get myArray] {
puts "$key: $value"
}
只对数组的键值进行遍历:
array set myArray {a 1 b 2 c 3}
foreach key [array names myArray] {
puts "$key: [array get myArray $key]"
}
变量相关的命令:unset、append、incr
unset, append, incr 是和变量相关的命令,具体功能如下所述。
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
append 和 incr
这两个命令提供了改变变量的值的简单手段。
append 命令把文本加到一个变量的后面,例如:
% set text hello
hello
% append text "! How are you"
hello! How are you
% set var hello
hello
% set var_1 world
world
% append var1 $var_1
helloworld
incr命令是把一个变量的值加上一个整数(数值运算,所以要求变量原来的值和新加的值都必须是整数)。
% set b a
a
% $ incr b
expected integer but got "a"
% set b 2
2
% incr b 3
5
% incr b
6
% incr b -1
5
Notes
-
这两个命令中使用变量的时候,都不能使用
$。示例1:

示例2:

调用系统环境变量
TCL 中可以使用 env 来调用系统环境变量,其基本用法如下:
set target_name $::env(TARGET_NAME)
其中,
env 是 TCL 预定义的一个全局数组 env,用于访问操作系统的环境变量。例如,$env(PATH) 表示系统的 PATH 环境变量,$env(HOME) 表示当前用户的主目录。
:: 表示全局命名空间。在 TCL 中,所有预定义的全局变量(如 env)都位于全局命名空间。因此,$env(TARGET_NAME) 直接访问全局的 env 数组,$::env(TARGET_NAME) 是更明确的写法,强调 env 位于全局命名空间。
info - 判断变量是否存在
TCL 中的 info 命令可以用来判断一个变量是否存在:
if {[info exists someVar]} {
puts "变量${someVar}存在"
} else {
puts "变量${someVar}不存在"
}
在使用 info 命令的时候,直接使用变量名,不能使用 ${} 进行变量置换,可以使用 ""。

表达式和运算符
操作数
TCL 表达式的操作数通常是整数或实数。整数一般是十进制的, 但如果整数的第一个字符是0(zero),那么 TCL将把这个整数看作八进制的,如果前两个字符是0x则这个整数被看作是十六进制的。
运算符和优先级
| 运算符 | 说明 |
|---|---|
+, -, * | 加减乘 |
~, ! | 比特反;逻辑非 |
/ ,% | 取整(除);取余 |
<< , >> | 左移、右移 |
<, <=, >, => | 小于、小于等于、大于 、大于等于(布尔) |
==,!= | 等于、不等于 |
&, ^, | | 按位与、异或(不同为1)、或 |
&&, || | 逻辑与、逻辑或 |
x ? y : z | 三目运算符,x 为 ture 值为 y,否则为 z |
数学函数
TCL支持常用的数学函数,表达式中数学函数的写法类似于 C/C++ :语言的写法,数学函数的参数可以是任意表达式,多个参数之间用逗号隔开。
% set x 2
2
% expr 2*sin($x<3)
1.68 # sin1 = 0.84
expr的语法为:expr arg ?arg arg …? 。 其中两个问号之间的参数可以省略。
expr的作用是把所有的参数组合到一起,将其作为一个表达式,然后求值。
TCL 中支持的数学函数有很多,如三角函数等。(具体内容用到的时候再查阅。)
List
list 这个概念在TCL中是用来表示集合的。
TCL中list是由一堆元素组成的有序集合,list可以嵌套定义,list每个元素可以是任意字符串,也可以是list。简单来说就是以空格为间隔的一串元素,例如:
{} # 空的 list
{a b c d}
{a {b c} d} # list 嵌套
List 的构建
将一个变量设置为 list 的方法如下所示:
% set color "red green blue"
% set a [list a b c]
# a = {a b c}
list 是 TCL 中比较重要的一种数据结构,对于编写复杂的脚本有很大的帮助,TCL 提供了很多基本命令对 list 进行操作,一些常用的操作如下所述。
List command
对 list 进行操作(编辑、查看等)的相关命令。
常用的命令如下所示:
| 命令 | 作用 |
|---|---|
concat | 合并 list |
lindex | 获取 list 中的某个元素 |
llength | 获取 list 中元素的个数 |
linsert | 在 list 的指定位置插入新的元素 |
lrepalce | 替换 list 中的一个或者多个连续的元素 |
lrange | 返回列表中的一个或者多个元素 |
lappend | 在 list 中插入新的元素 |
lsearch | 查找某个元素是否存在于 list 中 |
lsort | 排序 |
spilt | 将字符串分割成 list |
join | 将 list 合并成字符串 |
Notes:
- 这些命令就相当于是函数,所以需要一个变量来接收 " 返回值 ",如下所示:
# tcl script
set a {0 1 2 3}
set b [lreplace ${a} 0 0]
puts "a = ${a}"
puts "b = ${b}"
# run result
a = {0 1 2 3}
b = {1 2 3}
concat
语法
concat list ?list ...?
作用:
把多个 list 合成一个 list,每个 list 变成新 list 的一个元素
示例

lindex
语法:
lindex list index
作用:
返回 list 的第 index 个元素。
list 中元素的编号从 0 开始,最后一个元素可以用 end 作为 index,可以使用过 end-N 作为倒数第 N 个元素的 index。
示例1:
% lindex {1 2 {3 4}} 2
3 4
示例2:
lindex 命令中使用变量的时候,需要使用 $。

llength
返回 list 中元素的个数。
% llength {1 2 {3 4}}
3
linsert
在 list 的指定位置插入元素。
linsert list index value ?value...?
返回一个新串,新串是把所有的 value 参数值插入 list 的第 index 个 (0-based) 元素之前得到。
示例:
% linsert {1 2 {3 4}} 1 7 8 {9 10}
1 7 8 {9 10} 2 {3 4}
lreplace
替换或删除 list 中的一个或者多一个连续的元素
lreplace list first last ?value value ...?
返回一个新串,新串是把 list 的第 first (0-based) 到第 last (0-based) 个元素用所有的 value参数替换得到的。
如果没有 value 参数,就表示删除第 first 到第 last 个元素。
如果只想对一个元素进行操作,把 first 和 last 设置为相同的值。
示例1:
# 删除 index 为 3 的 {9 10}
% lreplace {1 7 8 {9 10} 2 {3 4}} 3 3
1 7 8 2 {3 4}
# 替换
% lreplace {1 7 8 2 {3 4}} 4 4 4 5 6
1 7 8 2 4 5 6
示例2: 按名称删除指定元素的方法如下:
set mylist {a b c}
puts $mylist
# get index of specific element
set idx [lsearch $mylist "b"]
# delete
set mylist [lreplace $mylist $idx $idx]
puts $mylis
此外,可以使用通配符*,删除包含某些字符串的元素。
lrange
返回 list 的第 first (0-based)到第 last (0-based)元素,返回值也是 list,可以用 list 的命令继续对其进行操作。
lrange list first last
示例:

lappend
lappend varname value ?value...?
将每一个 value 作为一个新的元素附加到 varname 后面。
如果 varname 这个变量不存在,lappend 会创建一个新的变量。
① varname 可以是 list,也可以是普通变量。
list:

普通变量:

② lappend 中使用变量的时候,不能使用 $

lsearch
lsearch ?-exact? ?-glob? ?-regexp? list pattern
返回 list 中第一个和 pattern 匹配的元素的索引,如果找不到匹配就返回 -1。
-exact、-glob、-regexp 是三种匹配方式。
示例
% set a { how are you }
how are you
% lsearch $a y*
2
% lsearch $a y?
-1
lsearch 命令会被用来判断 list 中是否存在某个元素,使用的时候需要注意:在 TCL 的布尔上下文中,-1 是真值(非零即真)。
所以,下图所示的这种写法是有问题的。

问题1:lsearch 找不到时返回 -1,即使 $mem_type 不在列表里,条件也会成立。
问题2:如果
m
e
m
t
y
p
e
是第一个元素,
‘
l
s
e
a
r
c
h
‘
的返回值是
0
,也是说,
mem_type 是第一个元素,`lsearch` 的返回值是 0,也是说,
memtype是第一个元素,‘lsearch‘的返回值是0,也是说,mem_type 在列表里,但是条件不成立
正确的写法应该是用 >= 0 或者 != -1 来判断:

lsort
lsort ?options? list
list 排序后的串,options 的部分值如下:
- -ascii:默认值,按 ASCII 字符的顺序排序
- increasing:按 ASCII 字符升序
- decreasing:按 ASCII 字符降序
- dictionary:按照字典顺序排序
split
split string ?splitChars?
把字符串 string 按分隔符 splitChars 分成一个个单词,返回由这些单词组成的 list。
如果 splitChars 没有给出,默认以空格为分隔符。
如果 splitChars 是一个空字符{},string 被按字符分开;
示例:
% split "how.are.you" .
how are you
% split "how are you"
how are you
% split "how are you" {}
h o w { } a r e { } y o u
join
join list ?joinString?
join 命令是 split 命令的逆,该命令把 list 的所有元素合并到一个字符串中,中间以 joinString 分开。joinString 的默认值是空格。
示例
% join {h o w { } a r e { } y o u} {}
how are you
# 合并的时候,元素之间没有空格,最后"how are you"中的空格是lsit中自带的
% join {how are you} .
how.are.you
list 的遍历
① foreach 直接遍历
set list {a b c d e}
foreach var ${list} {
puts ${var}
}
② foreach 和 lrange 结合遍历 list
set list {a b c d e}
# 遍历 index 范围为 1-3 的元素
foreach var [lrange ${list} 1 3] {
puts ${var}
}
删除重复元素
set uniqueList [lsort -unique ${myList}]
dict
在 Tcl 语言中,字典 dict 是一种无序的 “ 键(Key) - 值(Value) ” 对 集合,也被称为关联数组或者哈希表。它可以存储键值对,其中键是唯一的,值可以是任意 Tcl 数据类型。字典可以通过键来访问对应的值,因此它非常适合存储和处理大量数据。
dict 的创建
set my_dict [dict create key1 value1 key2 value2 key3 value3]
#
set dict1 {key1 value1 key2 value2} ;# 用花括号创建空字典
set dict2 [dict create] ;# 用dict create创建空字典
set dict3 [dict create Name "Tom" Age "18" Gender "male"] ;# 用dict create创建具有初始键值对的字典
查询
# Create a Dict
set dict [dict create name 张三 age 十岁 gender male]
# 取键为name的值并打印
puts [dict get $dict name]
修改
修改 dict 中的 keys 对应的 values 的方法有两种:
# Method 1
dict set dict_name key value
# Method 2
dict replace $dict_name key value
这两个命令都需要三个参数:字典、keys、新的 value。
dict set是直接更改了原始的字典,而 dict replace 则是产生一个新的字典。
所以,可以有如下用法:
set mydict [dict create name 张三 age 十岁 gender male]
# 将dict replace产生的新字典存储在变量newdict中
set newdict [dict replace $mydict gender female]
# 获取newdict中键为gender的值并打印
puts [dict get $newdict gender]
当 dict 为空的时候时候,也可以使用 dict set 命令来为其设定 keys 和 values。
获取所有的 keys 和 values
dict kyes 和 dict value 可以用于获取 dict 中的所有的 keys 或 values。
set exampel [dict create]
dict set exmple name Tom
dict set exmple age 18
dict set exmple sex male
##
puts $example
puts [dict keys $example]
puts [dict value $example]
# 还可以与指定模式的string match匹配情况进行筛选
puts [dict keys $example {*e}]
遍历
使用 dict for 命令可以对 dict 进行遍历,其基本语法为:
dict for {key value} $dictname { ... }
示例:
set dict {a 1 b 2 c 3 d 8 e 20}
dict for {key value} $dict {
puts "Key:$key, Value:$value"
if {$value % 2 == 0} {
dict set dict $keys [expr {$value ** 2}]
}
}
put $dict
执行后的结果如下:

控制流
TCL 中的控制流和C语言类似,包括if、while、for、foreach、switch、break、continue 等命令。
if 命令
语法:
if cond1 body1 ?elseif cond2 body2 elseif ...? ? else bodyn?
示例:
if { $x > 0 } {
...
} elseif { $x == 1 } {
...
} elseif { $x == 2 } {
...
} else {
...
}
Notes
if、elseif、else和其后面的中括号之间要有一个空格,否则 TCL 解释器会把if{作为一个整体当作命令名- body 中的
{一定要写在上一行,否则 TCL 解释器会认为if命令在换行符处已经结束,下一行会被当成新的命令 }和{之间一定要有空白符
while 循环
语法:
while { bool } { body }
Notes:
- while和中括号之间有一个空格,因为 tcl 把第一个单词当作命令名称
示例1:
# calculate the sum if all even postive
set a 0
set sum 0
while {$a <= 10} {
set sum [expr ${sum}+${a}]
set a [expr $a + 2] # incr a +2
}
示例2:假设 a 是一个 list,把 a 的值复制到 b
set b ""
set i [expr [llength $a]-1]
while { $i >= 0 } {
lappend b [lindex $a $i]
incr i -1
}
for 命令
语法:
for init bool step body
init 是设定 for 循环的初始值;bool 是布尔表达式;step 是执行 body 指令之后,下一次循环前的动作。
# Example 1 Summary
for {set a 0;set sum 0} {$a<=10} {incr a +2} {
set sum [expr $sum+$a]
}
# Example 2 Copy
set b ""
for {set i [expr [llength $a]-1]} {$i>=0} {incr i -1} {
lappend b [lindex $a $i]
}
foreach 命令
foreach命令是作用于 List 中的所有元素,有两种语法形式,如下所述。
语法1:
foreach varName list body
varName 用来表示来自 List 中的元素,以代入 body 中执行,每个元素都会执行一次。
set b ""
foreach i $a {
set b [linsert $b 0 $i]
}
语法2:
foreach varlist1 list1 ?varlist2 list2 ...? body
第一个参数 varlist1 是一个循环变量列表,第二个参数是一个列表 list1,varlist1 中的变量会分别取 list1 中的值。
示例1:
set x {}
foreach {i j} {a b c d e f} {
lapped $x $j $i
}
总共进行了三次循环,x 的值为 “b a d c f e”
示例2:
set x {}
foreach i {a b c} j {d e f g} {
lappend x $i $j
}
总共进行了四次循环,x 的值为 “a d b e c f {} g”。因为第一个 list 中只有三个元素,所以第四次训话循环的时候就是空的 list。
示例3:
set x {}
foreach i {a b c} {j k} {d e f g} {
lappedn x $i $j $k
}
# x is {a d e b f g c {} {}}
break 和 continue
在循环体中,break 是结束整个循环过程,并从循环中跳出;continue只是结束本次循环,开始下一次的循环。
如果是在循环嵌套中,break 和 continue 都是作用于其所在的循环语句中。
此外,error 命令可以使执行中的 TCL 程序终止,并发出错误信号;exit 命令可以直接退出 TCL 程序。
Procedure
TCL 支持过程(即其他编程语言中的函数)的定义和调用,如下所示:
% proc add {x y} {expr $x+$y}
proc 命令的第一个参数是所定义的 procedure 的名称,第二个参数是 procedure 的参数列表,参数之间用空格隔开,第三个参数是一个 TCL 脚本,代表过程体。 proc 相当于是创建一个新的命令,可以像固有命令一样调用:
% add 1 2
3
在定义 proc 时,可以使用 return 命令在任何地方返回想要的值。 return 命令直接中断 proc,并把它的参数作为 procedure 的结果。如下所示:
proc abs {x} {
if {$x > 0 } { return $x }
return [expr -$x]
}
在 TCL 中,如果没有显式使用 return 命令指定返回值,proc 会默认返回最后执行的命令的结果。如果 proc 中没有任何命令,或者最后一条命令没有产生返回值,proc 会返回一个空字符串。在 TCL 中,puts 命令用于向标准输出(即终端屏幕)写入信息,但它不影响过程的返回值。
如下示例,不用 return ,仅使用 puts 将内容输出到控制台,proc 返回值为空字符串 。
proc greet {} {
puts "Hello, World!" ;# 这行代码将信息输出到标准输出
}
set value [expr { [greet] + 99}]
puts $value
上述 TCL 在执行的时候会报错,因为空字符不能进行加法操作。

局部变量和全局变量
在 proc 中定义的变量,只能在 proc 中被访问,并且当 proc 退出时会被自动删除,所以称为局部变量;
在 proc 之外定义的变量就称之为全局变量。
如果想要在 proc 的内部引用全局变量,需要使用 global 命令。
# sample.tcl
set a 4
proc sample {x} {
global a
incr a
return [expr $a+$x]
}
# run
$ sample 3
8
$ set a
5
全局变量 a 在 proc 中被访问,在 proc 中对 a 的改变会直接反映到全局上。如果去掉语句 global a,TCL 会出错,因为它不认识变量 a。
缺省参数和可变个数参数
TCL 中可以定义一个没有参数的 proc ,如下所示:
proc add {} { expr 2+3 }
TCL 中还可以定义具有缺省参数值的 proc,即为 proc 的部分或全部参数提供默认值,如果调用 proc 时未提供那些参数的值,那么 proc 会自动使用默认值赋给相应的参数。
# proc
proc add { val1 {val2 2} {val3 3} } {
expr $val1+$val2+$val3
}
# run
add 1 // 值为 6
add 2 20 // 值为 25
add 4 5 6 // 值为 15
此外,TCL 的 proc 定义还支持可变个数的参数,如果 proc 的最后一个参数是 args, 那么就表示这个 proc 支持可变个数的参数。
位于 args 之前的参数和普通参数一样,任何附加的参数都需要在 proc 中进行特殊处理: proc 中会有局部变量,它的名称是 args,类型为列表,元素就是所有附加的变量。
如果没有附加的变量,args 就是一个空列表。
proc add { val1 args } {
set sum $val1
foreach i $args {
incr sum $i
}
return $sum
}
# run
add 2 // 值为 2
add 2 3 4 5 6 // 值为 20
参数传递
在 TCL 语言中,argv 是一个预定义的全局变量,其核心作用是存储从命令行传递给 TCL 脚本的参数列表。
argv 是一个标准的 TCL 列表(list),可通过列表命令操作参数。
当通过命令行执行 TCL 脚本时(例如 tclsh script.tcl arg1 arg2),所有跟在脚本名后的参数(arg1 arg2 …)会被自动存入 argv 列表中。
脚本名本身不包含在 argv 中,它存储在另一个变量 argv0 中。参数的数量保存在 argc 变量中,它等价于 [llength $argv]。
但是,在 EDA 工具中使用 source 命令执行 TCL 脚本时,argv 参数通常不可用。原因如下:
使用命令行执行(tclsh script.tcl arg1 arg2)脚本的时候,会创建新解释器进程,自动初始化 argv。但是,EDA 工具中的 source 是在当前解释器环境中直接加载脚本文件,不会初始化命令行参数变量.
所以,如果之前未定义过 argv,直接访问会导致错误,即使存在 argv,其内容也是当前会话的历史值,并非当前脚本的参数。
两种比较常用的替代方案如下:
方案1:通过变量预先传参
# 在 EDA 工具中先设置参数变量
set script_args [list -mode fast -level 3] ;# 定义参数列表
# 执行脚本(参数通过变量传递)
source script.tcl
# script.tcl 脚本中接收参数
if {[info exists script_args]} {
foreach {key value} $script_args {
puts "参数: $key = $value"
# 根据参数执行操作...
}
}
方案2:封装为带参数的 proc
### tcl
# script.tcl 中定义可调用的函数
proc run_script {args} {
puts "接收参数: $args"
# 脚本核心逻辑放在这里...
}
# 在 EDA 工具中调用
source script.tcl
run_script -mode fast -level 3 ;# 显式传递参数
exec 调用外部命令或程序
Tcl 的 exec 命令用于调用系统子进程或执行外部程序,其基本语法如下:
### tcl
exec ?switches? arg ?arg ...? ?&?
-
switches:可选的开关,用于控制命令行为。必须以
-开头,常见的包括:keepnewline:保留输出结果末尾的换行符(通常会被删除)。ignorestderr:忽略命令写到标准错误的输出,不将其作为错误处理。--:标志开关结束,后续即使有以-开头的参数也视为命令的一部分。 -
arg:构成命令行的一个或多个参数。第一个 arg 通常是程序名,后续的是传递给程序的参数。
-
&:如果最后一个参数是
&,则命令在后台执行
exec 会等待前台命令执行完毕,并默认返回该命令的标准输出(stdout)。在命令末尾加上 &之后, exec 不会等待命令完成,而是立即返回一个包含所有子进程 PID 的列表。
简单示例:
### tcl
# 执行 date 命令,并将输出保存到 result 变量
set result [exec date]
puts "当前日期时间: $result"
# 执行 ls -l 命令
set files [exec ls -l /usr/local/bin]
puts "$files"
# 将参数存储在变量中
set dir "/home/user"
set output [exec ls -l $dir]
# 计算当前目录下 .tcl 文件的数量
set count [exec ls -l | grep "\.tcl$" | wc -l]
puts "Tcl 文件数量: $count"

2250

被折叠的 条评论
为什么被折叠?



