引言
TCL(tool command language) ,一种解释执行的脚本语言。
语法
TCL语法是一些TCL解释器怎样对TCL命令进行分析的规则的集合
- 脚本、命令、单词符号
一个脚本包含一个或多个命令,这些命令之间用换行符或者分号隔开;
一个命令包括一个或几个单词,第一个单词代表命令名,另外的单词是这个命令的参数,单词之间必须用空格或者TAB分隔开;
TCL解释器对一个命令的求值过程分两部分:分析和执行。在分析阶段,TCL解释器运用规则把命令分成一个个独立的单词,同时进行必要的置换;在执行阶段,TCl会把第一个单词当做命令名,并查看这个命令是否有定义,如果有,就激活这个命令对应的C/C++的过程,并把所有单词作为参数传递给该命令,让命令过程进行处理。 - 置换(substitution)
TCL解释器在分析命令时,把所有的命令参数都当做字符串看待。例如:
set x 10;
set y x + 10
如果我们想告诉TCL解释器我们期望用x的值10而不是x这个字符,就需要用到置换:TCL提供三种形式的置换,变量置换、命令置换、反斜杠置换,每种置换都会导致一个或多个单词本身被其他值所代替,置换可以发生在包括命令名的每一个单词中,而且置换可以嵌套。
- 变量置换$(variable substitution)
变量置换导致变量的值插入到一个单词中:
set x 10;
set y $x+10; #y的值为10+10
- 命令置换[ ] (command substitution)
命令置换会导致某一个命令的所有或部分单词被另一个命令的结果所代替。
set x 10;
set y [expr $x + 10];
当TCL解释器遇到字符[时,就会把随后的expr当做一个命令名,从而激活与expr对应的C/C++过程,并把后面的参数传递给该命令过程进行处理。
[ ]中必须是一个合法的TCL脚本,长度不限,[ ]中脚本的值为最后一个命令的返回值,如:
set x 10;
set y [expr $x + 10; set b 100]; #y的值为100,因为 set b 100 的返回值是100
- 反斜杠置换(backslash substitution)
TCL语言中反斜杠类似于C语言中的用法,主要用于在单词中插入诸如换行符、空格、[、$等被TCL解释器特殊对待的字符,如:
set x muti\ space;
如果不加\,会报错,TCL解释器会认为muti和space是set命令的两个参数。
常用反斜杠置换:
转义序列 | 替换为 |
---|---|
\ddd | 八进制 ,转换为对应的ASCII码 |
\xhh | 十六进制 ,转换为对应的ASCII码 |
\newline space | 连接下一行 |
例如:
set x \111; #I
set y \x48; #H
set z [ expr \
2 + 3]; #5
- 双引号和花括号
除了使用反斜杠,TCL提供另外两种方法使得解释器把分隔符和置换符等特殊字符当做普通字符,不作特殊处理:
第一种用双引号:对各种分隔符不作处理,但对换行符以及$ [] 两种置换符照常处理。
第二种用花括号:其中所有字符都不会被特殊处理。
- 注释
#为注释符,但是要注意,#必须出现在TCL解释器期望命令的第一个字符出现。例如:
set a 10 #love 报错:会被当做命令的参数
set a 10 ; #love ;代表一个命令的结束,后面是下一个命令的期望位置
变量
- 简单变量
一个简单的TCL变量包含两个部分,名字和数值,名字和数值都可以是任意字符串。TCL解释器在分析一个变量置换时,只把$后直到第一个不是字母、数字、下划线之前的单词作为要被置换的变量的名字。例如:
set a 5
set a.1 10;
set b $a.1;
puts $b;#5.1
我们想把a.1这个变量的值赋给b,但是TCL解释器在分析置换变量时,只把从置换变量$后到第一个不是字母数字下划线之前的单词作为变量名字,然后.1字符串紧随其后。
如果即要用置换符$又要变量名中含有非数字字母下划线,可以用{} 将变量名括起来:
set a.1 10;
set b ${a.1};
puts $b;#10
- 数组
数组是一些元素的集合 ,TCL的数组和普通计算机语言中的数组有很大的区别,在TCL中,不能单独声明一个数组,数组只能和数组元素一起申明,数组中,数组元素的名字包括两部分,数组名字和元素名字,TCL元素名字(数组下标)可以为任意字符串。例如:
set day(monday) 1;
set day(tusday) 2;
数组元素的置换:
set x $day(monday);
set a tusday;
set y $day($a);
- 相关命令
- set
- unset
从解释器中删除变量,后面可以有任意多的参数,每个参数是一个变量名,可以是简单变量、数组、数组元素。例如:
unset a day(monday) day;
- append 和 incr
append 把一个文本加到变量的后面,改变变量的值。
incr给一个变量的值加上一个整数,要求变量原来的值和新加的值都必须是整数。
表达式
- 操作数
TCL操作数都是整数或实数,如果以0开始,将看作八进制,0x看作十六进制。 - 运算符和优先级
和其他语言一致 - 数学函数
注意数学函数不是命令,只有在表达式中才有意义。
List
list是TCL中表示集合的,TCL中list是由一堆元素组成的有序集合,list可以嵌套定义,list的每个元素可以是任意字符串,也可以是list,下面都是合法的list:
{};
{1 2 3}
{1 2 {1 2} 3};#可以嵌套
TCL提供了很多基本命令对list进行操作。
- list命令
这个命令生成一个list,list的参数就是list中的元素:
list 1 2 3 {1 2};#嵌套定义
- concat命令
concat list list …
把多个list合并为一个list,参数是任意个list - lindex命令
lindex list index
返回第index个(0-based)元素 - llength命令
llength list
返回list中元素个数 - linsert命令
linsert list index value value …
返回一个新串,将所有value参数值插入到list的第index个(0-based)元素之前 - lreplace命令
lreplace list first last value value …
返回一个新串,把从first到last的所有元素用所有的value代替,如果没有value参数,则删除第first到last的元素 - lrange命令
lrange list first last
返回list中第first到last的子串,last值为end表示从first到list尾,例如:
lrange {2 3 4 {1 2}} 1 end;#3 4 {1 2}
- lappend命令
lappend varname value value …
把value作为元素加到变量varname的后面,如果varname不存在,就生成这个变量。 - lsearch命令
lsearch ?-exact?-glob?-regexp? list pattern
返回list中第一个匹配模式pattern的元素的索引,找不到就返回-1,-exact 表示精确匹配,-glob的匹配方式和string match匹配方式相同,-regexp正则匹配,缺省时用-glob匹配。 - lsort命令
lsort options list
返回把list排序后的串,options可以是:
-ascii:按ASCII码排序
-dictionary:与ASCII不同的是:1.不考虑大小写,2.元素中有数字被当做整数排序。
-integer:将元素转换为整数排序
-real:将元素转换为浮点数排序
-increasing:按ASCII升序
-decreasing:按ASCII降序
-command command 利用command把两个元素一一比较,给出排序结果 - split命令
split string splitchar
按照splitchar分隔string,返回这些单词组成的串(list),如果splitchar为空字符{},string被按字符分开,缺省时为空格。 - join命令
join list joinstring
join 是 spilt 命令的逆,把list中所有元素合并到一个string中,中间插入joinstring,缺省为空格。
控制流
if while for foreach switch break continue
- if命令
if {$x < 0}{
...
}elseif{$x == 1}{
...
}else{
}
- 循环命令
- while
set b {};
set a {1 2 3 4 {1 2} 5}
set i 5;
while { $i>=0 } {
lappend b [lindex $a $i];
incr i -1;
}
puts $b;#attention : lappend + varname / lindex + list; while后命令都用空格隔开
- for
set b {};
set a {1 2 3 4 {1 2} 5};
for { set i 5 } { $i >= 0 } { incr i -1 } {
lappend b [lindex $a $i]
}
puts $b;# {} 之间也要用空格隔开
- foreach
set b {};
set a {1 2 3 4 {1 2} 5};
foreach i $a {
lappend b $i;
}
puts $b;
foreach varlist list1 varlist2 list2 …
set x {}
foreach {i j} {a b c d e f} {
lappend x $j $i
}# b a d c f e三次循环
set x {}
foreach i {a b c} j {d e f g} {
lappend x $i $j
}# 四次循环 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
}# 3次循环a d e b f g c {] {}, {j k} 为varlist
- break continue 同c
- switch
switch options string {pattern body pattern body…}
set x 5;
switch $x {
1 -
2 -
5 {incr x}
default {incr x}
}
puts $x
options 有-exact -glob -regexp,pattern后的-表示和下一个模式操作一样。default匹配任意值。
- eval命令
eval命令是一个用来构造和执行 TCL 脚本的命令 - source命令
source命令读一个文件并把这个文件的内容作为一个脚本进行求值
过程
类似C中的函数,定义自己的TCL命令
- 过程的定义和返回值
proc add {x y} {return [expr $x + $y]}
set a [add 1 2];
puts $a
第一个参数是proc名字,第二个参数是proc的参数列表,变量用空格隔开,第三个参数是一个TCL脚本,代表过程体。可以像调用固有命令一样调用proc命令。还可以return
- 局部变量和全局变量
如果我们想在proc内部引用全局变量,可以用global命令:
set b 10;
proc add {x} {
global b;
return [expr $b + $x]
}
set a [add 1];
puts $a
- 缺省参数和可变个数参数
proc add {x {y 2} {z 3}} {
return [expr $x + $y + $z]
}
set a [add 1];
puts $a
args表示吸收其余参数到一个list中,如果没有,args为一个空串。
proc add {x args} {
set sum $x;
foreach i $args {
incr sum $i;
}
return $sum;
}
set a [add 1 2 3];
puts $a
- 引用 upvar
upbar level othervar myvar
upvar命令使得用户可以在proc中对全局变量或者其他过程中的局部变量进行访问,upvar命令的第一个参数othervar是我们希望以引用方式访问的参数名字,第二个参数myvar是这个proc中局部变量的名字,一旦使用upvar把othervar和myvar绑定,在proc中对局部变量的读写就相当于被引用变量othervar的读写。
proc add {x} {
upvar $x b;
return [ incr b ];
}
set a 10;
add a ;
puts $a;# 11 attention : upvar 第一个othervar 需要转义
upvar 2 other x
这个命令使当前过程的调用者的调用者中的变量other,在当前过程中可以用x访问,缺省为1,对比第一个例子。如果要访问全局变量:
upvar #0 other x;#此时不论过程处于调用栈的什么位置,都可以在此过程对全局变量other访问
字符串操作
因为TCL把所有输入都当做字符串,所以TCL提供了较强的字符串操作功能,与之有关的命令有:string format regexp regsub scan.
- format
set name gary;
set age 20;
set out [format "%s is %d years old" $name $age];
puts $out;#gary is 20 years old
- scan
scan命令可以看做是format的逆,功能类似于C中scanf,按format提供的格式分析string字符串,然后把结果保存到varname中,注意除了空格和TAB之外,string和format中字符和%必须匹配:
scan "he is 1 dog" "he is %d dog" a;
puts $a;#scan中a不是引用
- regexp
regexp ?switch exp string ?matchvar ?submatchvar
判断exp是否全部或部分匹配字符串string,匹配返回1,否则返回0;
字符 | 意义 |
---|---|
. | 匹配任意单个字符 |
^ | 匹配行首 |
$ | 匹配行尾 |
\x | 匹配字符x,抑制x的含义 |
[chars] | 匹配字符集合chars中给出的任意字符,如果chars中第一个字符是^,表示不匹配chars中所有字符,chars表示方法支持a-z之类的表示 |
(regexp) | 把regexp作为单项匹配 |
* | 对前面的项进行0次或多次匹配 |
+ | 对前面的项进行1次或多次匹配 |
? | 对前面的项进行0次或1次匹配 |
regexp1 | regexp2 | 匹配1或2中的一项 |
set a 0x1234;
set b [ regexp {0x[0-9a-fA-F]+} $a];
puts $b;# 1
如果string后有matchvar或者submatchvar,第一个matchvar存整个匹配结果,submatchvar分别存子匹配结果。
set a 0x1234apple;
set b [ regexp {(0x[0-9]+)([a-z]+)} $a x y z];
puts "$x $y $z";#0x1234apple 0x1234 apple
regexp 还可以设置开关:switchs 控制匹配结果
-nocase 不考虑大小写
-indices 改变变量的值,使各变量的值变成对应匹配子串在整个字符串中的索引:
set a "~ 0x1234apple";
set b [ regexp -indices {(0x[0-9]+)([a-z]+)} $a x y z];
puts "$x $y $z";#2 12 2 7 8 12
-about 返回正则表达式本身的信息,而不是对缓冲区的解析,返回一个list,第一个是子表达式个数,第二个存放子表达式的信息。
set a "~ 0x1234apple";
set b [ regexp -about {(0x[0-9]+)([a-z]+)} $a x];
puts "$b";#2 REG_UUNPORT
-expanded 启用扩展规则,将空格和注释忽略,相当于内嵌语法(?x)
-line 启用行敏感匹配,正常情况下^和$只能匹配缓冲区起始和末尾,对于缓冲区内部新的行是不能匹配的,通过这个开关可以匹配缓冲区内新的行,相当于同时使用-linestop 和 -lineanchor,相当于内嵌语法(?n)
-linestop 启动行敏感开关,使^可以匹配缓冲区内部的新行,相当于(?p)
-lineanchor 改变^ 和 $ 匹配行为,可以匹配缓冲区的新行,相当于(?w)
-all尽最大可能匹配
-inline 把匹配结果:整个匹配结果和所有子匹配结果存入list返回
set a "0x1234apple";
set b [ regexp -inline {(0x[0-9]+)([a-z]+)} $a];
puts "$b";#0x1234apple 0x1234 apple
set b [ regexp -inline -all -- {\w(\w)} "abcdef" ];
puts $b;#ab b cd d ef f
– 代表后面再没有switches,都被当做正则表达式
- regsub
regsub ?switch exp string subspec varname
exp string部分和regexp 一致,不过regsub用第三个参数来替换string中和正则表达式匹配的部分,第四个参数被认为是一个变量,替换后的字符串存入这个变量。
set a "0x1234apple";
set b jjk;
set b [ regsub {(0x[0-9]+)} $a $b c];
puts "$c";#jjkapple
regsub中的开关:
\nocase 同regexp
-all 没有这个开关,regsub只替换第一个匹配,有了这个开关,把所有匹配的地方都替换。
–同regexp
- string 命令
string option arg ?arg …
- string compare ?-nocase ?-length int? string1 string2
把字符串string1 string2进行比较,string1小于 等于 大于对应-1 0 1,如果有length参数,那么只比较前int个字符,如果int为负数,那么这个参数被忽略,-nocase不区分大小写。 - string equal ?-nocase ?-length int? string1 string2
把字符串string1 string2进行比较,相等为1,否则为0. - string first string1 string2 ?startindex?
在string2中查找与string1 匹配的子串,找到就返回匹配的第一个字母所在的位置(0-based),没找到返回-1,startindex表示此处开始查找。 - string index string charindex
查找string 的字符,charindex可以是:整数n(第n个),end(最后一个),end-n(倒数第n个) - string last string1 string2 ?startindex?
同3,不过是从后开始 - string length string
- string match ?-nocase? pattern string
匹配为1,否则为0 - string range string first last
返回部分字符串 - string repeat string count
返回重复string count次的字符串 - string replace string first last ?newstring?
从string中删除从first到last的所有字符,如果有newstring,则用其替换。如果first<0则被视为0,如果last>字符串长度则被视为end,如果last < first,则string不改变。 - string tolower string ?first ?last
- string toupper string ?first ?last
- string trim string ?char?
返回string首尾删掉char字符集中包含的字符后的字符串,如果没有char,则删除spaces, tab, newlines, carriage returns. - string trimleft string ?char?
- string trimright string ?char?
文件访问
- 文件名
TCL文件名和Windows表示文件的方法有所不同,TCL表示文件目录结构时用/而不是\,这和TCL最初在unix下实现有关,如C盘tcl文件中的sample.tcl在TCL中:C:tcl/sample.tcl - 基本文件输入输出
proc tgrep { pattern filename } {
set f [ open $filename r ];
while { [gets $f line ] } {
if { [ regexp $pattern $line ] } {
puts stdout $line;
}
}
close $f;
}
输入匹配模式和文件名,输出匹配的所有行。
- open name ?access?
open以access方式打开文件,返回文件句柄,如果name第一个字符是|,管道命令被触发,而不是打开文件。access有以下方式:
access | 方式 |
---|---|
r | 以只读方式打开文件,文件必须已经存在,这是默认方式 |
r+ | 读写方式打开,文件必须存在 |
w | 只写方式打开,如果文件存在则清空文件内容,否则创建新的文件 |
w+ | 读写方式打开,如果文件存在则清空文件内容,否则创建新的文件 |
a | 只写方式打开文件,文件必须已经存在,并把指针指向文件尾,相当于追加模式 |
a+ | 读写,追加,文件不存在则创建 |
- gets fileid ?varname?
读文件句柄指向的文件的下一行,有varname就把读到的内容赋值给这个变量(文件尾返回-1),没有varname,返回下一行作为命令结果(如果到了文件尾,返回空字符串)
和gets类似的有read,但read不是以行为单位 - read ?-nonewline? fileid
读并返回fileid指向文件中所有剩下的字节,没有nonewline修饰时,在换行符处停止 - read fileid numbytes
读并返回下一个numbytes字节 - puts ?-nonewline? ?-fileid? string puts
把string写到fileid中,如果没有nonewline开关,添加换行符。fileid默认stdout,命令返回值为一空字符串。
puts命令使用C的标准IO缓冲区方案,这意味着用puts产生的信息不会立即出现在目标文件中,如果你想使数据出现在文件中,就调用flush命令。 - flush fileid
把缓冲区内容写到fileid文件中,命令返回值为空字符。
当文件关闭时,缓冲区内容自动flush。 - close fileid
关闭文件,返回值为空字符。TCL对串口、管道、socket操作和对文件操作类似,以上命令同样对它们适用。
- 随机文件访问
默认文件的输入输出是连续的,即每个gets或read命令返回的是上次访问位置后面的字节,每个puts命令是接着上次写位置写。TCL提供了seek、tell、eof等命令让用户可以非连续访问文件。
使用seek改变文件的访问点:
- seek fileid offset ?origin?
访问点为相对于origin偏移量为offset的位置。origin可以是start、current、end,默认为start,命令返回值是空字符串。
seek fileid 2000;#改变fileid文件的访问点,以便下次读写开始于文件的第2000个字节。
origin说明偏移量从哪里开始计算,current是偏移量从当前访问位置计算。
- tell fileid 返回当前访问位置
- eof fileid 如果访问到fileid文件尾 ,返回1,否则返回0。
- 当前工作目录
pwd和unix一致。
cd 参数中路径应用/ - 文件操作和获取文件信息
TCL提供了glob和file来操作文件或获取文件信息。
glob命令采用一种或多种模式作为参数,返回匹配这个模式的所有文件的列表。
glob ?switches? pattern ?pattern …?
switches可以是:
-nocomplain:允许返回空字符串,不开启时,结果为空就会报错。
– switch结束
glob命令的模式采用string match的匹配规则:
glob *.c *.h# main.c hash.c hash.h
glob还允许模式中包括在花括号中间以逗号分开的多种选择,例如:
glob { {src,backup}/*.[ch]};#src/main.c src/hash.c src/hash.h backup/hash.c
上面命令和下面等价:
glob {src/*.[ch]} {backup/*.[ch]}
file命令是有许多选项的常用命令,可以用来进行文件操作也可以检索文件信息
- file atime name
返回一个十进制的字符串,表示文件name最后被访问的时间,时间以秒为单位,从1970年1月1日 12:00AM开始计算,如果name文件不存在或者查询不到访问时间就返回错误。 - file copy ?-force? ?–? source target
把source中指明的文件或目录递归得拷贝到目的地址,只有存在-force选项时,已经存在的文件才会被覆盖,否则试图覆盖一个非空的目录或以一个文件覆盖一个目录或以一个目录覆盖一个文件都会报错。 - file delete ?-force? ?–? pathname ?pathname
删除pathname的文件或者目录,当指定了force,非空的目录也会被删除,即使没有指定force,只读文件也会被删除,删除一个不存在的文件不会引发错误。 - file dirname name
- file executable name
文件对用户是可执行的,返回1,否则返回0 - file exists name
file对当前用户拥有搜索权限的目录下返回1,否则0 - file extension name
返回name中.以后(包括.)的所有字符,没有.则返回空字符。 - file isdirectory name
如果name是目录返回1 - file isfile name
是文件返回1
…(很多,用到时候再更新)
后续是错误和日志一系列的问题,比较枯燥,待定吧。