TCL笔记

来源:互联网 发布:淘宝的等级怎么升级快 编辑:程序博客网 时间:2024/06/06 01:36

声明:以下内容全部转载自:

http://jerkwin.github.io/2016/10/28/TCL%E5%9F%B9%E8%AE%AD%E6%95%99%E7%A8%8B/

7. 过程(procedure)

TCL 支持过程的定义和调用, 在 TCL 中, 过程可以看作是用 TCL 脚本实现的命令, 效果与 TCL 的固有命令相似. 我们可以在任何时候使用proc命令定义自己的过程, TCL 中的过程类似于 C 中的函数.

7.1 过程定义和返回值

TCL 中过程是由proc命令产生的. 例如:

% proc add {x y } {expr $x+$y}

proc命令的第一个参数是你要定义的过程的名字, 第二个参数是过程的参数列表, 参数之间用空格隔开, 第三个参数是一个 TCL 脚本, 代表过程体. proc生成一个新的命令, 可以象固有命令一样调用:

% add 1 2
3

在定义过程时, 你可以利用return命令在任何地方返回你想要的值. return命令迅速中断过程, 并把它的参数作为过程的结果. 例如:

% proc abs {x} {
if {$x >= 0} { return $x }
return [expr -$x]
}

过程的返回值是过程体中最后执行的那条命令的返回值.

7.2 局部变量和全局变量

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

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

% set a 4
4
% proc sample { x } {
global a incr a
return [expr $a+$x]
}
% sample 3
8
%set a
5

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

7.3 缺省参数和可变个数参数

TCL 还提供三种特殊的参数形式:

首先, 你可以定义一个没有参数的过程, 例如:

proc add {} { expr 2+3 }

其次, 可以定义具有缺省参数值的过程, 我们可以为过程的部分或全部参数提供缺省值, 如果调用过程时未提供那些参数的值, 那么过程会自动使用缺省值赋给相应的参数. 和 C\C++中具有缺省参数值的函数一样, 有缺省值的参数只能位于参数列表的后部, 即在第一个具有缺省值的参数后面的所有参数, 都只能是具有缺省值的参数.

例如:

proc add {val1 {val2 2} {val3 3}} {
expr $val1+$val2+$val3
}

则:

add 1     //值为 6
add 2 20 //值为 25
add 4 5 6 //值为 15

另外, TCL 的过程定义还支持可变个数的参数, 如果过程的最后一个参数是args, 那么就表示这个过程支持可变个数的参数调用. 调用时, 位于args以前的参数像普通参数一样处理, 但任何附加的参数都需要在过程体中作特殊处理, 过程的局部变量args将会被设置为一个列表, 其元素就是所有附加的变量. 如果没有附加的变量, args就设置成一个空串, 下面是一个例子:

proc add { val1 args } {
set sum $val1
foreach i $args {
incr sum $i
}
return $sum
}

则:

add 2         //值为 2
add 2 3 4 5 6 //值为 20

7.4 引用: upvar

命令语法: upvar ?level? otherVar myVar ?otherVar myVar ...?

upvar命令使得用户可以在过程中对全局变量或其他过程中的局部变量进行访问. upvar命令的第一个参数otherVar是我们希望以引用方式访问的参数的名字, 第二个参数myVar是这个程中的局部变量的名字, 一旦使用了upvar命令把otherVarmyVar绑定, 那么在过程中对局部变量myVar的读写就相当于对这个过程的调用者中otherVar所代表的局部变量的读写. 下面是一个例子:

% proc temp { arg } {
upvar $arg b
set b [expr $b+2]
}
% proc myexp { var } {
set a 4 temp a
return [expr $var+$a]
}

则:

% myexp 7
13

这个例子中, upvar$arg(实际上是过程myexp中的变量a)和过程temp中的变量b绑定, 对b的读写就相当于对a的读写.

upvar命令语法中的level参数表示: 调用upvar命令的过程相对于我们希望引用的变量myVar在调用栈中相对位置. 例如:

upvar 2 other x

这个命令使得当前过程的调用者的调用者中的变量other, 可以在当前过程中利用x访问. 缺省情况下, level的值为1, 即当前过程(上例中的temp)的调用者(上例中的myexp)中的变量(上例中myexpa)可以在当前过程中利用局部变量(上例中tempb)访问.

如果要访问全局变量可以这样写:

upvar #0 other x

那么, 不管当前过程处于调用栈中的什么位置, 都可以在当前过程中利用x访问全局变量other.

8. 字符串操作

因为 TCL 把所有的输入都当作字符串看待, 所以 TCL 提供了较强的字符串操作功能, TCL 中与字符串操作有关的命令有: stringformatregexpregsubscan等.

8.1 format命令

语法: format formatstring ?vlue value...?

format命令类似于 ANSIC 中的sprintf函数和 MFC 中CString类提供的Format成员函数. 它按formatstring提供的格式, 把各个value的值组合到formatstring中形成一个新字符串, 并返回. 例如:

%set name john
John
%set age 20
20
%set msg [format "%s is %d years old" $name $age]
john is 20 years old

8.2 scan命令

语法: scan string format varName ?varName ...?

scan命令可以认为是format命令的逆, 其功能类似于 ANSI C 中的scanf函数. 它按format提供的格式分析string字符串, 然后把结果存到变量varName中,注意除了空格和 TAB 键之外, stringformat中的字符和%必须匹配. 例如:

% scan "some 26 34" "some %d %d" a b
2
% set a
26
% set b
34
% scan "12.34.56.78" "%d.%d.%d.%d" c d e f
4
% puts [format "the value of c is %d,d is %d,e is %d ,f is %d" $c $d $e $f]
the value of c is 12,d is 34,e is 56 ,f is 78

scan命令的返回值是匹配的变量个数. 而且, 我们发现, 如果变量varName不存在的话, TCL 会自动声明该变量.

8.3 regexp命令

语法: regexp ?switchs? ?--? exp string ?matchVar?\ ?subMatchVar subMatchVar...?

regexp命令用于判断正规表达式exp是否全部或部分匹配字符串string, 匹配返回1, 否则0.

在正规表达式中, 一些字符具有特殊的含义, 下表一一列出, 并给予了解释.


字符意义.匹配任意单个字符^表示从头进行匹配$表示从末尾进行匹配\x匹配字符x, 这可以抑制字符x的含义[chars]匹配字符集合chars中给出的任意字符, 如果chars中的第一个字符是^, 表示匹配任意不在chars中的字符, chars的表示方法支持a-z之类的表示(regexp)把regexp作为一个单项进行匹配
对前面的项0进行次或多次匹配+对+前面的项进行1次或多次匹配?对?前面的项进行0次或1次匹配regexp1|regexp2匹配regexp1或regexp2中的一项

下面的一个例子是从《Tcl and Tk ToolKit》中摘下来的, 下面进行说明:

^((0x)?[0-9a-fA-F]+|[0-9]+)$

这个正规表达式匹配任何十六进制或十进制的整数.

两个正规表达式以|分开(0x)?[0-9a-fA-F]+[0-9]+, 表示可以匹配其中的任何一个, 事实上前者匹配十六进制, 后者匹配十进制.

^表示必须从头进行匹配, 从而上述正规表达式不匹配jk12之类不是以0x或数字开头的串.

$表示必须从末尾开始匹配, 从而上述正规表达式不匹配12jk之类不是数字或a-fA-F结尾的串.

下面以(0x)?[0-9a-fA-F]+进行说明, (0x)表示0x一起作为一项, ?表示前一项(0x)可以出现0次或多次, [0-9a-fA-F]表示可以是任意0到9之间的单个数字或a到f或A到F之间的单个字母, +表示象前面那样的单个数字或字母可以重复出现一次或多次.

% regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} ab
1
% regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 0xabcd
1
% regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 12345
1
% regexp {^((0x)?[0-9a-fA-F]+|[0-9]+)$} 123j
0

如果regexp命令后面有参数matchVarsubMatchVar, 则所有的参数被当作变量名, 如果变量不存在, 就会被生成. regexp把匹配整个正规表达式的子字符串赋给第一个变量, 匹配正规表达式的最左边的子表达式的子字符串赋给第二个变量, 依次类推, 例如:

% regexp { ([0-9]+) *([a-z]+)} " there is 100 apples" total num word
1
% puts " $total ,$num,$word"
100 apples ,100,apples

regexp可以设置一些开关(switchs), 来控制匹配结果:

  • -nocase: 匹配时不考虑大小写

  • -indices: 改变各个变量的值, 这使各个变量的值变成了对应的匹配子串在整个字符串中所处位置的索引. 例如:

    % regexp -indices { ([0-9]+) *([a-z]+)} " there is 100 apples" total num word
    1
    % puts " $total ,$num,$word"
    9 20 ,10 12,15 20

正好子串100 apples的序号是 9-20, 100的序号是 10-12, apples的序号是 15-20

  • -about: 返回正则表达式本身的信息, 而不是对缓冲区的解析. 返回的是一个 list, 第一个元素是子表达式的个数, 第二个元素开始存放子表达式的信息.

  • -expanded: 启用扩展的规则, 将空格和注释忽略掉, 相当于使用内嵌语法(?x)

  • -line: 启用行敏感匹配. 正常情况下^$只能匹配缓冲区起始和末尾, 对于缓冲区内部新的行是不能匹配的, 通过这个开关可以使缓冲区内部新的行也可以被匹配. 它相当于同时使用-linestop-lineanchor开关, 或者使用内嵌语法(?n)

  • -linestop: 启动行结束敏感开关. 使^可以匹配缓冲区内部的新行. 相当于内嵌语法(?p)

  • -lineanchor: 改变^$的匹配行为, 使可以匹配缓冲区内部的新行. 相当于内嵌语法(?w)

  • -all: 进最大可能的匹配

  • -inline: Causes the command to return, as a list, the data that would otherwise be placed in match variables. When using -inline, match variables may not be specified. If used with -all, the list will be concatenated at each iteration, such that a flat list is always returned. For each match iteration, the command will append the overall match data, plus one element for each subexpression in the regular expression. Examples are:

    regexp -inline  {\w(\w)} " inlined "
    => {in n}
    regexp -all -inline {\w(\w)} " inlined "
    => {in n li i ne e}
  • -start index: 强制从偏移为index开始的位置进行匹配. 使用这个开关之后, ^将不能匹配行起始位置, \A将匹配字符串的index偏移位置. 如果使用了-indices开关, 则indices表示绝对位置, index表示输入字符的相对位置.

  • --: 表示这后面再没有开关(switchs)了, 即使后面有以-开头的参数也被当作正规表达式的一部分.

8.4 regsub命令

语法: regsub ?switchs? exp string subSpec varname

regsub的第一个参数是一个整个表达式, 第二个参数是一个输入字符串, 这一点和regexp命令完全一样, 也是当匹配时返回 1, 否则返回 0. 不过regsub用第三个参数的值来替换字符串string中和正规表达式匹配的部分, 第四个参数被认为是一个变量, 替换后的字符串存入这个变量中. 例如:

% regsub there "They live there lives " their x
1
% puts $x
They live their lives

这里there被用their替换了.

regsub命令也有几个开关(switchs):

  • -nocase: 意义同regexp命令中.

  • -all: 没有这个开关时, regsub只替换第一个匹配, 有了这个开关, regsub将把所有匹配的地方全部替换.

  • --: 意义同regexp命令中.

8.5 string命令

string命令的语法: string option arg ?arg...?

string命令具有强大的操作字符串的功能, 其中的option选项多达 20 个. 下面介绍其中常用的部分.

8.5.1 string compare ?-nocase? ?-length int? string1 string2

把字符串string1string2进行比较, 返回值为-1, 0 或 1, 分别对应string1小于, 等于或大于string2. 如果有-length参数, 那么只比较前int个字符, 如果int为负数, 那么这个参数被忽略. 如果有-nocase参数, 那么比较时不区分大小写.

8.5.2 string equal ?-nocase? ?-length int? string1 string2

把字符串string1string2进行比较, 如果两者相同, 返回值为 1, 否则返回 0. 其他参数与 8.5.1 同.

8.5.3 string first string1 string2 ?startindex?

string2中从头查找与string1匹配的字符序列, 如果找到, 那么就返回匹配的第一个字母所在的位置(0-based). 如果没有找到, 那么返回-1. 如果给出了startindex变量, 那么将从startindex处开始查找. 例如:

% string first ab defabc
3
% string first ab defabc 4
-1

8.5.4 string index string charIndex

返回string中第charIndex个字符(0-based). charIndex可以是下面的值:

  • 整数 n: 字符串中第 n 个字符(0-based)

  • end: 最后一个字符

  • end-整数 n: 倒数第 n 个字符. string index "abcd" end-1 返回字符c

如果charIndex小于 0, 或者大于字符串string的长度, 那么返回空.

例如:

% string index abcdef 2 c
% string index abcdef end-2 d

8.5.5 string last string1 string2 ?startindex?

参照 8.5.3, 唯一的区别是从后往前查找

8.5.6 string length string

返回字符串string的长度.

8.5.7 string match ?-nocase? pattern string

如果pattern匹配string, 那么返回 1, 否则返回 0. 如果有-nocase参数, 那么就不区分大小写.

pattern中可以使用通配符:

  • *: 匹配 string 中的任意长的任意字符串, 包括空字符串.

  • ?: 匹配 string 中任意单个字符

  • [chars]: 匹配字符集合 chars 中给出的任意字符,其中可以使用 A-Z 这种形式

  • \x: 匹配单个字符 x, 使用\是为了让 x 可以为字符*,-,[,].

例子:

% string match  abcdef
1
% string match a
abcdef
1
string match a?cdef abcdef
1
% string match {a[b-f]cdef} abcdef //注意一定要用'{',否则 TCL 解释器会把 b-f 当作命令名
1 //从而导致错误
% string match {a[b-f]cdef} accdef
1

8.5.8 string range string first last

返回字符串string中从第first个到第last个字符的子字符串(0-based). 如果first<0, 那么first被看作 0, 如果last大于或等于字符串的长度, 那么last被看作end, 如果firstlast大, 那么返回空.

8.5.9 string repeat string count

返回值为: 重复了string字符串count次的字符串. 例如:

% string repeat "abc" 2
abcabc

8.5.10 string replace string first last ?newstring?

返回值为: 从字符串string中删除了第first到第last个字符(0-based)的字符串, 如果给出了newstring变量, 那么就用newstring替换从第first到第last个字符. 如果first<0, 那么first被看作 0, 如果last大于或等于字符串的长度, 那么last被看作end, 如果firstlast大或者大于字符串string的长度或者last小于 0, 那么原封不动的返回string.

8.5.11 string tolower string ?first? ?last?

返回值为: 把字符串string转换成小写后的字符串, 如果给出了firstlast变量, 就只转换firstlast之间的字符.

8.5.12 string toupper string ?first? ?last?

同 8.5.11. 转换成大写.

8.5.13 string trim string ?chars?

返回值为: 从string字符串的首尾删除掉了字符集合chars中的字符后的字符串. 如果没有给出chars, 那么将删除掉 spaces, tabs, newlines, carriage returns 这些字符. 例如:

% string trim "abcde" {a d e}
bc
% string trim " def
> "
def

8.5.14 string trimleft string ?chars?

同 8.5.13. 不过只删除左边的字符.

8.5.15 string trimright string ?chars?

同 8.5.13. 不过只删除右边的字符.