VERDVANA'S BLOG Verdvana

Tcl语法与实例


1 前言

        搞IC的哪能不会Tcl,虽然我也是才学😅。


2 Tcl基础

2.1 简介

        Tcl全称Tool command language,是一种解释性语言,不像其他高级语言需要编译,而像shell语言一样,直接对每条语言顺次解释执行。对它而言,要处理的数据只有一种——字符串,它将变量值以字符串的形式进行存储,不关心它的实际使用类型。

        Tcl的执行是交互式的,Tcl提供了交互式命令界面:tclsh和wish。tclsh只支持Tcl命令,wish支持Tcl和Tk命令。通过交互界面,我们就可以像执行shell命令一样,逐条命令执行,并即时得到执行结果。

2.2 命令格式

        一条Tcl基本语法为:

#命令 参数1 参数2 ...
command arg1 arg2 ... 

        其中command为Tcl内建命令或者Tcl过程,各条命令可以单独一行,也可以在同一行,在同一行时,要用“;”来分割:

command1 arg1; command2 arg2; #comment

        字符串中如果有空格,要用“”或者{}括起来,因为Tcl语句中各命令参数是以空格来分割的,否则,字符串中的单词会被认为多余的参数而出错。

2.3 可执行脚本文件

        编写完一个脚本文件吼,可用source来执行该脚本,但需要先启动tclsh。要编写可执行脚本文件,需要在脚本中加入指定解释器,类似其他脚本比如Perl:

#!/usr/bin/tclsh

3 输出、复制与替换

3.1 puts

        Tcl的输出命令是“puts”,将字符串输出到标准输出channelld:

puts hello;                   # 字符串不需要引号
puts "hello world";           # 有空格需要加引号或花括号
puts -nonewline "hello world";# 此选项不输出回车换行

        需要注意的是,双引号和花括号是有差别的,在处理“替换操作时”,前者允许替换发生,后者可能会阻止替换。

3.2 set&unset

        set为变量定义和赋值命令,不需要指定变量值的类型,因为变量值的类型只有一种:字符串。

        unset命令与set命令作用相反,它取消变量定义,并释放变量所占的内存空间。

        通过“$”符,可以引用变量的值。

        set命令也可以只跟变量而无变量值,若变量已经定义,则返回变量值,效果和puts类似。如果变量不存在,则返回错误信息。

set   a hello;                # 定义变量a并赋值字符串
set   a "hello world";        # 重新赋值
set   a;                      # 输出变量a值,此时不加“$”
puts  $a;                     # 输出变量a值,此时要加“$”
set   b $a;                   # 将变量a的值赋给b
unset a;                      # 释放变量a
puts  $a;                     # 报错“can't read "a": no such variable”

3.3 替换

        “$”可以实现替换,例如3.1节中提到的。Tcl对替换只进行一次解释,对嵌套的“$”不予理睬。

set a x;                      # 定义变量a为x
set b a;                      # 定义变量b为a
puts $$b;                     # 等效为 puts {$a}

        方括号“[]”完成命令替换,将命令括起来,命令执行完后,返回结果。

set b [set a 5];              # 将set a 5的结果赋值给b,即set b 5
set c [expr 5*10];            # 将乘式结果赋给c

        双引号和花括号黄多个单词组成一个参数,也是一种替换操作。双引号可以正常替换,花括号会组织内部的嵌套替换,如puts {$a};如果花括号用作界定符,如过程定义时用作界限过程体时,不阻止替换操作,其他还有if条件语句、循环语句、switch语句和过程声明、数学表达式等。

set s hello
puts "the length of \"$s\" is [string length $s]";  # 打印:the length of "hello" is 5
puts {the length of $s is [string length $s]};      # 打印:the length of $s is [string length $s]

set x 10;
set y 20;
set z [expr {$x+$y}];         # 用花括号来组织算术表达式,不阻止$替换操作
if {$x==10} {puts "x=$x"};    # 在条件语句中,花括号用来界定条件体喝执行体,不阻止内部替换

        反斜杠用来引用特殊字符,跟其他语言里类似。


4 数学表达式

4.1 数学和逻辑运算符

运算符 说明
- + ~ ! 一元减(取负)、一元加、比特反、逻辑非
* / % 乘、除、取余
+ - 加、减
« » 左移、右移
< <= > >= 布尔小于、小于或等于、大于、大于或等于
== != 布尔等、不等
& 比特与
^ 比特异或
| 比特或
&& 逻辑与
|| 逻辑或
x?y:z 三目运算符,跟verilog里一样

4.2 数学函数

函数名 说明 举例
abs() 绝对值  
acos() 反余弦  
asin() 反正弦  
atan() 反正切  
atan2 比值取反正切  
ceil() 返回不小于输入值的整数值  
cos() 余弦  
cosh() 双曲余弦  
double() 转换双精度  
exp() e的幂  
floor()    
fmod 取余(结果为浮点型)  
hypot(x,y) 根据直角三角形两直角边长度计算出斜边长度  
int() 取整  
log() 自然对数  
log10() 以十为底的对数  
pow 幂运算  
rand() 取0到1之间的随机实数  
round() 四舍五入取整  
sin()    
sinh    
sqrt() 求二次根  
srand() 以输入的整数为随机数生成器的种子产生随机数  
tan()    
tanh()    

        例如取绝对值:

set a 4
set b 1
set z [expr abs([expr $b - $a])]

4.3 incr命令

        incr命令根据指定的步长来增加或减少参数的值,默认步长为+1。

set a 5;        # a=5
incr a;         # a=6
incr a -2;      # a=4

5 字符串

5.1 基本命令集

命令 描述
append 将值追加到字符串尾
binary 二进制字符串操作
format 字符串格式化
regexp 正则表达式
regsub 用正则表达式进行字符串模式匹配和替换
scan 字符串分解
string option 字符串操作和命令集
subst 字符代替(替代特殊字符)

5.2 append

        将字符串连接到零一字符串尾部,组成新的字符串。此命令对变量直接修改。

set var1 hello
set var2 world

append var1 $var2;      # $var1的内容为“helloworld”

5.3 format

        和C语言中的printf命令类似,它根据一组格式说明来格式化字符串。他不会改变被操作字符串的内容。

format spec val1 val2

5.4 scan

        scan命令根据格式描述符来解析一个字符串并将对应值赋值给后面的变量,返回成功转换的个数。

5.5 binary

        Tcl8.0之后增加了对二进制字符串的支持。

        根据数据的存储组织形式,可以分为ASCII形式和二进制形式。按ASCII存放时,每个字节存放一个ASCII代码,代表一个数字字符,以二进制存放的时候,将存储数字的二进制值。

5.6 subst

        subst命令在字符串中搜索方括号、美元符号和反斜杠,并对其进行替换操作,而对其他数据不做处理,字符串内部的花括号对这种替换操作不阻止。

subst {a = $a sum=[expr 1+2]};      # 
subst {a = {$a} sum={[expr 1+2]}};  # 

5.7 string

        string命令提供了一些简单的模式匹配机制,而正则表达式则提供了更为复杂、更为强大的模式匹配机制。

5.7.1 string命令列表集

        下表给出了string命令语法格式和说明。

命令 说明
string bytelength str1 返回用于存储字符串的字节数
string compare [-nocase] [-length len] str1 str2 根据字典顺序比较字符串。-nocase选项表示大小写无关。-length选项表示只比较指定长度的开头部分字符。如果字符串相同就返回0;如果str1的顺序比str2考前就返回-1,其他情况返回1
string equal [-nocase] str1 str2 比较字符串,相同返回1,否则返回0
string first str1 str2 返回在str2中str1第一次出现的索引位置,如果没有找到则返回-1
string index str1 var1 返回指定位置的字符,var1是从0开始
string is class [-strict] [-failindex varname] str1 判断字符串类型,如果是指定类型就返回1,有整形,布尔型等。-strict表示不匹配空字符;如果指定了failindex则将string中非class的字符索引赋给varname变量
string last str1 str2 返回str2在str1最后一次出现的位置索引,没有索引就返回-1
string length str1 返回str1中的字符个数
string map [-nocase] chaMap str1 返回根据charMap中输入、输出列表,将str中的字符进行字符映射后而产生的新字符串
string match pattern str1 如果str1匹配pattern就返回1,否则返回0;使用的是通配风格的匹配
string range str1 index1 index2 返回str1中从index1到index2之间的字符串
string repeat str1 count 返回将str1重复count次的字符串
string replace str1 first last [newstr] 将从first开始到last结束的一段字符串替换为newstr字符串,如果newstr没有,则这部分字符串内推将被删除
string tolower str1 [first] [last] 将制定范围的字符串转化为小写格式
string totile str1 [first] [last] 通过将第一个字符替换为Unicode的标题型字符或大写形式,而其余的替换为小写形式的方法,将str1转换为开始字母大写形式,可以从参数指定操作范围
string toupper str1 [first] [last] 将指定范围的字符转化为大写形式
string trim str1 [chars] 从str1两端删除chars中指定的字符,chars默认为空字符
string trimleft str1 [chars] 从str1开头删除chars中指定的字符,chars默认为空字符
string trimright str1 [chars] 从str1结尾删除chars中指定的字符,chars默认为空字符
string wordend str1 index 返回str1中在索引位置index包含字符的单词之后的字符的索引位置
string wordstrat str1 index 返回str1中在索引位置index包含字符的单词中第一个字符的索引位置

5.7.2 字符串比较

        如果用比较运算符“==”、“!”、“<”、“>”,必须用双引号来将字符串值括起来,这样表达式语法分析器才能按照字符串类型来进行识别,然后必须用花括号将整个表达式括起来以阻止主解释器将双引号去掉

if {$x=="ture"} {puts OK}

        然而这样的直接比较还是会带来一些意想不到的问题,比较安全的方法是使用string中的两个比较命令来操作,执行速度也更快

set s1 abc
set s2 abd

if {[string compare $s1 $s2]==0} {
  puts "s1 is same as s2"
} else {
  puts "s1 isn't same as s2"      ;# 打印此信息
}

if {[string equal $1 $2]} {
  puts "s1 is same as s2"
} else {
  puts "s1 isn't same as s2"      ;# 打印此信息
}

        注意命令返回值,compare在不同的情况下返回1,而equal在相等时返回1。

5.7.3 字符串匹配

        string match沿用了各类UNIX shell中所使用的文件名模式匹配机制,下表给出了匹配模式的三种结构:

字符 说明
* 通配符
\? 匹配一个字符
[chars] 匹配chars中的任意一个字符

        为了使结果返回1(匹配),pattern和字符str必须相同,除非使用了匹配字符。

string match a* alpha               ;# 返回1
string match ? XY                   ;# 返回0
string match ?? XY                  ;# 返回1,一个“?”对应一个字符

string match {[ab]*} bell           ;# 返回1
string match {[a-z0-7]} 7           ;# 返回1
string match {[a-c][o-q]?} "apo"    ;# 返回1

string match {*\?} "who are u?"     ;# 返回1
string match *\a "who are u?"       ;# 返回0,“\a”被进行了替换
string match *\\a "who are u?"      ;# 返回1

set pat {[ab]*x}
string match $pat box               ;# 返回1

5.7.4 字符串替换

        string replace可以用新的字符串代替字符串中指定范围内的字符,如果没有指定新字符串,则指定范围内的字符串都会被删除,替换不改变原来字符串变量的值,只是返回更改后的新字符串。

string replace aaaabbbb 1 3 ccc     ;# acccbbbb
string replace aaaabbbb 1 3         ;# abbbb

set a aaabbb
string replace $a 1 2               ;# abbb

5.7.5 字符类别(class)测试

        string is命令用来测试一个字符串是否属于某个特定的类。他对于进行参数输入合法性检查非常有效,例如要确保输入参数是整数,可以这样:

if {![string is intrger $input]} {
  error "Invaild input parameter: $input. Please enter a integer number"  ;# if语句检查输入参数input的值是否为integer,如果不是则报错;error作用与puts类似,但会终止程序
}

        此命令正确时返回1,不正确时返回0。类是按照Unicode字符集定义的,它们要比ASCII编码方式指定范围的字符集更通用,下表列举了这些类。

字符类 说明
alnum 任何字母或数字
print alnum的同义词
alpha 任何字母
digit 任何数字
xdigit 有效的十六进制数字
integer 有效整数
double 有效浮点数
ascii 任何具有7位字符编码的字符
graph 不包含空格字符在内的任何打印字符
lower 全为小写的字符串
upper 全为大写的字符串
punct 任何标点符号
space 空格符、制表符、换行符、回车、垂直制表符、退格符
wordchar 字幕、数字、下划线
true 1,true(不区分大小写)
false 0,false(不区分大小写)
boolean 0,1,true,false(不区分大小写)
contral 字符编码小于32而又不是NULL的字符

5.7.6 字符串映射

        string map命令根据字符映射对字符串进行转换,映射咦输入、输出表的形式表示。凡是字符串中包含有输入序列的地方都使用相应的输出序列替换。输入、输出要成对使用:

string map {f p d l} "food"   ;# 打印pool

        上述命令中{f p d l}为输入、输出表,成对出现,f被p替换,d被l替换。输入和输出项可不止一个字符而且不要求长度相同,与UNIX shell的tr命令比较相似。

string map {f pp d ll oo a} "food"  ;# 打印ppall

6 列表

        列表是具有特殊解释的字符串。

6.1 列表命令集

        列表相关命令:

命令 说明
list arg1 arg2 创建列表
lindex list index 返回列表
llength list 返回列表元素个数
lrange list index1 index2 返回指定范围内(从index1到index2)的元素
lappend list arg1 arg2 将新元素追加到原来的列表list后,组成新的列表
linsert list index arg1 arg2 将新元素插入到list中位于index元素之前的位置上
lreplace list index1 index2 arg1 arg2 替换list中指定范围的元素
lsearch [mode] list value 根据匹配元素模式mode,查找list中与value匹配的元素位置索引,mode一般为-exact、-glob、-regexp,默认为-glob,匹配不到返回-1
losrt [switches] list 根据开关选项对列表进行排序
concat list1 list2 连接多个列表内容组成一个列表
join list joinChars 以joinChars为分隔符将列表中的元素合并在一起
split string splitChars 以splitChars中的字符作为分隔符将字符串分解为列表元素
foreach var list {proc body} 遍历列表各项,逐次将各元素值存入var中并执行proc body,相当于一个循环控制语句

6.2 列表命令

        列表命令用来传来列表,列表可以嵌套,即一个列表可以包含子列表。

set l1 [list sun mon tues]        ;# 列表l1含有三个元素
set l2 [list $l1 wed]             ;# 列表l2含有两个元素:{sun mon tues} wed

set str1 "sun mon tues"
set l3 [list $str1 wed]           ;# 结果与l2一样,可以看出列表是特殊的字符串

set l4 [list "sum mon tues" "wed"];# 当元素是字符串时,会被花括号括起来:{sum mon tues} wed

set l5 [list $4 "thur"]           ;# l5为:{ {sun mon tues} wed } thur

set b 10
set l6 [list {a $b c} d]          ;# 花括号阻止替换:{a $b c} d
set l7 [list "a $b c" d]          ;# l7为:{a 10 c} d

        可以简单理解为:花括号内部代表的是子列表。当用list命令创建列表时,如果元素是单个的词,就不用大括弧括起来,但如果某个元素是以空格分隔的字符串时,就将其看作一个子列表而用花括号括起来。

6.3 concat命令

        concat以空格为分隔符,将多个列表拼装在起义形成新的列表,它和双引号的作用比较相似。

        list和concat都可以完成列表的合并。list和lappend保留每个列表的结构,将每个列表作为一个整体,生成新列表的元素来完成。而concat则要先把各个列表的最外层列表结构去掉,将其中的所有元素取出来作为新列表的元素来完成合并,即新列表的每个元素也是合并前列表的元素,这个区别在后面动态建立Tcl命令的时候显得尤为重要。

set x {x y}
set y "$x 3"        ;# $x被替换后,作为列表结构的花括号被去掉,元素被提出来和3一起走位新列表的元素,y:1 2 3
set y "$x {3}"      ;# y:1 2 {3}
set y [concat $x 3] ;# y:1 2 3
set y [list $x 3]   ;# y:1 2 {3}
set y [list $x {3}] ;# y: {1 2} 3
set y [lappend x 3] ;# y:{1 2} 3

6.4 lappend命令

        用来将新元素追加到列表末尾。

lappend new 1 2     ;# new:1 2
lappend new {3 4} "5" {6} 7 ;# 单个词的原色的双引号和花括号被剥离,new:1 2 {3 4} 5 6 7

6.5 llength命令

        可以得到列表内元素的个数。

set l1 "1 2 3 4 5"        ;# 定义了字符串
set num [llength $l1]     ;# num:5

6.6 lindex

        返回列表中指定位置的特定元素,列表索引从0开始计数。

set x {1 4 5}             ;# 定义字符串
lindex $x 1               ;# 4
lindex $x end             ;# 5
lindex $x end-1           ;# 4

6.7 lrange命令

        返回一个指定区段的列表元素。可以以end或者end-n作为索引(n为正整数)。

lrange {1 2 3 {4 5} 6} 2 end  ;# 3 {4 5} 6

6.8 linsert和lreplace命令

        linsert命令用来将元素插入到一个列表的由索引指定的位置,如果索引为0或更小,则元素就会被添加到最前面,如果索引值大于或等于列表长度,则元素被追加到列表尾部,其他情况元素被添加到指定位置之前。

        lreplace命令将一个指定区段的列表元素替换为新元素,如果没有指定新元素,则这个区域的元素就会被从列表中删除。

        这两个操作不会改变原来列表的内容,只是返回一个新列表。

set x {1 2}
set new [linsert $x 0 he she]   ;# new:he she 1 2
set new [linsert $x end he she] ;# new:1 2 he she
set new [linsert $x 1 he she]   ;# new:1 he she 2
puts $x                         ;# x的值没有变,x:1 2
puts $new                       ;# new:1 he she 2
set y [lreplace $new 1 2 B C]   ;# y:1 B C 2 
set y [lreplace $new 0 0]       ;# y:he she 2
set y [lreplace $new 1 2]       ;# y:1 2

6.9 lsearch命令

        在给定列表中搜索与匹配字符串匹配的元素,成功就返回正确的元素索引,否则返回-1。lsearch支持通配符格式,也可以使用-exact选项将其屏蔽而进行精确匹配。

set l1 [list this is one list]      ;# l1:this is one list
set index [lsearch $l1 l*]          ;# index:3
set index [lsearch -exact $l1 l*]   ;# index:-1
set index [lsearch -exact $l1 list] ;# index:3

set ix is
set index [lsearch $l1 $ix]         ;# index:2

6.10 lsort命令

        实现对列表的排序,排序操作不影响原表,而是返回排序之后的新表。

        排序的方式有多种选择,可以通过-ascii、-dictionary、-integer、-real来指定基本排序类型,然后使用-increasing、-decreasing指定排序方向。默认为-ascii、-increasing。

set list a Z z n100 n200 M p Hl hL m 1 20"
lsort $list ;# 返回:1 20 Hl M Z a hL m n100 n200 p z
lsort -dictionary $list ;# 返回:1 20 a Hl hL M m n100 n200 p Z z

6.11 join与split命令

        join命令接收一个列表,并用指定的分隔符将列表元素整合成一个字符串。

join {1 {2 3} {4 5 6}} :  ;# 1:2 3:4 5 6

        split的作用与join相反,用于分割的字符应该在字符串中存在没否则split因为没有搜索到对应字符而将整个字符串作为唯一列表元素而返回,即返回原字符串。

set str cm8/auto/tools/aries/ASIC/auto_fix.tcl
set s /
set l1 [split $str $s]        ;# cm8 auto tools aries ASIC auto_fix.tcl
set l2 [split $str "/."]      ;# cm8 auto tools aries ASIC auto_fix tcl (可指定多个分隔符)

        split的默认分隔符为空白符,包括空格符、制表符和换行符。如果分隔符在字符串位置开始位置,或者有多个分隔符相连,那么split就会产生空列表元素,用{}表示,分隔符并不被合并。

        若打算将字符串的每个字符都区分开,即将每个字符都分割成列表元素,可以将分隔符指定为空字符串{},这个方法对分析和处理字符串中的每个字符时比较有用。当遇到字符串内含有特殊的字符,如空格符时,split也将其作为一个字符元素处理,为了利于区别器件,用花括号将空格元素括起来。

test

7 数组

        Tcl中的数组和其它高级语言的数组有些不同,Tcl数组元素的索引(或称键值),可以是任意的字符串,且其本身没有多维数组概念。数组的存取速度比列表有优势,数组在内部使用散列表来存储,每个元素存取开销几乎相同,而列表的存取数据花费时间与其长度成正比。

7.1 数组的定义与格式

        数组索引是有圆括号来指定,每个数组元素变量名的格式是“数组名(索引值)”,数组元素使用set命令来定义和赋值:

set arrName(index) value
;# or
array set arrName {index1 value1 index2 value2}

array set arrName ""  ;# 定义空数组

8 文件操作

8.1 文件I/O

        Tcl支持缓存机制的文件I/O操作,一般用getsputs,如果数据量很大,可以用read命令命令将整个文件数据都读出来,然后用split命令分割。

        文件操作命令:

命令 说明
open 打开文件或通道,返回文件描述符fileID
puts 向文件描述符写入字符串
gets 读取一行字符,丢弃换行符
close 关闭文件,缓存内容flush
read 读取剩余的字节并返回字符串
seek 设置读写定位偏移量
tell 放回访问指针偏移量(10进制字符串)
flush 输出通道缓存中的输出数据
eof 检查文件结束

        告辞。