系统学习TCL脚本入门教程


系统学习 TCL 脚本入门教程 版本:1. 0 作者:次 目 录 1 TCL语法................................................................................................................................................... 4 1.1 简介 ........................................................................................................................................................... 4 1.2 运行环境 ................................................................................................................................................... 4 1.3 本文约定 ................................................................................................................................................... 4 1.4 参考资料 ................................................................................................................................................... 4 2 引言 ........................................................................................................................................................... 5 2.1 第 1 课:简单文本输出 ........................................................................................................................... 5 2.2 第 2 课:给变量赋值 ............................................................................................................................... 5 2.3 第 3 课:命令的赋值与置换一 ............................................................................................................... 6 2.4 第 4 课:命令的赋值与置换二 ............................................................................................................... 7 2.5 第 5 课:命令的赋值与置换三 ............................................................................................................... 7 2.6 第 6 课:算数运算 ................................................................................................................................... 8 2.7 第 7 课:文本比较-SWITCH应用 ........................................................................................................ 9 2.8 第 8 课:数值比较-IF应用.................................................................................................................. 10 2.9 第 9 课:WHILE 循环........................................................................................................................... 11 2.10 第 10 课:FOR循环和INCR............................................................................................................ 11 2.11 第 11 课:过程PROC............................................................................................................................. 12 2.12 第 12 课:过程PROC的参数定义................................................................................................. 13 2.13 第 13 课:变量的作用域 ............................................................................................................... 13 2.14 第 14 课:LIST结构....................................................................................................................... 14 2.15 第 15 课:LIST项的增删改........................................................................................................... 15 2.16 第 16 课:更多LIST相关............................................................................................................... 16 2.17 第 17 课:字符串函数 ................................................................................................................... 17 2.18 第 18 课:更多字符串函数 ........................................................................................................... 17 2.19 第 19 课:修改字符串函数 ........................................................................................................... 20 2.20 第 20 课:正则表达式 ................................................................................................................... 21 2.21 第 21 课:更多正则表达式 ........................................................................................................... 22 2.22 第 22 课:数组 ............................................................................................................................... 24 2.23 第 23 课:更多数组相关 ............................................................................................................... 25 2.24 第 24 课:文件存取 ....................................................................................................................... 28 2.25 第 25 课:文件信息 ....................................................................................................................... 30 2.26 第 26 课:TCL中的子进程调用-OPEN & EXEC .......................................................................... 33 2.27 第 27 课:命令或者变量是否存在-INFO .................................................................................... 34 2.28 第 28 课:解释器状态-INFO........................................................................................................ 35 2.29 第 29 课:过程信息-INFO............................................................................................................ 36 2.30 第 30 课:模块化-SOURCE........................................................................................................... 37 2.31 第 31 课:建库-UNKNOWN & INFO LIBRARY ............................................................................... 38 2.32 第 32 课:创建命令-EVAL ........................................................................................................... 40 2.33 第 33 课:在EVAL中应用FORMAT & LIST....................................................................................... 40 2.34 第 34 课:不使用EVAL替换-FORMAT & SUBST............................................................................ 42 第 2 页 共 75 页 2.35 第 35 课:改变工作目录- CD & PWD.......................................................................................... 43 2.36 第 36 课:调试和错误-ERRORINFO & ERRORCODE & CATCH ..................................................... 44 2.37 第 37 课:调试-TRACE................................................................................................................. 45 2.38 第 38 课:命令行参数和环境串 ................................................................................................... 46 2.39 第 39 课:TIME & UNSET ................................................................................................................ 47 2.40 第 40 课:SOCKET & FILEEVENT & VWAIT...................................................................................... 49 2.41 第 41 课:日期时间-CLOCK ........................................................................................................ 51 2.42 第 42 课:I/O通道-FBLOCKED & FCONFIG ................................................................................... 53 2.43 第 43 课:子解释器 ....................................................................................................................... 56 2.44 第 44 课:数据库操作 ................................................................................................................... 57 2.45 第 45 课:函数或过程数组的输入和输出方法............................................................................ 59 2.46 第 46 课:INFO的用法.................................................................................................................... 60 2.47 第 47 课:多线程 ........................................................................................................................... 61 2.48 第 48 课:解析XML ........................................................................................................................ 72 3 跋 ............................................................................................................................................................. 75 第 3 页 共 75 页 1 TCL 语法 1.1 简介 作为脚本语言,tcl 语法简单而功能强大。它诞生于 80 年代初,进入中国的时间也许多年了。不过关 于 tcl 的资料多而杂,一时心血来潮,想到写一篇文章,以例子为中心,系统讲解 tcl 语法,让技术人员花 最少的时间对 tcl 有个全面而系统的了解,工作上使用时可以速查或参考代码。于是有了本文。 1.2 运行环境 多数代码运行在 ActiveTcl8.3.4.1-9.win32-ix86.exe 安装以后的 windows 环境中,只有两个例程运行在 unix 的环境下。 1.3 本文约定 本文为了便于速查和速学,所以每课分成讲解和例子两部分,主要是围绕着例子进行讲解。文字不多。 1.4 参考资料 本文主要参考了《TclTutor 2.0 beta4》。 2 引言 2.1 第 1 课:简单文本输出 讲解: 1. 注释符号是 # 或者 ;# ,在命令后注释用 ;# ,在行开头两者均可; 2. puts :输出文本,多个单词如被空格或 TAB 分隔需要使用“”或{} 括起来; 3. 多个命令写在一行使用 ; 间隔。 例子:002_puts.tcl # ok ;# 正确 ;# ok ;# 正确 ; # ok ;# 正确, 分号和井号之间可以有空格 puts Hello ;# 正确 puts Hello,World ;# 正确,多个单词之间不是被空格或者 TAB 分隔开 puts Hello World ;# 这行命令运行出错,被空格分隔 puts "Hello, World - In quotes" ;# 注释 puts {Hello, World - In Braces} # 这行命令运行出错,必须使用 ;# 作为注释符号 puts "This is line 1"; puts "this is line 2" ;# 正确,用分号分隔两个命令 puts "Hello, World; - With a semicolon inside the quotes" ;#正确,分号在双引号内,作为字符串一 部分 2.2 第 2 课:给变量赋值 讲解: 1. set:给变量赋值,格式为 set var value 例子:003_var.tcl ;# 给变量 X 赋一个字符串 set X "This is a string" ;# 给变量 Y 赋一个数字 set Y 1.24 ;# 显示 X 和 Y 的内容 puts $X puts $Y ;# 打印一个分隔串 puts "..............................." ;# 打印在一行中,推荐使用双引号 set label "The value in Y is: " puts "$label $Y" puts $label$Y 2.3 第 3 课:命令的赋值与置换一 讲解: 1. TCL 中命令的赋值分为置换和赋值两个步骤 2. 续行符为 \ 3. 转义符同为 \ 4. 特殊字符列表: 序号 字符 输出 十六进制 1 \a 响铃 \x07 2 \b 回车 \x08 3 \f 清屏 \x0c 4 \n 换行 \x0a 5 \r 回车 \x0d 6 \t 制表符 \x09 7 \v 垂直制表符(Vertical Tab) \x0b 8 \ddd 八进制值 d=0-7 9 \xhh 十六进制值 h=0-9,A-F,a-f 例子:004_eval.tcl ;# Show how a \ affects the $ set Z "Albany" set Z_LABEL "The Capitol of New York is: " puts "$Z_LABEL $Z" ;#显示 Albany puts "$Z_LABEL \$Z" ;#显示$Z,被 \ 转义 ;# The next line needs a backslash to escape the '$' puts "\nBen Franklin is on the \$100.00 bill" ;# \n 换行; $100 前的 \ 必须有,否则会将 100 作为一 个变量,提示出错 set a 100.00 puts "Washington is not on the $a bill" ;# This is not what you want puts "Lincoln is not on the $$a bill" ;# 显示$100,说明是后结合的,先置换了$a,此处严格的写 应该写为 \$$a puts "Hamilton is not on the \$a bill" ;# 显示$a puts "Ben Franklin is on the \$$a bill" ;# 显示$100,说明是后结合的,先置换了$a 第 6 页 共 75 页 puts "\n................. examples of escape strings" puts "Tab\tTab\tTab" puts "This string prints out \non two lines" ;# 行中 \ 没有打印出来,如果要打印出来,需要写 成 \\ puts "This string comes out\ on a single line" ;# 当一行太长,不便于阅读,使用 \ 做续行符 2.4 第 4 课:命令的赋值与置换二 讲解: 1.最外层是 {} 则不会进行置换操作,但其中的续行符仍然有效 例子:005_escape.tcl set Z "Albany" set Z_LABEL "The Capitol of New York is: " puts "\n................. examples of differences between \" and \{" ;#and前的双引号前必须有\ 进行转义,否 则这个双引号回和前面的双引号结合, 导致成了 “xxx” and \{“ 的结构,会提示出错 puts "$Z_LABEL $Z" ;# 显示 The Capitol of New York is: Albany puts {$Z_LABEL $Z} ;# 显示 $Z_LABEL $Z,没有进行置换,{}中不会置换 puts "\n....... examples of differences in nesting \{ and \" " puts "$Z_LABEL {$Z}" ;# 最外层是双引号,所以进行了置换 puts {Who said, "What this country needs is a good $Z cigar!"?} ;#最外层是花括号,所以没有进行置换 puts "\n................. examples of escape strings" puts {There are no substitutions done within braces \n \r \x0a \f \v} ;# puts {But, the escaped newline at the end of a\ string is still evaluated as a space} ;#续行符仍然生效 2.5 第 5 课:命令的赋值与置换三 讲解: 1. [ ] 可以传递其中的命令结果,注意不能被 {} 包含; 2. 双引号包含的 [ ] 中的命令可以正常执行,命令结果也可以传出; 3. {} 包含的 [ ] 中的命令不会执行,更不会有命令结果传出来。 例子:006_escape.tcl set x "abc" puts "A simple substitution: $x\n" ;#显示 abc 第 7 页 共 75 页 set y [set x "def"] ;#先执行[]中的命令,将”def”赋值给 x,然后将该命令的结果赋值给 y puts "Remember that set returns the new value of the variable: X: $x Y: $y\n" ;#显示 x 和 y 都是 def set z {[set x "This is a string within quotes within braces"]} ;#由于在{}中,所以并没有执行对 x 的赋值, 只是将{}赋值给 z puts "Note the curly braces: $z\n" set a "[set x {This is a string within braces within quotes}]" ;#执行了对 x 的赋值操作,并将值传出来赋 给了 a puts "See how the set is executed: $a" puts "\$x is: $x\n" set b "\[set y {This is a string within braces within quotes}]" puts "Note the \\ escapes the bracket:\n \$b is: $b" puts "\$y is: $y" 2.6 第 6 课:算数运算 讲解: 1. 操作符 序号 操作符 解释 1 - + ~ ! - : 负号 + : 正号 ~ : 位操作非 ! : 逻辑非 2 * / % * : 乘 / : 除 % : 取模 3 + - + : 加 - : 减 4 << >> << : 循环左移 >> : 循环右移 5 & & : 按位与 6 ^ ^ : 按位异或 7 | | : 按位或 8 && && : 逻辑与 9 || || : 逻辑或 10 x?y:z if-then-else 2. 数学函数 序号 函数 序号 函数 1 acos 11 log10 2 cos 12 tan 3 hypot 13 atan2 4 sinh 14 floor 5 asin 15 pow 6 cosh 16 tanh 7 log 17 ceil 8 sqrt 18 fmod 第 8 页 共 75 页 9 atan 19 sin 10 exp 例子:007_math.tcl set X 100; set Y 256 ;# 行末是否有分号都可以 set Z [expr "$Y + $X"] ;# 变量是否被双引号包含都可以,不过建议使用双引号 set Z [expr $Y + $X] set Z_LABEL "$Y plus $X is " puts "$Z_LABEL $Z" puts "The square root of $Y is [expr sqrt($Y)]\n" puts "Because of the precedence rules \"5 + -3 * 4\" is: [expr -3 * 4 + 5]" puts "Because of the parentheses \"(5 + -3) * 4\" is: [expr (5 + -3) * 4]" puts "\n................. more examples of differences between \" and \{" puts {$Z_LABEL [expr $Y + $X]} ;#外层是花括号不会进行置换 puts "$Z_LABEL {[expr $Y + $X]}" ;# 外层是双引号会进行置换 puts "The command to add two numbers is: \[expr \$a + \$b]" 2.7 第 7 课:文本比较-SWITCH 应用 讲解: 1. switch 的分支中的命令使用花括号包含,但是并不会影响花括号中的命令执行,切记,这是 switch 的格式; 2. 如果不想分支条件进行置换,需要在外加上花括号,不会影响分支中的命令执行。 例子:008_switch.tcl ;# Set the variables we'll be comparing set x "ONE"; set y 1; set z "ONE"; ;# This is legal switch $x "ONE" "puts ONE=1" "TWO" "puts TWO=2" "default" "puts NO_MATCH" ;#这种写法合法, 但是阅读不便 switch $x \ "ONE" "puts ONE=1" \ "TWO" "puts TWO=2" \ "default" "puts NO_MATCH" ;#这种写法好看一些,推荐 第 9 页 共 75 页 ;#下面这种写法$z 被置换,走入$z 的条件分支,表面上看条件分支中的命令在花括号内,这只是 switch 的一种 格式,所以其中的命令仍然被执行了。 switch $x \ "$z" {set y1 [expr $y+1]; puts "MATCH \$z. $y + $z is $y1" } \ "ONE" {set y1 [expr $y+1]; puts "MATCH ONE. $y + one is $y1"} \ "TWO" {set y1 [expr $y+2]; puts "MATCH TWO. $y + two is $y1" } \ "THREE" {set y1 [expr $y+3]; puts "MATCH THREE. $y + three is $y1" } \ "default" {puts "$x does not match any of these choices"} ;# This form of the command disables variable substitution in the pattern ;#下面为了不置换$z,在外层加上了花括号,于是走入了 ONE 分支,而分支中的命令仍然被执行了 switch $x { "$z" {set y1 [expr $y+1]; puts "MATCH \$z. $y + $z is $y1" } "ONE" {set y1 [expr $y+1]; puts "MATCH ONE. $y + one is $y1"} "TWO" {set y1 [expr $y+2]; puts "MATCH TWO. $y + two is $y1"} "THREE" {set y1 [expr $y+3]; puts "MATCH THREE. $y + three is $y1"} "default" {puts "$x is NOT A MATCH"} } 2.8 第 8 课:数值比较-IF 应用 讲解: 1. 条件式结果 FALSE TRUE 数值 0 非零 yes / no no yes true / false false true 2.置换变量的方法,set y x ; puts $$y ,因为是后结合并且是一次置换,所以打出来的是 $x ,不是 $x 的值;但是在 if 的条件式中进行了二次置换, $$y 被置换成了 $x 的值 3.注意:新行中需要写为 } else { ,不能将 } 写到前一行的末尾,也不能省略 } 后面的那个空格, 后面的 { 也需要写在当行,并且前面需要一个空格。 例子:009_if.tcl set x 1; if {$x == 2} {puts "$x is 2"} else {puts "$x is not 2"} ;#判断是否相等使用 == if {$x != 1} { ;#判断是否不等使用 != puts "$x is != 1" } else { puts "$x is 1" } 第 10 页 共 75 页 if $x==1 {puts "GOT 1"} set y x; if "$$y != 1" { ;#在 if 条件式中$$y 进行了二次置换 puts "$$y is != 1" ;#在 puts 命令中,只进行了一次置换 } else { puts "$$y is 1" } 2.9 第 9 课:WHILE 循环 x 讲解: 1.while 后面的条件表达式是放在花括号中的;放在双引号中会只执行一次置换 例子:010_while.tcl set x 1; while {$x < 5} {puts "x is $x"; set x [expr $x + 1]} puts "exited first loop with X equal to $x\n" set x 0; while "$x < 5" { ;#只执行一次置换 1<5,于是该条件永远为真 set x [expr $x + 1] if {$x >6} break; ;#如果去掉这句就成了死循环 if "$x > 3" continue; ;#这句使 4 打不出来 puts "x is $x"; } puts "exited second loop with X equal to $x\n" 2.10 第 10 课:FOR 循环和 incr 讲解: 1.incr x 和 set x [expr $x + 1] 达到一样的效果,向上加一 x 例子:011_for.tcl for {puts "Start"; set i 0} {$i < 2} {incr i; puts "I after incr: $i"; } { ;#第一部分只执行一次,后面两部分 每次循环都会执行 puts "I inside first loop: $i" 第 11 页 共 75 页 } for {puts "Start"; set i 3} {$i < 2} {incr i; puts "I after incr: $i"; } { ;#不会执行循环体中的命令 puts "I inside second loop: $i" } puts "Start"; set i 0; while {$i < 2} { puts "I inside first loop: $i" incr i; puts "I after incr: $i"; } 2.11 第 11 课:过程 PROC 讲解: 1. 格式:proc name args body 2. 调用方法中参数可以用花括号或者双引号包含,也可以不包含 3. 在 puts 等命令中需要置换的话,需要使用方括号 例子:012_proc.tcl proc sum {arg1 arg2} { set x [expr $arg1+$arg2]; return $x ;#过程返回值 } puts " The sum of 2 + 3 is: [sum 2 3]\n\n" ;#调用过程 #puts " The sum of 2 + 3 is: [sum {2 3}]\n\n" ;#出错,提示找不到第二个参数,置换过程中第一个参数是 {2 3},所以找不到第二个参数 puts " The sum of 2 + 3 is: sum(2 3)\n\n" ;#输出 sum(2 3),因为没有方括号,根本没有进行置换 puts " The sum of 2 + 3 is: sum{2 3}\n\n" ;#输出 sum{2 3},因为没有方括号,根本没有进行置换 sum 2 3 ;#正确 sum {2} {3} ;#正确 sum "2" "3" ;#正确 proc for {a b c} { puts "The for command has been replaced by a puts"; puts "The arguments were: $a\n$b\n$c\n" } for {set i 1} {$i < 10} {incr i} 第 12 页 共 75 页 2.12 第 12 课:过程 PROC 的参数定义 讲解: 1. 过程的参数赋缺省值:proc name {arg1 {arg2 value}} 2. 过程的不确定个数的参数定义:proc name {arg1 args} 例子:013_proc.tcl proc example {first {second ""} args} { ;#参数定义:赋缺省值和不确定个数参数定义 if {$second == ""} { puts "There is only one argument and it is: $first"; return 1; } else { if {$args == ""} { puts "There are two arguments - $first and $second"; return 2; } else { puts "There are many arguments - $first and $second and $args"; return "many"; } } } set count1 [example ONE] set count2 [example ONE TWO] set count3 [example ONE TWO THREE ] set count4 [example ONE TWO THREE FOUR] puts "The example was called with $count1, $count2, $count3, and $count4 Arguments" 2.13 第 13 课:变量的作用域 x 讲解: 1. 全局变量定义:global var1 2. 局部变量:upvar x y 等同于 upvar 1 x y,作用有两个:一是将上一层的 x 的值赋给 y;二是将上 一层的 x 的地址赋给 y,于是修改 y 等于修改 x。1 代表作用范围,也可为 2,3 等,不能为 0 例子:014_varscope.tcl proc SetPositive {variable value } { ;#此处 variable 只是一个参数名,可以修改为其他的来代替变量 upvar $variable myvar ;#此处也可写为 upvar 1 $variable myvar if {$value < 0} { set myvar [expr -$value];} else {set myvar $value;} return $myvar; } 第 13 页 共 75 页 SetPositive x 5; SetPositive y -5; puts "X : $x Y: $y\n" proc two {y} { upvar 1 $y z ;#此处绑定了 two 中的 z 和 one 中的 y upvar 2 x a ;# 此处绑定了主程序中的 x 和 two 中的 a puts "two: Z: $z A: $a" set z 1; ;# Set z, the passed variable to 1; set a 2; ;# Set x, two layers up to 2; } ;# A first level proc - This will be called by the global space code. proc one {y} { upvar $y z ;# This ties the calling value to variable z puts "one: Z: $z" ;# Output that value, to check it is 5 two z; ;# call proc two, which will change the value } one y; ;# Call one, and output X and Y after the call. puts "\nX: $x Y: $y" 2.14 第 14 课:LIST 结构 讲解: 1. list 结构下标是从零开始的,引用方式是 lindex list 位置-1 2. 字符串可以使用 :split 字符串 分隔符 拆分得到一个 list,缺省分隔符是空格 3. list 可以直接定义:set z [list a b] 4. foreach x $list :用以列出 list 中的所有项 5. llength $list :用以列出 list 中的项数 例子:015_list.tcl set x "a b c" puts "Item 2 of the list {$x} is: [lindex $x 2]\n" ;#引用 list 的第三项(从 0 开始)的值 c 使用函数 lindex, 并使用方括号 set y [split 7/4/1776 "/"] ;#等同于 set y [split “7/4/1776” "/"] puts "We celebrate on the [lindex $y 1]'th day of the [lindex $y 0]'th month\n" 第 14 页 共 75 页 set z [list puts "arg 2 is $y" ] ;#此处的 list z 是两个项:puts 和” arg 2 is $y” puts "A command resembles: $z\n" ;#这里的输出是:A command resembles: puts {arg 2 is 7 4 1776},注 意:1。当有多个子项时自动使用了花括号来明确是一项;2。由于是双引号保护,对$y 进行了置换 set i 0; foreach j $x { ;#注意这里是 foreach j $x 而不是 foreach $j $x puts "$j is item number $i in list x" incr i; } 2.15 第 15 课:LIST 项的增删改 讲解: 1. 在[]中执行的命令不会改变其中变量的值,在外面单独执行会改变其值; 2. list 函数列表: 序号 函数 解释 1 concat ?arg1 arg2 ..argn 合并 list 2 lappend listname ?arg1 arg2 ..argn 在 list 后增加项 3 linsert listname index arg1 ?arg2 ..argn 在 list 中插入项 4 lreplace listname first last ?arg1 arg2 ..argn 替代 list 中的项 例子:016_list.tcl set b [list a b {c d e} {f {g h}}] ;#为 4 项:a; b; {c d e};{f {g h}} puts "Treated as a list: $b\n" set b [split "a b {c d e} {f {g h}}"] puts "Transformed by split: $b\n" ;#输出为:a b \{c d e\} \{f \{g h\}\} set a [concat a b {c d e} {f {g h}}] puts "Concated: $a\n" ;#concat 去掉了第一层花括号,输出:a b c d e f {g h} lappend a {ij K lm} ;#在 a 后面增加了一项{ij K lm},注意此处 a 的值改变了 lappend a ij K lm ;#在 a 后面增加了三项 ij K lm,注意此处 a 的值改变了 puts "After lappending: $a\n" set b [linsert $a 3 "1 2 3"] ;# 在 a 的第三项(0 开始数)插入一项”1 2 3” ,注意此处 a 的值并没有被改变 set b [linsert $a 4 {1 2 3}] ;# 在 a 的第三项(0 开始数)插入一项{1 2 3},注意此处 a 的值并没有被改变 set b [linsert $a 1 1 2 3] ;# 在 a 的第三项(0 开始数)插入三项 1 2 3,注意此处 a 的值并没有被改变 puts "After linsert at position 3: $b\n" ;# "AA" and "BB" are two list elements. 第 15 页 共 75 页 set b [lreplace $b 3 5 "AA" "BB"] ;#注意是第三到五项(0 开始数)被替换 puts "After lreplacing 3 positions with 2 values at position 3: $b\n" 2.16 第 16 课:更多 LIST 相关 讲解: 1. list 相关的函数列表: 序号 函数 解释 1 lsearch list pattern 按照某种模式查找 list 中的项,返回满足条件的第一 项的出现位置 2 lsort list 对 list 排序 3 lrange list first last 从 list 中取出一个范围的项 lsort -mode list 排列列表。 -mode : -ascii -dictionary 与 acsii 类似,只是不区分大小写 -integer 转化为整数再比较 -real 转化为浮点数再比较 -increasing 升序(按ASCII字符比较) -decreasing 降序(按 ASCII 字符比较) -command command 执行 command 来做比较 2. 通配符列表 序号 通配符 解释 1 * 代表任意字符 2 ? 代表一个字符 3 \X 转义符 4 [...] 代表一个集合 例子:017_list.tcl set list1 [list a b c] set bpos [lsearch $list1 b] puts "b position : $bpos" ;#返回位置值 1 set list [list {Washington 1789} {Adams 1797} {Jefferson 1801} \ {Madison 1809} {Monroe 1817} {Adams 1825} ] set x [lsearch $list Washington*] ;#返回 0 set y [lsearch $list Madison*] ;#返回 3 set y [lsearch $list M*] ;#返回满足条件的第一项的位置 3 #set x [lsearch $list 17*] ;#返回-1,没有找到满足条件的项 第 16 页 共 75 页 #set y [lsearch $list 180?] ;#返回-1,没有找到满足条件的项 incr x; incr y -1; ;# Set range to be not-inclusive set subsetlist [lrange $list $x $y] puts "The following presidents served between Washington and Madison" foreach item $subsetlist { puts "Starting in [lindex $item 1]: President [lindex $item 0] " } set x [lsearch $list Madison*] set srtlist [lsort $list]; set y [lsearch $srtlist Madison*]; puts "\n$x Presidents came before Madison chronologically" puts "$y Presidents came before Madison alphabetically" 2.17 第 17 课:字符串函数 讲解: 1.字符串函数列表 序列 函数 解释 1 string length 返回字符串的长度 2 string index 返回字符串相应位置的字符 3 string range 返回字符串中一个范围内的字符子串 例子:018_string.tcl set string "this is my test string" puts "There are [string length $string] characters in \"$string\"" puts "[string index $string 1] is the second character in \"$string\"" ;#返回 h puts "\"[string range $string 5 10]\" are characters between the 5'th and 10'th" ;#返回”is my ” 2.18 第 18 课:更多字符串函数 讲解: 1.字符串函数列表 序号 函数 解释 1 string compare string1 string2 字符串比较 第 17 页 共 75 页 返回: -1 :string1 比 string2 小 0 :string1 和 string2 相等 1 :string1 比 string2 大 2 string first string1 string2 返回 string1 在 string2 中第一次出现的位置;如果 string2 不在 string1 中, 返回-1 3 string last string1 string2 返回 string1 在 string2 中最后一次出现的位置;如果 string2 不在 string1 中,返回-1 4 string wordstart string1 index 返回 string1 中 index 处的单词的开始位置 5 string wordend string1 index 返回 string1 中 index 处的单词的结束位置 6 string match pattern string1 返回 string1 中是否满足匹配模式 pattern 匹配模式的通配符: * :任意字符 ? :单个字符 \X :转义符 [...] :字符区间,例如:[a-z] 例子:019_stringcmp.tcl set fullpath "/usr/home/clif/TCL_STUFF/TclTutor/Lsn.17" set relativepath "CVS/Entries" set directorypath "/usr/bin/" set paths [list $fullpath $relativepath $directorypath] foreach path $paths { set first [string first "/" $path]; set last [string last "/" $path]; ;#根据开头是否是 \ 来判断是相对路径还是绝对路径 if {$first != 0} { puts "$path is a relative path" } else { puts "$path is an absolute path" } ;# If "/" is not the last character in $path, report the last word. ;# else, remove the last "/", and find the next to last "/", and ;# report the last word. incr last; if {$last != [string length $path]} { set name [string range $path $last end]; 第 18 页 共 75 页 puts "The file referenced in $path is $name" } else { incr last -2; set tmp [string range $path 0 $last]; set last [string last "/" $tmp]; incr last; set name [string range $tmp $last end] puts "The final directory in $path is $name" } ;# 如果是包含 CVS,判断名字开头的大小写 if {[string match "*CVS*" $path]}{ ;#注意和 lsearch 格式的区分,lsearch list pattern,匹配模式是在后面 puts "$path is part of the source code control tree" } ;#判断一个名字开头是大写还是小写字母 set comparison [string compare $name "a"] if {$comparison >= 0} { puts "$name starts with a lowercase letter\n" } else { puts "$name starts with an uppercase letter\n" } } ;#说明 string wordstart 和 string wordend set word "1 12 123" ;# 1 的开始和结束位置,返回:0 和 1 puts "wordstart : [string wordstart $word 0]" puts "wordend : [string wordend $word 0]" ;# 位置 1 上的空格的开始和结束位置,返回:1 和 2 puts "wordstart : [string wordstart $word 1]" puts "wordend : [string wordend $word 1]" ;# 位置 2 上所在单词 12 的开始和结束位置,返回:2 和 4 puts "wordstart : [string wordstart $word 2]" puts "wordend : [string wordend $word 2]" ;# 位置 5 上所在单词 123 的开始和结束位置,返回:5 和 8 puts "wordstart : [string wordstart $word 5]" puts "wordend : [string wordend $word 5]" ;# 位置 6 上所在单词 123 的开始和结束位置,返回:5 和 8 puts "wordstart : [string wordstart $word 6]" puts "wordend : [string wordend $word 6]" 第 19 页 共 75 页 2.19 第 19 课:修改字符串函数 讲解: 1. 字符串函数 序号 函数 解释 1 string tolower string1 把 string1 转换为小写字母 2 string toupper string1 把 string1 转换为大写字母 3 string trim string1 ? trimchars ? 去掉 string1 前后的 trimchars 字符,如果不指定,缺省为空 格,trimleft 和 trimright 一样的情况 4 string trimleft string1? trimchars ? 去掉 string1 左边的 trimchars 字符 5 string trimright string1? trimchars ? 去掉 string1 右边的 trimchars 字符 2. format 函数 格式:format formatstring ?arg1 arg2 ... argn ? 注意是 format,不是 string format 格式串列表: 序号 格式 描述 1 s 字符串 2 d 十进制整数 3 x 十六进制数值 4 o 八进制数值 5 f 浮点数 6 - 左对齐 7 + 右对齐,不指定 – 或 + ,缺省是右对齐 例子:020_stringmodify.tcl set upper "THIS IS A STRING IN UPPER CASE LETTERS" set lower "this is a string in lower case letters" set trailer "This string has trailing dots ...." set leader "....This string has leading dots" set both "((this string is nested in parens )))" puts "tolower converts this: $upper" puts " to this: [string tolower $upper]\n" puts "toupper converts this: $lower" puts " to this: [string toupper $lower]\n" puts "trimright converts this: $trailer" puts " to this: [string trimright $trailer .]\n" 第 20 页 共 75 页 puts "trimleft converts this: $leader" puts " to this: [string trimleft $leader .]\n" puts "trim converts this: $both" puts " to this: [string trim $both "()"]\n" set a " trim " puts "[string trim $a]" ;#返回 trim,说明缺省是去掉的空格 set labels [format "%-20s %+10s " "Item" "Cost"] ;# %-20s:总长度 20,字符串左对齐 set price1 [format "%-20s %10d Cents Each" "Tomatoes" "30"] ;# %10d:总长度 10,数值右对齐 set price2 [format "%-20s %10d Cents Each" "Peppers" "20"] set price3 [format "%-20s %10d Cents Each" "Onions" "10"] set price4 [format "%-20s %10.2f per Lb." "Steak" "3.59997"] ;#%10.2f:总长度 10,小数点后两位,数值 右对齐 puts "\n Example of format:\n" puts "$labels" puts "$price1" puts "$price2" puts "$price3" puts "$price4" 2.20 第 20 课:正则表达式 讲解: 1. 利用正则表达式在字符串中查找子串 格式:regexp ?switches?exp string1?matchVar? ?subMatch1 ... subMatchN? 例如:regexp {([A-Za-z]+) +([a-z]+)} $sample match sub1 sub2 ¾ switches exp :正则表达式,使用 {} 包含,如果使用””包含,需要转义符才能执行 ¾ $sample :被查找的字符串 ¾ match:满足{([A-Za-z]+) +([a-z]+)}匹配的子串传递给 match ¾ sub1:满足([A-Za-z]+)的子串传递给 sub1,正则表达式中圆括号的匹配项会将结果按顺序传 递给后面的变量 ¾ sub2:满足([a-z]+)的子串传递给 sub2,正则表达式中圆括号的匹配项会将结果按顺序传递给 后面的变量 2. 利用正则表达式在字符串中替换子串 格式:regsub ?switches? exp string1 subSpec VarName 例如:regsub "way" $sample "lawsuit" sample2 switches exp:正则表达式,此处是字符串"way" string1:被替换的字符串 第 21 页 共 75 页 subSpec:替换为什么字符串,此处是"lawsuit" VarName:替换后的结构赋给的变量 注意:只替换一次。 3. 正则表达式常用通配符列表 序号 通配符 描述 1 ^ 匹配一个字符串的开头 2 $ 匹配一个字符串的结尾 3 . 匹配任意一个字符 4 * 匹配 0 到 n 个任意字符 5 + 匹配 1 到 n 个任意字符 6 [...] 匹配一个字符集合,例如:[a-z]代表匹配所有小写字母 7 [^...] 匹配不包括该集合,例如:[^a-z]代表匹配所有非小写字母 8 (...) 圆括号会将其中的正则表达式的匹配项传递给后面的变量 例子:021_regular.tcl set sample "Where there is a will, There is a way." set result [regexp {[a-z]+} $sample match] puts "Result: $result match: $match" ;#返回 here set result [regexp {([A-Za-z]+) +([a-z]+)} $sample match sub1 sub2 ] puts "Result: $result Match: $match 1: $sub1 2: $sub2" ;#sub1=Where, sub2=there set result [regexp {([A-Za-z]+)( +)([a-z]+)} $sample match sub1 sub2 sub3] puts "Result: $result Match: $match 1: $sub1 2: $sub2 3: $sub3" ;#返回的结果为:sub1=Where,sub2 是 一个空格,sub3=there。说明加上圆括号就能将匹配子串传递给后面的变量 regsub "way" $sample "lawsuit" sample2 puts "New: $sample2" set sample "Where there is a will, There is a way way." regsub "way" $sample "lawsuit" sample2 puts "New: $sample2" ;#返回结果: ..There is a lawsuit way. 可以看出只是替换了左边的一个 way 2.21 第 21 课:更多正则表达式 讲解: 第 22 页 共 75 页 正则表达式非常重要,虽然已经有正则表达式的工业标准,但是实际情况并不完全统一,perl,shell, tcl,java 等的正则表达式都有些许区别,所以大家需要留心细节。 1.[^ ]* :0 到 n 个非零字符 2. * :0 到 n 个零 3.[0-9]+ :1 到 n 个数字 4.[^/]* :0 到 n 个非反斜杠字符 5.(/[a-z]*) :前面是一个反斜杠,后面跟着 0 到 n 个小写字母 6.([^\t]+) :非制表符的任意字符 7.(\t) :一个制表符 8.\\\[ :其实是匹配的\[ ,因为书写的时候需要转义符 例子: set list1 [list {/dev/wd0a 17086 10958 5272 68% /}\ {/dev/wd0f 179824 127798 48428 73% /news}\ {/dev/wd0h 1249244 967818 218962 82% /usr}\ {/dev/wd0g 98190 32836 60444 35% /var}] foreach line $list1 { regexp {[^ ]* *([0-9]+)[^/]*(/[a-z]*)} $line match size mounted; puts "$mounted is $size blocks" # get every field value regexp {^([^ ]*)} $line match first; regexp {[^ ]* *([0-9]*)} $line match first; regexp {[^ ]* *[0-9]* *([0-9]*)} $line match first; regexp {[^ ]* *[0-9]* *[0-9]* *[0-9]* *([0-9]*%)} $line match first; regexp {[^ ]* *[0-9]* *[0-9]* *[0-9]* *[0-9]*% *(/[a-z]*)} $line match first; regexp {(/[a-z]*)$} $line match first; puts "$first"; } # get two number regexp {.(.[0-9]*).a} "3343a" match ss; puts "$ss" set line {Interrupt Vector? [32(0x20)]} regexp "\[^\t]+\t\\\[\[0-9]+\\(0x(\[0-9a-fA-F]+)\\)]" $line match hexval ;#看得人头晕,建议各位还是用花 括号吧 puts "Hex Default is: 0x$hexval" set str2 "abc^def" regexp "\[^a-f]*def" $str2 match 第 23 页 共 75 页 regexp {[a-z]*\^[a-z]*$} $str2 match puts "using \[^a-f] the match is: $match" regexp "\[a-f^]*def" $str2 match regexp {[a-f^]*def} $str2 match puts "using \[a-f^] the match is: $match" regsub {\^} $str2 " is followed by: " str3 puts "$str2 with the ^ substituted is: \"$str3\"" regsub "(\[a-f]+)\\^(\[a-f]+)" $str2 "\\2 follows \\1" str3 puts "$str2 is converted to \"$str3\"" 注意: 1.()和[]的区别 (ab) 代表的是 ab 串,也就是说 ab 是有顺序的 [ab] 代表的是 a,b 等,代表的是不确定的数个字符,没有顺序问题 2. [^a-z] 代表的是非 a-z 的任意字符 [a-z^] 代表的是 a-z 及 ^ 字符,此处的^ 不代表非的意思,它和 a,b,c 一样是个单独的字符 3. [a-z]* 和 ([a-z]*) 的区别 : 例子: regexp {([0-9]*)} "333" match ss; puts "$ss" ;#有括号才有返回,没有括号就没有返回 2.22 第 22 课:数组 讲解: 1.数组相关命令 序号 命令 描述 1 array exists arrayName 判断一个数组是否存在,数组存在返回 1,数组不存在返回 0 2 array names arrayName ?pattern 返回一个数组的指示列表,相当于数组的第一维,如果没有匹配串 则完全返回 3 array size arrayName 返回数组的列数,相当于数组的第二维数目 4 array get arrayName 取得数组的值列表,它使数组的赋值变得简单,例如: array set arrayX [array get arrayY] ,实现了将 arrayY 赋值给 arrayX 5 array set arrayName datalist 数组定义 第 24 页 共 75 页 例子:023_array.tcl array set array1 [list {123} {Abigail Aardvark} \ {234} {Bob Baboon} \ {345} {Cathy Coyote} \ {456} {Daniel Dog} ] puts "Array1 has [array size array1] entries\n" puts "Array1 has the following entries: \n [array names array1] \n" puts "ID Number 123 belongs to $array1(123)\n" set array1(123) {modified} set array1(123) "modified" puts "ID Number 123 belongs to $array1(123)\n" array set array3 [array get array1] ;#可以看出 tcl 中的数组赋值非常简单,不用写循环来赋 值 puts "Array3 has [array size array3] entries\n" puts "Array3 has the following entries: \n [array names array3] \n" puts "Array3 has the following entries while using pattern: \n [array names array3 *2*] \n" ;#使用匹配串 puts "ID Number 123 belongs to $array3(123)\n" if {[array exist array1]} { puts "array1 is an array" } else { puts "array1 is not an array" } if {[array exist array2]} { puts "array2 is an array" } else { puts "array2 is not an array" } 2.23 第 23 课:更多数组相关 讲解: 1. 使用 foreach 浏览数组内容; 2. 使用 array startsearch,array anymore 和 array nextelement 浏览数组内容; 3. 数组相关函数列表: 序号 函数 描述 1 array startsearch arrayName 得到数组第一项,返回的是 id 2 array nextelement arrayName searchID 得到数组的下一项,返回的是 id 第 25 页 共 75 页 3 array anymore arrayName searchID 根据当前的 id 判断是否还有内容,返回 1 为找 到,返回 0 为没有找到 4 array donesearch arrayName searchID 根据 id 查找相应项,会破坏相应的状态信息 4. global 和 upvar 的使用注意事项 global upvar 函数内 用$ 用$ 普通变量 函数外 用$ 不用$ 函数内 用$ 用$ 数组变量 函数外 不用$ 不用$ 解释:除了 global 普通变量函数内外都用$,其他都是函数外不用$,函数内用$。特例不考虑。 下面有详细的例子 例子:024_array.tcl array set array1 [list {123} {Abigail Aardvark} \ {234} {Bob Baboon} \ {345} {Cathy Coyote} \ {456} {Daniel Dog} ] ;# ;# Simply iterating through an array with a foreach loop: ;# foreach id [array names array1] { ;#浏览数组内容方法一 puts "$array1($id) has ID: $id" } ;# Without sorted even turn is not original ;# Two procs iterating through the same array with iteration commands ;# proc getrec_format1 {arrayVar searchid} { global $arrayVar upvar $searchid id ;#注意这里的 id 并非在上一层次中已经定义的一个变量, 他只是表明通过参数修改的是上一级堆栈的变量,此处 id 随便改成一个上一级堆栈并未定义的变量名是没 有问题的,可以试试将 id 改为 id1 set record [array nextelement $arrayVar $id]; return "The current ID is: $record" } ;# proc getrec_format1 {arrayVar searchid} { ;#这种写法是其他语言最常用的写法,但是 tcl 中是错误的,数组的传递与其他简单类型不同 第 26 页 共 75 页 ;# set record [array nextelement $arrayVar $searchid]; ;# return "The current ID is: $record" ;# } proc getrec_format2 {arrayVar searchid} { global $arrayVar upvar $searchid id set record [array nextelement $arrayVar $id]; return $record; } set searchId [array startsearch array1] ;# 123 开始,浏览数组内容方法二 puts "" set item 0; while {[array anymore array1 $searchId]} { incr item; if {[expr $item %2]} { set format1 [getrec_format1 array1 searchId] puts "item number: $item format 1: $format1" } else { set format2 [getrec_format2 array1 searchId] puts "item number: $item format 2: $format2" } } ;#普通类型 global 传值 proc tryglobal {glo} { global $glo return "The current glo is: $glo" } set glo1 "12341234" puts "[tryglobal glo1]" ;#返回 glo1, 而用 upvar,就不加$ 在前面 puts "[tryglobal $glo1]" ;#返回 12341234 ;#普通类型 upvar 传值 proc tryupvar {upv} { upvar $upv upv1 return "The current upv is: $upv1" } 第 27 页 共 75 页 set upv1 "1234512345" puts "[tryupvar upv1]" ;#返回 1234512345 ;# puts "[tryupvar $upv1]" ;#出错了 ;#定义一个数组后面用 array set arr1 [list {1} {2} {3} {4}] ;# 会被理解为二维的,即 1,3 是 array names;2,4 是值 ;#数组类型 global 传值 proc tryarrglobal {arrglo} { global $arrglo return "The current glo is: [array names $arrglo]" } ;#puts "[tryarrglobal arr1]" ;#函数中 arrglo:空 ;#puts "[tryarrglobal $arr1]" ;#函数中 arrglo:出错了 puts "[ tryarrupvar arr1]" ;#函数中$arrglo:正确:返回 1,3 ;#puts "[ tryarrupvar $arr1]" ;#函数中$arrglo:出错了 ;#数组类型 upvar 传值 proc tryarrupvar {arrglo} { upvar $arrglo arrglo1 return "The current glo is: [array names $arrglo1]" } ;#puts "[tryarrglobal arr1]" ;#函数中 arrglo1: 正确:返回 1,3 ;#puts "[tryarrglobal $arr1]" ;#函数中 arrglo1: arrglo 出错了 puts "[ tryarrupvar arr1]" ;#函数中$arrglo1:正确:返回 1,3,建议 ;#puts "[ tryarrupvar $arr1]" ;#函数中$arrglo1:出错了 ;# 参数传递非常容易混乱,所以举的例子,没有用文字说明,下面来总结一下 ;# global: 普通变量:函数内:用$ 函数外:用$ 数组:函数内:用$ 函数外:不用$ ;# upvar: 普通变量:函数内:用$ 函数外:不用$ 数组:函数内:用$ 函数外:不用$ 或 函 数内:不用$ 函数外:不用$ ;# 一句话:除了 global 普通变量函数内外都用$,其他都是函数外不用$,函数内用$。特例不考虑。 2.24 第 24 课:文件存取 讲解: 第 28 页 共 75 页 1. 文件打开命令: 格式:openfile fileName ? access ? permission 解释: ¾ fileName:文件名称 ¾ access:存取模式 序号 存取模式 描述 1 r 打开文件读。文件必须已经存在 2 r+ 打开文件读写。文件必须已经存在 3 w 打开文件写。如果文件不存在创建一个,长度设为零 4 w+ 打开文件读写。如果文件不存在创建一个,长度设为零 5 a 打开文件写。文件必须已经存在,位置指到文件末尾 6 a+ 打开文件读写,如果文件不存在,创建一个,位置指到文件末尾 ¾ permission:权限,举一个例子: 000 000 000 :第一组三位为 user 权限;第二组三位为同组其他用户的权限;第三组三位为其他组所 有人的权限。每个三位的权限依次代表读,写,执行。如果有相应的权限就设置为一,没有设置为 0。然 后三位为组转成十进制数。 2. 文件关闭命令,文件用完后一定要记得关闭 格式:close fileID 3. 读取文件内容 格式:gets fileID ?varName? ¾ fileID 可以为以下四种中的一种 z 通过 open 打开文件得到的文件标志符 z stdin:标准输入 z stdout:标准输出 z stderr:标准错误 ¾ varName:读出的文件内容存入 4. 向文件写入内容 格式:puts ?-nonewline ? fileID ?string fileID 可以是:通过 open 打开文件得到的文件标志符,stdout,stderr 5.读入文件内容 功能:读入文件所有剩下的内容 格式:read ?-nonewline? fileID 解释:如果使用了 nonewline,读入每一行前会先去掉最后一个字符 功能:读入指定字节数的文件内容 格式:read fileID numBytes 6.改变指向文件中的位置 格式:seek fileID ?offset ?orgin? 第 29 页 共 75 页 ¾ offset:偏移量 ¾ orgin:偏移量的相对位置 z start:文件开头位置 z current:文件当前位置 z end:文件结束位置 7.其他文件函数 功能:返回文件位置 格式:tell fileID 功能:刷新缓存区的内容,将内存中缓冲区的内容写到硬盘上 格式:flush fileID 功能:判断是否在文件末尾 格式:eof fileID 例子:025_file.tcl set file "025_file.txt" set fileid [open $file w+] ;#得到文件的标志符 seek $fileid 0 start ;#定位到文件头 puts $fileid "This is one.\nIt is two.\nIt is three" seek $fileid 0 start ;#定位到文件头 set chars [gets $fileid line1] ;#读文件中的一行 set line2 [gets $fileid]; ;#读文件中的第二行 puts "There are $chars characters in \"$line1\"" puts "The second line in the file is: \"$line2\"" seek $fileid 0 start ;#定位到文件头 set buffer [read $fileid] ;#读入文件所有的内容 puts "\nTotal contents of the file are:\n$buffer" close $fileid 2.25 第 25 课:文件信息 讲解: 1.File 命令提供文件信息 第 30 页 共 75 页 序号 函数 描述 1 file dirname 返回路径名 2 file extension 返回文件扩展名 3 file rootname 返回不包括扩展名的文件名 4 file tail 返回不包括路径名的文件名 5 file atime 返回最后一次文件存取时间 6 file executable 如果可执行返回 1 7 file exists 如果文件存在返回 1 8 file isdirectory 如果是个路径返回 1 9 file isfile 如果是个文件返回 1 10 file lstat 返回一个文件状态信息数组 atime:最后一次存取时间 ctime:最后文件状态改变时间 dev:设备节点 gid:文件的组 id ino:节点数 mode:节点保护模式 mtime:最后数据修改时间 nlink:硬链接数 size:文件字节数 type:文件类型 uid:文件用户 id 注意:这个函数和 file stat 不同在于当为一个符号链接 时,file lstat 是得到链接的信息,而 file stat 是得到链 接的文件的信息 11 file mtime 返回文件最后修改时间 12 file owned 如果用户是该文件所有者返回 1 13 file readable 如果文件可读返回 1 14 file readlink 返回符号链接的文件名 15 file size 返回文件大小(byte 为单位) 16 file stat 返回一个文件状态信息数组 17 file type 返回文件类型 file:普通文件 directory:目录 characterSpecial:字符导向设备 blockSpecial:块导向设备 fifo:命名管道 link:符号链接 socket:套接字 18 file writable 如果文件可写返回 1 2.glob 命令通过匹配模式返回一个文件名列表 格式:glob ?switches? pattern? patternN? 第 31 页 共 75 页 ¾ switches: -nocomplain:允许返回一个空列表的时候不生成错误,如果没有这个开关,返回空列表的时候会生 成错误 --:允许在匹配式中使用横杠 ¾ pattern:匹配式 z {a,b,....}:匹配所有的 a,b,等字符串 z 文件名以小数点开头则解析为小数点,其他地方就解析为通配符; z 所有的 / 都必须准确匹配; z 如果匹配式开始是:~/ ,则 ~ 解析为环境变量 HOME; z 如果匹配式的第一个字符是 ~ 后面跟着一个登录 id,则 ~loginid 被解析为用户目录。 例子:026_file.tcl ;# Collect a bunch of files to compare set ail1 [glob C:/windows/system/m*.dll] set ail2 [glob C:/windows/*.exe] ;# Set the format string (see Lsn.18), and display column headers set fmt "%-12s %-16s %8s %-7s" puts "[format "$fmt Comment" "Directory" "Name" "Inode" "Type"]" ;# Loop through the filenames collected by glob, and ;# determine their inode, size, and type. ;# Then display the results. foreach name [concat $ail1 $ail2] { ;#puts "NAME : $name" ;# split the name into pieces for display: set dir [file dirname $name] set filename [file tail $name] ;# Collect some status and type info. file stat $name arr set type [file type $name] ;# Display what we've learned. puts -nonewline "[format $fmt $dir $filename $arr(ino) $type]" ;# and particular data depending on whether item is a file or symbolic link. 第 32 页 共 75 页 if {[string match [file type $name] "link"]} { puts " points to: [file readlink $name]" } if {[string match [file type $name] "file"]} { puts " Size: [file size $name] bytes " } } 2.26 第 26 课:TCL 中的子进程调用-open & exec 讲解: 1. 格式:open |progName ?access? 功能:为管道返回一个文件描述符。如果 progName 用引号括起来,可以包含参数。 2. 格式:exec ?switches?arg1?arg2?....?argN? 功能:执行子进程 ¾ switches:-keepnewline:管道输出的每行后面加个新行。 -- :标志开关结束,哪怕后面跟着有 – 开头的字符串,都作为参数处理。 ¾ argN:参数可以是:可执行程序;作为子进程运行的命令行;输出重定向 输出重定向包含有: 序号 重定向 描述 1 | 将标准输出重定向到标准输入中 2 fileName 管道中最后一个程序的输出内容覆盖写入文件 6 >>fileName 管道中最后一个程序的输出内容写入文件,添加到文件末尾 7 2>fileName 管道中所有程序的标准错误输出到文件,覆盖文件原有内容 8 2>>fileName 管道中所有程序的标准错误输出到文件,添加到文件末尾 9 >@fileID 管道中程序输出内容写到文件描述符,该文件描述符是使用 open...”w” 打开 例子:027_file.txt set io [open "|c:/windows/system32/notepad.exe c:/temp/inv_2700.tcl" r+] ;#open 调用外部程序 set invert [exec c:/windows/system32/notepad.exe c:/temp/001.txt] ;#exec 调用外部程序 第 33 页 共 75 页 因为管道的例子在 windows 下比较难举,现以 unix 下一段代码为例子,通过管道读出 ps(列出进程) 命令的输出 tclfolder%tclsh % set fid [open "|ps -ef" r+] file5 % gets $fid line;puts "line: $line" line: UID PID PPID C STIME TTY TIME CMD % gets $fid line;puts "line: $line" line: root 1 0 0 Nov 11 - 0:01 /etc/init % gets $fid line;puts "line: $line" line: root 86118 1 0 Nov 11 - 5:00 /usr/sbin/syncd 60 % gets $fid line;puts "line: $line" line: tclll 90248 893094 0 11:54:55 pts/14 0:00 -csh % gets $fid line;puts "line: $line" line: root 110730 1 0 Nov 11 - 0:00 /usr/sbin/uprintfd % gets $fid line;puts "line: $line" line: root 118848 1 0 Nov 11 - 0:00 /usr/ccs/bin/shlap64 % exit tclfolder% 2.27 第 27 课:命令或者变量是否存在-info 讲解: 1. info 命令列表,使用的匹配式规则和 string match 一致,并且如果不使用匹配式,返回所有的项 序号 命令 描述 1 info commands ?pattern? 返回匹配的命令列表 2 info exists varName 变量存在返回一,否则返回零 3 info globals ?pattern? 返回全局变量列表 4 info locals ?pattern? 返回局部变量列表 5 info procs ?pattern? 返回过程列表 6 info vars ?pattern? 返回变量列表 例子:028_file.tcl proc safeIncr {val {amt 1}} { upvar $val v if {[info exists v]} { incr v $amt} else { set v $amt } } if {[info procs safeIncr] == "safeIncr"} { ;#判断过程是否定义 safeIncr a ;#a 没有定义 第 34 页 共 75 页 } puts "After calling SafeIncr with a non existent variable: $a" ;#返回 1 set a 100 safeIncr a puts "After calling SafeIncr with a variable with a value of 100: $a" ;#返回 101 safeIncr b -3 puts "After calling safeIncr with a non existent variable by -3: $b" ;#返回-3 set b 100 safeIncr b -3 puts "After calling safeIncr with a variable whose value is 100 by -3: $b" ;#返回 97 puts "\nThese variables have been defined: [lsort [info vars]]" puts "\nThese globals have been defined: [lsort [info globals]]" ;# 检查本地过程是否存在 set exist [info procs localproc]; if {$exist == ""} { puts "\nlocalproc does not exist at point 1" } proc localproc {} { global argv; set loc1 1; set loc2 2; puts "\nLocal variables accessible in this proc are: [lsort [info locals]]" puts "\nVariables accessible from this proc are: [lsort [info vars]]" puts "\nGlobal variables visible from this proc are: [lsort [info globals]]" } set exist [info procs localproc]; if {$exist != ""} { puts "localproc does exist at point 2" } localproc; 2.28 第 28 课:解释器状态-info 第 35 页 共 75 页 讲解: 1. 关于当前解释器状态信息命令列表 序号 命令 描述 1 info cmdcount 返回解释器已经运行的命令的数量 2 info level ?number? 返回相应栈级别的过程的名字和参数 3 info patchlevel 返回全局变量解释器补丁版本和修订号 tcl_patchlevel 的值 4 info tclversion 返回全局变量解释器版本 tcl_version 5 info script 返回当前脚本的名字 6 pid 返回当前解释器进程 id 例子:029_file.tcl puts "This is how many commands have been executed: [info cmdcount]" puts "Now *THIS* many commands have been executed: [info cmdcount]" puts "\nThis interpreter is revision level: [info tclversion]" puts "This interpreter is at patch level: [info patchlevel]" puts "\nThe temporary script this is executing is named: [info script]" puts "The Pid for this program is [pid]" proc factorial {val} { puts "Current level: [info level] - val: $val" set lvl [info level] if {$lvl == $val} {return $val;} return [expr ($val-$lvl) * [factorial $val]]; } set count1 [info cmdcount] set fact [factorial 3] set count2 [info cmdcount] puts "The factorial of 3 is $fact" puts "Before calling the factorial proc, $count1 commands had been executed" puts "After calling the factorial proc, $count2 commands had been executed" puts "It took [expr $count2-$count1] commands to calculate this factorial" 2.29 第 29 课:过程信息-info 讲解: 1.关于过程信息的命令列表 第 36 页 共 75 页 序号 命令 描述 1 info args procname 返回一个过程的参数列表 2 info body procname 返回一个过程的内容 3 info default procname arg varName 如果一个过程的某个参数设置了缺省值则返回一,否则返回零 例子:030_procinfo.tcl proc demo {argument1 {default "DefaultValue"} } { puts "This is a demo proc. It is being called with $argument1 and $default" } puts "The args for demo are: [info args demo]\n" puts "The body for demo is: [info body demo]\n" set arglist [info args demo] foreach arg $arglist { if {[info default demo $arg defaultval]} { ;#defaultval 用于取回缺省值 puts "$arg has a default value of $defaultval" } else { puts "$arg has no default" } } 2.30 第 30 课:模块化-source 讲解: 1. source 的用途 ¾ 将一个程序分为多个文件; ¾ 可以将一组过程放到一个文件中,成为一个库文件; ¾ 配置程序; ¾ 加载数据文件。 2. 格式:source fileName 说明: ¾ 读入文件并执行; ¾ 如果代码出错,source 返回那个错误 ¾ 如果执行到返回,就立刻返回,即便返回命令后面还有命令也不执行立刻返回 ¾ 如果文件名以 ~ 开头,替换为环境变量 $HOME 例子:031_source.tcl set filename "C:\\windows\\temp\\TT_[pid]" 第 37 页 共 75 页 set outfile [open "$filename" "w"]; puts $outfile {set scr [info script]} puts $outfile "proc testproc {} {" puts $outfile "global scr;" puts $outfile "puts \"testproc source file: \$scr.\"" puts $outfile "puts \"testproc executing from \[info script]\n\"" puts $outfile "}" puts $outfile {set abc 1}; puts $outfile {return}; puts $outfile {set aaaa 1} ;#没有执行这一句 close $outfile; puts "This is the contents of $filename:" puts ".............................................................." puts "[exec cmd /C type $filename]" puts ".............................................................." puts "\n" puts "Global variables visible before sourceing $filename:" puts "[lsort [info globals]]\n" if {[info procs testproc] == ""} { puts "testproc does not exist. sourceing $filename" source $filename ;#加载上过程后,就可以调用了 } puts "\nNow executing testproc" testproc ;#执行过程 puts "Global variables visible after sourceing $filename:" puts "[lsort [info globals]]\n" exec cmd /c del $filename ;#删除该文件 2.31 第 31 课:建库-unknown & info library 讲解: 1. 自动加载库文件的方法: 2. 建库的相关函数列表 序号 函数 描述 第 38 页 共 75 页 1 auto_mkindex libdir file1...filen 为库文件创建索引文件(tclIndex),自动加载库的过程定义在 init.tcl 文件中,当 tclsh 启动的时候执行 2 info library 返回库所在路径,实际是环境变量 TCL_LIBRARY 的值,tcl 会在该路径下查找 init.tcl 3 unknown args 当解释器遇到调用索引文件中不存在的过程是,该函数尝试下 面的步骤来执行命令: 1. 查找 auto_path 的路径中的索引文件,如果找到过程定义, 使用 auto_load 过程加载执行; 2. 如果解释器交互执行的,tcl尝试使用过程auto_exec去执行; 3. 如果命令是个不带后缀名的 tcl 命令,unknown 完善命令名 字,并执行 例子:032_source.tcl ;# Set up a temporary file with a test proc. set filename "C:/temp/TT_[pid]" ;#1. 生成文件 set outfile [open "$filename" "w"]; puts $outfile {set scr [info script]} puts $outfile "proc testproc {} {" puts $outfile "global scr;" puts $outfile "puts \"testproc source file: \$scr.\"" puts $outfile "puts \"testproc executing from \[info script]\n\"" ;#注意:返回的是 032_source.tcl 而不是 TT_[pid] puts $outfile "}" close $outfile; puts "The directories in the auto path are: $auto_path\n" puts "The default library is: [info library]\n"; auto_mkindex "C:/temp" [file tail $filename] ;#2. 对文件建立索引文件,c:/temp 路径下生成索引文 件 tclIndex # With Tcl8.3, this must come after auto_mkindex. lappend auto_path "C:/temp" ;#3. 将库的路径加到 auto_path 中 if { [info procs testproc] == ""} { puts "testproc does not exist\n" } testproc 第 39 页 共 75 页 if { [info procs testproc] != ""} { puts "\ntestproc does exist now" } file delete C:/temp/tclIndex file delete $filename 2.32 第 32 课:创建命令-eval 讲解: 1.在程序运行的过程中,tcl 可以执行其中创建的命令 格式:eval arg1 ??arg2??... ??argn?? 功能:将参数连接成一个字符串,传递给 tcl_Eval 进行执行,并返回结果或错误码 例子:033_eval.tcl set cmd {puts "Evaluating a puts"} puts "CMD IS: $cmd" eval $cmd if {[string match [info procs tempFileName] ""] } { puts "\nDefining tempFileName for this invocation" set num 0; set cmd "proc tempFileName " set cmd [concat $cmd "{} {\n"] set cmd [concat $cmd "global num;\n"] set cmd [concat $cmd "incr num;\n"] set cmd [concat $cmd " return \"/tmp/TMP.[pid].\$num\";\n"] set cmd [concat $cmd "}"] eval $cmd } puts "\nThe body of tempFileName is: \n[info body tempFileName]\n" puts "tmpFileName returns: [tempFileName]" ;# 这里$num 返回 1 puts "tmpFileName returns: [tempFileName]" ;# $num 加一,返回 2 2.33 第 33 课:在 eval 中应用 format & list 第 40 页 共 75 页 讲解: 1.常见的 eval 错误 ok:eval puts ok err:eval puts not ok ok:set x “OK” ; eval puts $x err:set x “NOT OK”; eval puts $x not ok 被解析为了两个参数 下面三种写法是正确的: eval [list puts {NOT OK}] eval [list puts “NOT OK”] set cmd “puts”; lappend cmd {NOT OK};eval $cmd 结论:对于构建 eval 的需要的命令,要么使用 string 的 format,要么使用 list 和 lappend。 2.格式:info complete string 功能:检查字符串中的空格,引号,括号等是否匹配,匹配返回一,不匹配返回零 例子:034_format_list.tcl set cmd "OK" eval puts $cmd ;#正确 set cmd "puts" ; lappend cmd {Also OK}; eval $cmd ;#正确 set cmd "NOT OK" #eval puts $cmd ;#出错 eval [format {%s "%s"} puts "Even This Works"] ;#正确 set cmd "And even this can be made to work" eval [format {%s "%s"} puts $cmd ] ;#正确 set tmpFileNum 0; set cmd {proc tempFileName } lappend cmd "" lappend cmd "global num; incr num; return \"/tmp/TMP.[pid].\$tmpFileNum\"" ;#这里$tmpFileNum 不会 返回 0,会返回$tmpFileNum,因为它是在过程外赋值的 eval $cmd puts "\nThis is the body of the proc definition:" puts "[info body tempFileName]\n" 第 41 页 共 75 页 set cmd {puts "This is Cool!"} if {[info complete $cmd]} { eval $cmd } else { puts "INCOMPLETE COMMAND: $cmd" } 2.34 第 34 课:不使用 eval 替换-format & subst 讲解: 1.不使用 eval 的时候,字符串会进行一次置换 例如: set a “sampleA” set c a puts “$$c” ;#会返回$a,而不是$a 的值 要想进行多次置换,需要使用 format 或者 subst,使用一次置换一次 注意:使用双引号会置换一次,但是使用花括号不会置换 2.格式:subst ?-nobackslashes??-nocommands??-novariables? string -no 后面接什么就不置换什么 例子: set a "alpha" set b a puts {a and b with no substitution: $a $$b} puts "a and b with one pass of substitution: $a $$b" puts "a and b with subst in braces: [subst {$a $$b}]" puts "a and b with subst in quotes: [subst "$a $$b"]\n" puts "format with no subst [format {$%s} $b]" puts "format with subst: [subst [format {$%s} $b]]" eval "puts \"eval after format: [format {$%s} $b]\"" set num 0; set cmd "proc tempFileName {} " set cmd [format "%s {global num; incr num;" $cmd] set cmd [format {%s return "/tmp/TMP.%s.$num"} $cmd [pid] ] set cmd [format "%s }" $cmd ] eval $cmd 第 42 页 共 75 页 puts "[info body tempFileName]" set a arrayname set b index set c newvalue eval [format "set %s(%s) %s" $a $b $c] puts "Index: $b of $a was set to: $arrayname(index)" set x "xyz" set y x set z y puts "xyz is : $x $$y $$$z" puts "xyz is : [subst "$x $$y $$$z"]" ;#返回:xyz xyz $x puts "xyz is : [subst [subst "$x $$y $$$z"]]" ;#返回:xyz xyz xyz 2.35 第 35 课:改变工作目录- cd & pwd 讲解: 1. 改变工作目录 格式:cd ?dirName? 功能:改变当前目录到 dirName,如果没有 dirName 就改变到当前用户的工作目录$HOME,或者目录 是 ~ ,也是改变到当前用户的工作目录$HOME,如果是 ~ 开头,后面紧跟的字符被解析为 loginid。 2. 显示当前路径:pwd 例子:036_cd_pwd.tcl set dirs [list C:/windows C:/windows/system C:/temp C:/foo ] puts "[format "%-15s %-20s " "FILE" "DIRECTORY"]" foreach dir $dirs { catch {cd $dir} set c_files [glob -nocomplain c*] foreach name $c_files { puts "[format "%-15s %-20s " $name [pwd]]" } } 第 43 页 共 75 页 2.36 第 36 课:调试和错误-errorinfo & errorCode & catch 讲解: 1.错误相关: 序号 命令或变量 描述 1 error message ?info? ?code? 生成一个错误条件,如果 info 或者 code 有值,变量 errorInfo 或 errorCode 就这两个值被初始化 2 catch script ?varName? 执行 script,如果成功返回 TCL_OK,否则返回 TCL_ERROR, 结果存在 varName 中。书上这么说的,但是我的试验结果是 失败 varName 存储的错误信息,成功 varName 存储的 0 3 return ?-code code? ?-errorinfo info??-errorcode errorcode??value? 生成一个返回异常条件 -code :返回状态,可以是: ok,error,无,break -errorinfo:info 是 errorInfo 变量中的第一个字符串 -errorcode:过程设置 errorcode 为全局变量的 errorCode 值 value:过程返回值 4 errorInfo 包含了命令错误信息的全局变量 5 errorCode 包含了命令错误代码的全局变量 例子:037_error.tcl proc errorproc {x} { if {$x > 0} { error "Error generated by error" "Info String for error" $x } } catch errorproc puts "after bad proc call: ErrorCode: $errorCode" ;#这里 errorCode 返回 NONE puts "ERRORINFO:\n$errorInfo\n" set errorInfo ""; catch {errorproc 0} puts "after proc call with no error: ErrorCode: $errorCode" ;#这里 errorCode 返回 NONE,没有错误提示 信息 puts "ERRORINFO:\n$errorInfo\n" catch {errorproc 2} puts "after error generated in proc: ErrorCode: $errorCode" ;#这里 errorCode 返回 2 puts "ERRORINFO:\n$errorInfo\n" 第 44 页 共 75 页 proc returnErr { x } { return -code error -errorinfo "Return Generates This" -errorcode "-999" } catch {returnErr 2} puts "after proc that uses return to generate an error: ErrorCode: $errorCode" ;#这里 errorCode 返回 999 puts "ERRORINFO:\n$errorInfo\n" ;#这里返回的错误信息第一行是设置值,但是后面还有其他 错误信息 proc withError {x} { set x $a } catch {withError 2} puts "after proc with an error: ErrorCode: $errorCode" puts "ERRORINFO:\n$errorInfo\n" catch {open "/no_such_directory/no_such_file" "r"} puts "after an error call to a nonexistent file:" puts "ErrorCode: $errorCode" puts "ERRORINFO:\n$errorInfo\n" 2.37 第 37 课:调试-trace 讲解: 1.调试方法: 序号 命令 描述 1 trace variable variableName operation procname 跟踪变量 variableName operation 可以是: r:read , 读 w:write ,写 u:unset ,取消 当变量有相应操作执行 procname 2 trace vdelete variableName operation procname 取消变量 variableName 相应操作 operation 的跟踪 3 trace vinfo variableName 返回跟踪的变量 variableName 的信息 例子: proc traceproc {variableName arrayElement operation} { set op(w) "Write"; set op(u) "Unset"; set op(r) "Read" ;#定义一个操作数组 第 45 页 共 75 页 set level [info level] incr level -1; if {$level > 0} { set procid [info level $level] } else {set procid "main"} ;#当级别为零,设置为 main,在主程序 中 if {![string match $arrayElement ""]} { ;#判断跟踪的变量是否是数组 puts "TRACE: $op($operation) $variableName($arrayElement) in $procid" } else { puts "TRACE: $op($operation) $variableName in $procid" } } proc testProc {input1 input2} { upvar $input1 i upvar $input2 j set i 2 set k $j; } trace variable i1 w traceproc ;#传递给 traceproc 的三个参数分别是:i1,空,w,当 i1 是数组的时 候,第二个参数会得到数组的 name trace variable i2 r traceproc trace variable i2 w traceproc set i2 "testvalue" puts "\ncall testProc" testProc i1 i2 puts "\nTraces on i1: [trace vinfo i1]" puts "Traces on i2: [trace vinfo i2]\n" trace vdelete i2 r traceproc puts "Traces on i2 after vdelete: [trace vinfo i2]" puts "\ncall testProc again" testProc i1 i2 2.38 第 38 课:命令行参数和环境串 第 46 页 共 75 页 讲解: 1.程序和用户交互的方法有很多,以下是其中四种: ¾ 提示用户输入需要信息; ¾ 读入配置文件; ¾ 从命令行读入参数; ¾ 从环境变量读入 2.命令行参数:argc 得到参数个数,argv0 得到程序自身名字,argv 得到其他参数,注意没有 argv1 这类的全局变量 3.env 是存储着环境变量的数组,对环境变量修改只是当前程序中生效了 例子:039_arg.tcl puts "There are $argc arguments to this script" puts "The name of this script is $argv0" ;#得到程序名 if {$argc > 0} {puts "The other arguments are: $argv" } ;#得到其他的参数 puts "You have these environment variables set:" foreach index [array names env] { puts "$index: $env($index)" } set prompt "\$P" if { [info exists env(PROMPT)] } { puts "$env(PROMPT)" ;#返回 $P$G set env(PROMPT) $prompt ;#设置环境变量为$P,程序执行完后仍然是$P$G,因为它只是在 该程序中生效了 puts "$env(PROMPT)" } 2.39 第 39 课:time & unset 讲解: 1. 得到代码执行的时间 格式:time script?count? 功能:返回执行 script 脚本 count 次的花费时间,注意是消耗时间,并不是 cpu 时间,这里 cpu 时间 指的是在 cpu 上花费的时间片累加,而消耗时间是指等待程序执行完花费时间,通常 cpu 时间更短。 2. 从解释器的名称空间删除一个变量 格式:unset variableName1? varialeName2 ?......? 功能:从解释器的名称空间删除变量。如果变量名是个数组名,则整个数组被删掉,如果是数组中的 第 47 页 共 75 页 某个项,则只删除那个项。如果删除的变量不存在,提示错误。 3.判断一个变量是否存在 existence variableName 例子:040_time.tcl ;#两个过程使用 time 测算运行时间和描述循环的优化,结论是列表比数组对某项的引用速度慢,还有 就是列表和数组在 tcl 中都非常的大 proc timetst1 {lst} { set x [lsearch $lst "20000"] return $x } proc timetst2 {array} { upvar $array a return $a(20000); } ;#做个大数组和大列表 for {set i 0} {$i < 20001} {incr i} { set array($i) $i lappend list $i } ;#观察数组和列表的时间花费 puts "Time for list search: [ time {timetst1 $list} 10]" puts "Time for array index: [ time {timetst2 array} 10]" ;#这个过程测试参数是否存在 proc existence {variable} { upvar $variable testVar; if {[info exists testVar]} { puts "$variable Exists" } else { puts "$variable Does Not Exist" } } set x 1 set y 2 for {set i 0} {$i < 5} {incr i} { set a($i) $i; } puts "\ntesting unsetting a simple variable" 第 48 页 共 75 页 ;# Confirm that x exists. existence x ;# Unset x puts "x has been unset" unset x ;# Confirm that x no longer exists. existence x ;# Do the same for a(0); puts "\ntesting unsetting a member of an array" existence a(0); puts "a0 has been unset" unset a(0); existence a(0); puts "\ntesting unsetting several members of an array, with an error" existence a(3); existence a(4); catch {unset a(3) a(0) a(4)} ;#因为 a(0)已经被删除,会引起错误 puts "\nAfter attempting to delete a(3), a(0) and a(4)" existence a(3) ;# a(3)已经被删除,所以不存在 existence a(4) ;# a(4)由于删除 a(0)时出错,所以并没有被删除,需要注意 puts "\ntesting unsetting an array" existence a; puts "a has been unset" unset a ;#删除整个数组 existence a; 2.40 第 40 课:socket & fileevent & vwait 讲解: 1.socket 服务端开启 格式:socket –server command ?options? port -server :表明开启的服务器端 port:端口 command:当有客户端来连接的时候,执行这个过程,这个过程有三个参数 channel:给新客户端的通道 address:提供给客户端连接的 ip 地址 第 49 页 共 75 页 port:端口 2.客户端连接服务器端 格式:socket ?options? host port host port :客户端连接的服务器 ip 和端口 3.fileevent 定义了一个句柄,满足条件时执行 格式:fileevent channelId readable? script? fileevent channelId writeable? script? readable:当通道 channelId 有数据准备好被读了,执行脚本 script writeable:当通道 channelId 有数据准备好接收数据了,执行脚本 script 4.vwait 命令使执行暂停,直到 varName 被赋值,即便赋值前后相同 格式:vwait varName 例子 1: 环境: 服务器端:10.17.35.110 程序 sockclient01.tcl : set chan [socket 10.17.35.110 12345] ;# Open the connection puts $chan hello ;# Send a string flush $chan ;# Flush the output buffer puts "10.17.35.110:12345 says [gets $chan]" ;# Receive a string close $chan ;# Close the socketServer (IP: 10.17.35.110) 程序 sockserver01.tcl : proc accept {chan addr port} { ;# Make a proc to accept connections puts "$addr:$port says [gets $chan]" ;# Receive a string puts $chan goodbye ;# Send a string close $chan ;# Close the socket (automatically flushes) } ;# socket -server accept 12345 ;# Create a server socket vwait forever ;# Enter the event loop 服务器端情况,首先检查端口 12345 是否被占用,返回为空,没有占用;开始运行后,页面没有返回 信息,停止住: bash-3.00$ netstat -an|grep 12345 第 50 页 共 75 页 bash-3.00$ tclsh sockserver01.tcl 新打开一个连接,检查端口状态,变为了侦听状态 bash-3.00$ netstat -an|grep 12345 *.12345 *.* 0 0 49152 0 LISTEN 客户端连续两次链接上服务器端程序,发送了两次信息,执行情况如下: bash-2.03$ tclsh sockclient01.tcl 10.17.35.110:12345 says goodbye bash-2.03$ tclsh sockclient01.tcl 10.17.35.110:12345 says goodbye 服务器端的返回信息如下: bash-3.00$ tclsh sockserver01.tcl 10.17.34.243:42745 says hello 10.17.34.243:42746 says hello 使用 ctrl+c 终止服务器端的程序后,再次执行客户端,返回错误: bash-2.03$ tclsh sockclient01.tcl couldn't open socket: connection refused while executing "socket 10.17.35.110 12345" invoked from within "set chan [socket 10.17.35.110 12345] " (file "sockclient01.tcl" line 1) 此时检查端口状态,改变为 TIME_WAIT bash-3.00$ netstat -an|grep 12345 10.17.35.110.12345 10.17.34.243.42745 24820 0 49640 0 TIME_WAIT 10.17.35.110.12345 10.17.34.243.42746 24820 0 49640 0 TIME_WAIT 2.41 第 41 课:日期时间-clock 讲解: 1.格式:clock seconds 功能:返回从计算机纪元开始的秒数,不同操作系统开始时间可能不同,所以这个值通常用来作为命 令 clock format 的输入 第 51 页 共 75 页 2.格式:clock format clockValue ?-gmt boolean ? –format string? clockValue:clock clicks 返回 -gmt:设置为 1 或者 true,则设置为格林威治时间,否则为当地时间 -format:将格式转化为可读字符串 序号 格式 描述 1 %a 缩写星期名称,例如:Mon,Tue 等 2 %A 写全星期名称,例如:Monday, Tuesday 等 3 %b 缩写月名称,例如:Jan, Feb 等 4 %B 写全月名称,例如:January, February 等 5 %d 月的第几日 6 %j 儒略日 7 %m 月数(01-12) 8 %y 世纪年 9 %Y 四位年 10 %H 小时(00-23) 11 %I 小时(00-12) 12 %M 分钟(00-59) 13 %S 秒(00-59) 14 %p PM 或 AM 15 %D 日期格式:%m/%d/%y 16 %r 时间格式:%I:%M:%s %p 17 %R 时间格式:%I:%M 18 %T 时间格式:%I:%M:%S 19 %Z 时区名称 3.功能:将可读的时间串转换成机器时钟,和 clock seconds 返回的一样 格式:clock scan dateString dateString 格式可以为: time : =hh:mm:ss ?meridian ?zone? =hhmm ?meridian?zone? meridian:AM 或 PM hh:如果没有特别说明,hh 指的 24 小时制 zone:为三个字母描述的时区,如 EST,PDT 等 date :=mm/dd/yy =mm/dd =monthname dd,yy =monthname dd =dd monthname yy =dd monthname =day, dd monthname yy 第 52 页 共 75 页 例子:042_time_date.tcl ;# 得到系统秒数 set systemTime [clock seconds] ;# 使用时间格式显示 puts "The time is: [clock format $systemTime -format %H:%M:%S]" ;# 使用日期格式显示 puts "The date is: [clock format $systemTime -format %D]" ;# 使用复杂的日期格式显示 puts [clock format $systemTime -format {Today is: %A, the %d of %B, %Y}] ;# 没有使用格式输出 puts "\n the default format for the time is: [clock format $systemTime]\n" ;# 得到两个时间,进行相减 set halBirthBook "Jan 12, 1997" set halBirthMovie "Jan 12, 1992" set bookSeconds [clock scan $halBirthBook] set movieSeconds [clock scan $halBirthMovie] puts "The book and movie versions of '2001, A Space Oddysey' had a" puts "discrepency of [expr $bookSeconds - $movieSeconds] seconds in how" puts "soon we would have sentient computers like the HAL 9000" 2.42 第 42 课:i/o 通道-fblocked & fconfig 讲解: 1.fblocked 用于检查通道中是否有有效输入。当非阻塞(non0blocking)方式工作的时候,需要确定通 道中是否有可用数据,或者判断通道是否被关闭。 2.对通道进行配置 格式:fconfigue channel ?param1 ?value1 ?param2 ?value2 如果只有单个参数,则参数值被返回; 如果提供了参数和值,则对参数进行设置 参数设置包括: -blocking:设置工作模式是否阻塞,当通道中的数据不能被读写的时候(读的时候没有数据,写的时候 缓冲区满) -buffersize:设置缓冲区的大小,整型:10-1000000 -translation:设置输出结束符 第 53 页 共 75 页 =auto:自动,换行,回车,回车换行等都作为结束符 =binary:换行作为结束符 =cr:回车作为结束符 =crlf:回车换行作为结束符 =lf:换行作为结束符(UNIX 标准) 例子: ;# 当客户端连接服务器端时,执行 serverOpen proc serverOpen {channel addr port} { puts "channel: $channel - from Address: $addr Port: $port" ;#得到客户端连接时提供 的 ip 和端口 puts "The default state for blocking is: [fconfigure $channel -blocking]" ;#得到是否阻塞模式 puts "The default buffer size is: [fconfigure $channel -buffersize ]" ;#得到缓冲区大小 ;# Set this channel to be non-blocking. fconfigure $channel -blocking 0 ;#设置为非阻塞模式 set bl [fconfigure $channel -blocking] puts "After fconfigure the state for blocking is: $bl" ;# Change the buffer size to be smaller fconfigure $channel -buffersize 12 ;#修改缓冲区大小 puts "After Fconfigure buffer size is: [fconfigure $channel -buffersize ]\n" ;# When input is available, read it. fileevent $channel readable "readLine Server $channel" ;#注册函数 readLine,当通道中有数据可读的 时候执行函数 } ;# A proc to read a line from a channel proc readLine {who channel} { global didRead global blocked puts "There is input for $who on $channel" set len [gets $channel line] ;#读数据 set blocked [fblocked $channel] ;#得到通道是否阻塞 puts "Characters Read: $len Fblocked: $blocked" if {$len < 0} { ;#没有读到数据 if {$blocked} {puts "Input is blocked" ;#阻塞了 第 54 页 共 75 页 } else { puts "The socket was closed - closing my end" close $channel ;#客户端关闭连接, 非阻塞状态, 服务器端关闭 通道 } } else { puts "Read $len characters: $line" ;#打印读出的数据 puts $channel "This is a return" flush $channel ;#给通道一个返回信息 } incr didRead; puts "didRead : $didRead" } ;# Set up a server to listen on port 33000 set server [socket -server serverOpen 33000] ;#建立服务器端 after 120 update; # This kicks MS-Windows machines for this application,更新事物 ;# connect to port 33000 set sock [socket 127.0.0.1 33000] ;#建立客户端 set bl [fconfigure $sock -blocking] set bu [fconfigure $sock -buffersize] puts "Original setting for sock: Sock blocking: $bl buffersize: $bu" fconfigure $sock -blocking No fconfigure $sock -buffersize 8; set bl [fconfigure $sock -blocking] set bu [fconfigure $sock -buffersize] puts "Modified setting for sock: Sock blocking: $bl buffersize: $bu\n" # Send a line to the server -- NOTE flush set didRead 0 puts -nonewline $sock "A Test Line" flush $sock ;#这里触发了 readLine,但是因为没有行结束符,读了个 空,并且等待继续读,阻塞了,但是 didRead 被加成了一 # Loop until two reads have been done. while {$didRead < 2} { ;# Wait for didRead to be set vwait didRead if {$blocked} {puts $sock "Newline" ; flush $sock; puts "SEND NEWLINE"} ;#这里触发 readLine,读 第 55 页 共 75 页 到了:A Test LineNewline,读到值后,状态变成了不阻塞,didRead 被加成了二,所以这个循环只执行了一次 } ;# Read the return, and display it. set len [gets $sock line] puts "Return line: $len -- $line" close $sock ;#这里触发了 readLine,等待服务器端通道关闭后再关闭服务器端 连接 vwait didRead catch {close $server} 2.43 第 43 课:子解释器 讲解: 1.子解释器相关命令 序号 命令 描述 1 interp create ?-safe ?name? 常见一个子解释器,返回名字 -safe:使子解释器不能存取某些危险的系统工具 2 interp delete name 按名字删除掉子解释器 3 interp eval args 在子解释器中执行 args 4 interp alias srcPath srcCmd targetPath targetCmd ?arg arg? 这个命令使子解释器和主解释器可以共享代码 例子: set i1 [interp create firstChild] set i2 [interp create secondChild] puts "first child interp: $i1" puts "second child interp: $i2\n" ;#在两个子进程中分别设置 name 变量和过程 nameis foreach int [list $i1 $i2] { interp eval $int [list set name $int] interp eval $int {proc nameis {} {global name; return "nameis: $name";} } } foreach int [list $i1 $i2] { interp eval $int "puts \"EVAL IN $int: name is \$name\"" puts "Return from 'nameis' is: [interp eval $int nameis]" } 第 56 页 共 75 页 ;#主进程中定义 proc rtnName {} { global name return "rtnName is: $name" } interp alias $i1 rtnName {} rtnName ;#在子进程 i1 中,定义 rtnName {} 的别名为 rtnName, 但是这 个过程中使用了主进程的 name 变量,子进程中没有这个变量,引起错误 puts "" puts "firstChild reports [interp eval $i1 rtnName]" 2.44 第 44 课:数据库操作 讲解: 1.这个例子是在 unix 下运行通过的,附带的说明已经非常详细了,所以不再另做讲解 例子:045_db.tcl package requir Oratcl ;# 也可以使用类似:Load libOratcl25.so 命令替代第一行,不过要先确定库文件是否存在于库路径中 ;# 读取表的内容,通过列表结构返回 proc selectSql { conStr sqlStr } { ;#设置返回变量 set lstRet [list] ;#登录数据库 set conHandle [oralogon $conStr] ;#打开事物,可以打开多个 set conTran [oraopen $conHandle] ;#解析 sql 语句或者 plsql 语句,如果包含绑定变量,使用:开头 oraparse $conTran $sqlStr ;#执行 sql 命令,注意这里参数是事物 oraexec $conTran -commit 第 57 页 共 75 页 ;#一行一行的取回结果,存入返回列表 orafetch $conTran -datavariable oneRow while {[oramsg $conTran rc] == 0} { ;#返回零为执行正确 ;#puts "oneRow: $oneRow" ;#打出来看看每一行的内容 lappend lstRet $oneRow orafetch $conTran -datavariable oneRow } ;#关闭事物 oraclose $conTran ;#中断数据库连接 oralogoff $conHandle ;#返回结果 return $lstRet } ;# 执行 sql 语句,如果出错返回错误提示 proc executeSql { conStr sqlStr } { ;#登录数据库 set conHandle [oralogon $conStr] ;#打开事物,可以打开多个 set conTran [oraopen $conHandle] ;#解析 sql 语句或者 plsql 语句,如果包含绑定变量,使用:开头 set errMsg "" ;#检查记录执行结果 set err [catch {oraparse $conTran $sqlStr} errMsg] ;# 如果成功返回 TCL_OK(0),否则返回 TCL_ERROR(1) if {$err == 1} { ;#解析失败就不执行命令了 } else { oraexec $conTran -commit ;#执行 sql 命令,注意这里参数是事物 } ;#关闭事物 oraclose $conTran ;#中断数据库连接 oralogoff $conHandle ;#返回错误信息,为空则命令执行成功 return $errMsg 第 58 页 共 75 页 } set conStr "zk/zk@OPENBOSS_146" set sqlStr "select * from cm_user where rownum<5" ;# 演示调用第一个函数 set lstTable [selectSql $conStr $sqlStr] set lstRow [llength $lstTable] puts "lstRow : $lstRow" for {set i 0} {$i<$lstRow} {incr i} { ;#puts "$i : [lindex $lstTable $i]" ;#一次显示一行 ;#一个一个显示出来 puts "$i :" ;#打印行号 foreach data [lindex $lstTable $i] { puts -nonewline "$data " ;#-nonewline 不让换行 } puts "" ;#换行 } ;# 演示调用第二个函数 set sqlStr "select * from cm_user where rownum<5" set errMsg [executeSql $conStr $sqlStr] if { $errMsg != "" } { puts "errMsg: $errMsg" } 2.45 第 45 课:函数或过程数组的输入和输出方法 讲解: 1. 参看<第 23 课:更多数组相关>,函数中数组的输入使用 upvar 的例子; 2. 函数输出数组使用方法:return [ array get op2 ] 例子:045_db.tcl proc tryarrupvar { op1 } { upvar $op1 op2 set op2(1) 2222 return [ array get op2 ] } set arr1(1) 1111 第 59 页 共 75 页 array set arr2 [ tryarrupvar arr1 ] puts "The current arr2 is: $arr2(1)" ;# 打印出 2222 以下是 bidz 添加内容 2.46 第 46 课:info 的用法 info cmdcount 返回当前的解释器已经执行的命令的个数。 info commands info commands pattern 如果不给出模式,返回所有的命令的列表,内建和自建的。 模式是用 C Shell 匹配风格写成的。 info complete command 检查名是否完全,有无错误。 info default procname arg varname procname 的参数 arg,是否有缺省值。 info exists varName 判断是否存在该变量。 info globals info globals pattern 返回全局变量的列表,模式同样是用 C Shell 风格写成的。 info hostname 返回主机名。 info level info level number 如果不给参数 number 则返回当前的在栈中的绝对位置,参 见 uplevel 中的描述。如加了参数 number,则返回一个列表包 含了在该 level 上的命令名和参数。 info library 返回标准的 Tcl 脚本的可的路径。实际上是存在变量 tcl_library 中。 info locals info locals pattern 第 60 页 共 75 页 返回 locale 列表。 info procs info procs pattern 返回所有的过程的列表。 info script 返回最里面的脚本(用 source 来执行)的文件名。 info tclversion 返回 Tcl 的版本号。 info vars info vars pattern 返回当前可见的变量名的列表。 2.47 第 47 课:多线程 tcl 中多线程通常需要另外安装模块,此处的例子是使用的 thread2.6.5 1.相关知识点 序号 命令或变量 描述 1 thread::join $id 将某个线程和主程序关联,只有这个线程结束了,主程序才能结束 2 thread::send $id operation 将操作(比如一个函数)发送给某个线程执行 3 thread::release $id 终止某个线程 4 thread::wait 某个线程进入等待状态,直到被主进程中的代码 release 释放 5 thread::errorproc logError 设置 logError 函数进行线程错误处理 6 tsv::set var1 pwd [pwd] 设置线程间共享变量,将 pwd 变量和 pwd 变量的值[pwd]写入数组 var1 中 7 thread::transfer $id $fid 将共享变量传递给某个线程 8 set mutex [tsv::set tasks mutex [thread::mutex create]] 创建锁 mutex 9 thread::mutex lock $mutex 锁 10 thread::mutex unlock $mutex 解锁 11 thread::mutex destroy $mutex 删除锁 12 set cond [tsv::set tasks cond [thread::cond create]] 创建条件变量 13 thread::cond notify $cond 通知条件变量 第 61 页 共 75 页 2.关于 join 的使用 创建了几个线程,如果不做任何处理,主程序就会直接退出,很容易想到,可以在程序的结尾写一个 循环,不停的去读现在还有几个线程,当线程数变为一(就是还剩一个主线程)的时候,就可以退出主程序 了。 package require Thread puts "*** I'm thread [thread::id]" # Create 3 threads for {set thread 1} {$thread <= 3} {incr thread} { set id [thread::create { # Print a hello message 3 times, waiting # a random amount of time between messages for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Thread [thread::id] says hello" } }] ;# thread::create puts "*** Started thread $id" } ;# for puts "*** Existing threads: [thread::names]" # Wait until all other threads are finished while {[llength [thread::names]] > 1} { after 500 } puts "*** That's all, folks!" 看看返回结果: *** I'm thread tid1 *** Started thread tid3 *** Started thread tid4 *** Started thread tid5 第 62 页 共 75 页 *** Existing threads: tid5 tid4 tid3 tid1 Thread tid3 says hello Thread tid4 says hello Thread tid5 says hello Thread tid3 says hello Thread tid3 says hello Thread tid5 says hello Thread tid4 says hello Thread tid5 says hello Thread tid4 says hello *** That's all, folks! 这种写法显然很土,于是就引入了 join,使用 thread::join $id,将生成的线程一个个和主程序关联,于 是主程序就会等待这些线程都执行完了才会结束 package require Thread puts "*** I'm thread [thread::id]" # Create 3 threads for {set thread 1} {$thread <= 3} {incr thread} { set id [thread::create -joinable { # Print a hello message 3 times, waiting # a random amount of time between messages for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Thread [thread::id] says hello" } }] ;# thread::create puts "*** Started thread $id" lappend threadIds $id } ;# for puts "*** Existing threads: [thread::names]" 第 63 页 共 75 页 # Wait until all other threads are finished foreach id $threadIds { thread::join $id } puts "*** That's all, folks!" 返回情况: *** I'm thread tid1 *** Started thread tid3 *** Started thread tid4 *** Started thread tid5 *** Existing threads: tid5 tid4 tid3 tid1 Thread tid3 says hello Thread tid4 says hello Thread tid5 says hello Thread tid5 says hello Thread tid4 says hello Thread tid5 says hello Thread tid3 says hello Thread tid3 says hello Thread tid4 says hello *** That's all, folks! 3.thread::send $id oper 的使用和错误捕捉 thread::send $id oper 就是将命令交给某个线程去执行 package require Thread set t [thread::create] ;# Create a thread set myX 42 ;# Create a variable in the main thread # Copy the value to a variable in the worker thread thread::send $t [list set yourX $myX] # Perform a calculation in the worker thread thread::send $t {expr { $yourX / 2 } } puts "catch error :" 第 64 页 共 75 页 catch {thread::send $t {expr { $yourX / 0 } } } ret puts "ret : $ret" puts "errorInfo : $errorInfo" 返回情况: catch error : ret : divide by zero errorInfo : divide by zero while executing "expr { $yourX / 0 } " invoked from within "thread::send $t {expr { $yourX / 0 } } " 线程的异步 send 的执行方法: thread::send -async $t [list ProcessValues $vals] result vwait result 4.线程的释放 thread::release $id package require Thread puts "*** I'm thread [thread::id]" # Create 3 threads for {set thread 1} {$thread <= 3} {incr thread} { set id [thread::create -joinable { # Print a hello message 3 times, waiting # a random amount of time between messages for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Thread [thread::id] says hello" } }] ;# thread::create puts "*** Started thread $id" lappend threadIds $id 第 65 页 共 75 页 } ;# for puts "*** Existing threads: [thread::names]" # Wait until all other threads are finished foreach id $threadIds { thread::release $id } puts "*** That's all, folks!" 返回情况: *** I'm thread tid1 *** Started thread tid3 *** Started thread tid4 *** Started thread tid5 *** Existing threads: tid5 tid4 tid3 tid1 *** That's all, folks! 可以看到各个线程连 hello 都没有说就结束了 5. 其他的命令用法 ¾ thread::wait :线程进入等待状态,必须主程序中有代码 release 该线程才能结束 set t [thread::create { puts "Starting worker thread" thread::wait # This is executed after the thread is released puts "Exiting worker thread" }] ¾ thread::errorproc logError :定义一个函数来进行线程的错误处理 set errorFile [open errors.txt a] proc logError {id error} { global errorFile puts $errorFile "Error in thread $id" puts $errorFile $error puts $errorFile "" 第 66 页 共 75 页 } thread::errorproc logError ¾ tsv::set var1 pwd [pwd] :线程间共享一个变量 thread::transfer $id $fid :将共享变量传递给某个线程 上面两个命令适用场景,比如:多个线程需要将日志写入同一个日志文件,先创建一个共享变量$fid, 某个线程需要写日志的时候,就使用 transfer 将共享变量传递给该线程 set fid [open myfile.txt r] # ... set t [thread::create] thread::transfer $t $fid # 注意:这是拷贝了一个$fid 给线程 $t thread::send $t [list set fid $fid] ¾ 锁和条件的使用 set mutex [tsv::get tasks mutex] set cond [tsv::get tasks cond] # 测试 predicate 是否为 true 前先锁上 thread::mutex lock $mutex while {![tsv::get tasks predicate]} { #等待通知 thread::cond wait $cond $mutex } # predicate 已经被其他线程修改为 true,可以解锁了 thread::mutex unlock $mutex 6.其实以上的内容看懂就行了,真正的应用自然是使用成熟的线程池,没必要自己去编写程序。在 中有 tpool.tcl ,真正需要做的是使用该程序。 线程池 序号 命令或变量 描述 1 tpool::create ?options? 创建一个线程池,返回线程池的 id 2 tpool::post tpoolId script 将 script 脚本交给线程池 tpoolID 去执行 3 tpool::wait tpoolId jobList ?varName? 等待 joblist 中的线程完成,varname 中存放 joblist 中还没有执行完 的线程 id 4 tpool::get tpoolId jobId 返回一个线程池中任务的执行结果 第 67 页 共 75 页 5 tpool::names 返回一个存在的线程池 ID 列表 6 tpool::preserve tpoolId 标记线程池 7 tpool::release tpoolId 释放 preserver 标记的线程池数量,如果该值为零或者其他值,意味 着线程池终止了 线程池的配置选项: 序号 命令或变量 描述 1 -minthreads number 最小线程数量,缺省为零 2 -maxthreads number 最大线程数量,缺省为四 3 -idletime seconds 空闲时间(秒),一个工作线程退出前最大的空闲时间,缺省为零, 意味着空闲的线程一直等下去 4 -initcmd script 新建的工作线程执行的脚本 5 -exitcmd script 一个工作线程退出前执行的脚本 例子 1: package require Thread # 从 list l 中删除 list v 中的元素 proc lremove { l v } { foreach i $v { set ind [lsearch $l $i] if { $ind == -1 } { continue } set indm1 [expr {$ind-1}] set indp1 [expr {$ind+1}] set l [concat [lrange $l 0 $indm1] [lrange $l $indp1 end]] } return $l } set tpoolID [tpool::create -minworkers 2 -maxworkers 4 -idletime 20 -initcmd { for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Say hello" } }] set joblist {} set nodone {} lappend joblist [tpool::post $tpoolID { for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] 第 68 页 共 75 页 puts "Say World" } }] lappend joblist [tpool::post $tpoolID { for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Say Good" } }] while 1 { set f [tpool::wait $tpoolID $joblist] puts "1: joblist : $joblist f : $f" set joblist [lremove $joblist $f] puts "2: joblist : $joblist f : $f" if { [llength $joblist] == 0 } { break } after 100 } tpool::release $tpoolID 返回: Say hello Say hello Say hello Say hello Say hello Say hello Say Good Say World Say World Say Good Say World 1: joblist : 1 2 f : 1 2: joblist : 2 f : 1 Say Good 1: joblist : 2 f : 2 2: joblist : f : 2 第 69 页 共 75 页 此时如果修改: set tpoolID [tpool::create -minworkers 1 -maxworkers 4 -idletime 20 -initcmd { 返回仍然为: Say hello Say hello Say hello Say World Say hello Say World Say hello Say World Say hello 1: joblist : 1 2 f : 1 2: joblist : 2 f : 1 Say Good Say Good Say Good 1: joblist : 2 f : 2 2: joblist : f : 2 明明上次执行的时候删除了 tpool,怀疑该线程池的选项仍然有效,因为不太可能使用这种方式去执行 代码,所以也就没有深究了。而且多执行几次,就会出现 core,不过好在都是 core 在 release 的时候,也 就暂时没有细查了。哪位仁兄有空帮忙查查原因。 例子 2: 如果要给线程指定执行相应的函数,要注意函数定义的位置,一般定义在线程池创建的 initcmd 后面 package require Thread # 从 list l 中删除 list v 中的元素 proc lremove { l v } { foreach i $v { set ind [lsearch $l $i] if { $ind == -1 } { continue } set indm1 [expr {$ind-1}] set indp1 [expr {$ind+1}] set l [concat [lrange $l 0 $indm1] [lrange $l $indp1 end]] } return $l } 第 70 页 共 75 页 set tpoolID [tpool::create -minworkers 1 -maxworkers 4 -idletime 20 -initcmd { proc sayWhat {word1 {num 3}} { for {set i 1} {$i <= $num} {incr i} { after [expr { int(500*rand()) }] puts "Says $word1" } return 0 } }] set joblist {} set nodone {} lappend joblist [tpool::post $tpoolID { set ret [sayWhat " world" 8] }] lappend joblist [tpool::post $tpoolID { for {set i 1} {$i <= 3} {incr i} { after [expr { int(500*rand()) }] puts "Say Good" } }] while 1 { set f [tpool::wait $tpoolID $joblist] puts "1: joblist : $joblist f : $f" set joblist [lremove $joblist $f] puts "2: joblist : $joblist f : $f" if { [llength $joblist] == 0 } { break } puts "after" after 100 } puts "release" tpool::release $tpoolID 第 71 页 共 75 页 返回信息: Says world Say Good Says world Say Good Says world Say Good 1: joblist : 1 2 f : 2 2: joblist : 1 f : 2 after Says world Says world Says world Says world Says world 1: joblist : 1 f : 1 2: joblist : f : 1 release 2.48 第 48 课:解析 xml 用于解析 xml 的扩展颇多,此处选择的是:tDOM-0.8.2,下载自:http://www.tdom.org/ 解包:gunzip tDOM-0.8.2.gz ; mv tDOM-0.8.2 tDOM-0.8.2.tar ; tar xvf tDOM-0.8.2.tar 注意:明明是 tar 文件,但是 gz 解包后居然没有扩展名,使用命令查看文件类型: bash-3.00$ file tDOM-0.8.2 tDOM-0.8.2.tar: USTAR tar archive 本想列出细节的命令解释,后来发现意义不大,只在此处写了两个常用的例子,如果需要更细节的内 容,请查找 tDOM 的教程。 例子 1: 程序:seekxml01.tcl package require tdom set xml { 第 72 页 共 75 页 James Bond Still attractive Male Austin Powers Depends on your timeline Yes, please } set dom [dom parse $xml] set doc [$dom documentElement] puts "Agent: [$doc selectNodes {string(/agents/agent[@id='013']/@id)}]" puts "First Name: [$doc selectNodes {string(/agents/agent[@id='013']/name[@type='first'])}]" puts "Last Name: [$doc selectNodes {string(/agents/agent[@id='013']/name[@type='last'])}]" puts "Age: [$doc selectNodes {string(/agents/agent[@id='013']/age)}]" 执行情况: bash-3.00$ tclsh seekxml01.tcl Agent: 013 First Name: Austin Last Name: Powers Age: Depends on your timeline 例子 2: 程序:seekxml02.tcl package require tdom set rf [tDOM::xmlReadFile "seekxml02.xml"] set doc [dom parse $rf] puts "Agent: [$doc selectNodes {string(/agents/agent[@id='013']/@id)}]" puts "First Name: [$doc selectNodes {string(/agents/agent[@id='013']/name[@type='first'])}]" puts "Last Name: [$doc selectNodes {string(/agents/agent[@id='013']/name[@type='last'])}]" puts "Age: [$doc selectNodes {string(/agents/agent[@id='013']/age)}]" 第 73 页 共 75 页 相关文件: seekxml02.xml : James Bond Still attractive Male Austin Powers Depends on your timeline Yes, please 执行情况: bash-3.00$ tclsh seekxml02.tcl Agent: 013 First Name: Austin Last Name: Powers Age: Depends on your timeline 第 74 页 共 75 页 3 跋 经过三周的辛苦劳动,终于收笔了。如有错误之处,望不吝赐教。若是能给各位的 tcl 学习和工作上 tcl 的应用带来便利,将是我最大的荣幸。
还剩74页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 11 人已下载

下载pdf

pdf贡献者

kappakappa

贡献于2014-05-28

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf