TCL脚本语言

1、什么是Tcl?

Tcl 全称是Tool command Language。它是一个基于字符串的命令语言,基础结构和语法非常简单,易于学习和掌握。
Tcl 语言是一个解释性语言。所谓解释性是指不象其他高级语言需要通过编译和联结,它象其他shell 语言一样,直接对每条语句顺次解释执行。
Tcl 数据类型简单。对Tcl 来说,它要处理的数据只有一种——字符串。Tcl 将变量值以字符串的形式进行存储,不关心它的实际使用类型。
Tcl 的执行是交互式的。Tcl 提供了交互式命令界面,界面有两种:tclsh和wish。tclsh只支持Tcl 命令,wish支持Tcl 和Tk命令。通过交互界面,我们就可以象执行UNIX shell 命令一样,逐条命令执行,并即时得到执行结果。
Tcl/Tk可以提供跨平台的支持。Tcl 语言可以运行于绝大多数当今流行的UNIX、WINDOWS和Macintosh等系统上,而且命令通用,只是启动的细节有些不同。
Tcl/Tk与C/C++的良好兼容性。Tcl/Tk 脚本可以很好的集成到C/C++程序中。

2、Tcl脚本创建与执行

2.1.Tcl脚本创建

#用文本编辑器创建一个文件,名为HelloWorld.tcl 并保存到目录
#输入如下一条命令后保存、关闭文件:
#puts “hello”
#!/usr/bin/tclsh
puts "hello"

2.2. Tcl脚本执行

在Linux环境中,最普遍的编写可执行应用程序的方法就是用“ #!” 机制。用它生成的启动脚本如下:

#!/usr/bin/tclsh

本启动脚本告诉Linux shell,用wish来运行剩余的脚本。选项-f是用来支持3.6以下的Tk版本脚本的。这种启动脚本需要提供wish或tclsh的绝对路径名。

[Angel@linux ~]$ ./HelloWorld.tcl
hello

3、输出、赋值与替换

1.输出puts
Tcl 的输出命令是“ puts” ,它会将字符串输出到标准输出channel Id。

[Angel@linux ~]$ vim HelloWorld.tcl
#!/usr/bin/tclsh
puts "Hello World!!!"

[Angel@linux ~]$ chmod 755 HelloWorld.tcl
[Angel@linux ~]$ ./HellowWorld.tcl
Hello World!!!

2.赋值 set/unset
set是变量定义和赋值命令。在定义的时候,也不必指定变量值的类型,因为变量值的类型就一种——字符串。执行set命令为变量赋值时,会在内存中为变量开辟一段内存空间。
unset命令与set命令作用相反,取消变量定义,并释放变量所占的内存空间。通过前置“ $” 符,可以引用变量的值(替换)。
set命令也可以只跟一个变量,如果此变量已经定义,会返回变量值,效果和puts类似。

[语法] set varName [value] / unset varName

例如:

[Angel@linux ~]$ vim set.tcl
#!/usr/bin/tclsh
set Clock 800MHz                            #变量赋值
set Data 55aa                               #数据Data第一次赋值

puts "Clock is $Clock, Data is $Data."      #打印输出
unset Data                                  #取消赋值
set Data 7788                               #数据Data第二次赋值
puts "Data is $Data."                       #打印输出

[Angel@linux ~]$ ./set.tcl
Clock is 800MHz, Data is 55aa.
Data is 7788.

3.替换 $,[],{}
1) $ 符可以实现引用替换操作,用以引用参数值。Tcl 对替换只进行一遍解释,对嵌套的 “$”不于理睬。replace.tcl如下:

#!/usr/bin/tclsh
set rxdata txdata
set Data1 rxdata                                #变量赋值
set Data2 $$Data1                               #数据Data第一次赋值

puts "rxdata is $rxdata, Data1 is $Data1, Data2 is $Data2."  #打印输出

脚本执行如下:
可以看到Data2并没有二次解析$rxdata,而是直接打印输出

[Angel@linux ~]$ ./replace.tcl
rxdata is txdata, Data1 is rxdata, Data2 is $rxdata     

2).[]方括号
方括号“ []” 用来完成命令替换。用“ []” 将一条命令括起来,命令执行完成后,会返回命令执行的输出结果。

#!/usr/bin/tclsh
set b [set a 5]     #将set a 5命令输出的结果赋给b
set c [expr 5*10]
puts " b is $b\n C is $c"

[Angel@linux ~]$ ./replace.tcl
b is 5  
C is 50   

3)." " {}
双引号和花括号将多个单词组织成一个参数。也是一种替换操作。对于" “和{}内的替换又如何处理呢?一般的原则是在” "内的替换正常进行,而在{}内的替换有可能会被阻止。

对花括号内的替换操作可以概括为:如果花括号是用做替换操作,则它会阻止内部的嵌套替换,如果花括号用做界限符,如过程定义时用做界限过程体时,不阻止替换操作,这些例子如if条件语句、循环语句、switch语句和过程声明、数学表达式等。{ }的作用比较特殊,需要根据不同的情况区别处理。

#!/usr/bin/tclsh
set b [set a 5]               #将set a 5命令输出的结果赋给b
set c [expr 5*10]
puts " b is $b\n {C is $c}"   # 花括号阻止替换

可以看到变量打印的C 仍为$c 并没有替换为50

[Angel@linux ~]$ ./replace.tcl
b is 5  
C is $c  

4、数学表达式与expr

4.1 数学与逻辑运算符

尽管Tcl 是基于字串操作的,但它仍旧提供了有效数学运算和逻辑运算的功能。通过命令expr可以实现对数学表达式的分析和计算。如表4.1
在这里插入图片描述

4.2 数学函数

如表4.2 数学函数
在这里插入图片描述
在这里插入图片描述

4.3 例子

math.tcl脚本如下:

#!/usr/bin/tclsh
set a 10
set b 5
set c [exper $a+$b]
set d [expr $a/$b]
puts "c is $c, d is $d...."

脚本执行结果:

[Angel@linux ~]$ ./math.tcl
c is 15, d is 2....

4.4 incr命令

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

#!/usr/bin/tclsh
set a 0
while {
    
    1} {
    
    
if {
    
     $a < 5 } {
    
    
	incr a          #默认变量+1
	puts "A = $a"   #打印输出新值
} else {
    
    
	break           #跳出while循环
}

}

脚本执行结果如下:

[Angel@linux ~]$ ./incr.tcl
A = 1
A = 2
A = 3
A = 4
A = 5

5、字符串string

5.1 基本命令集

Tcl 将所有的变量值视作字符串,并将他们作为字符串来保存。下表列出了字符串操作的几个常用命令。
表 4-1 比较有用的字符串操作命令
在这里插入图片描述
本章主要讲述append、format、scan、binary、subst、string等命令。regexp、regsub在正则表达式一章讲述。

5.2 append

append 命令比较简单,它将一段字符串连接到另一字符串尾部从而组成新的字符串。此命令对变量直接修改

#!/usr/bin/tclsh
set A hello
set B world
append A $B
puts "welcome to $A"

脚本执行结果如下:

[Angel@linux~]$ ./append.tcl
welcome to helloworld

5.3 format

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

语法:format spec value1 value2

spec变元包含了格式说明关键词和附加文字。使用%来引入一个关键词,后跟0个或者多个修饰符,然后使用一个转换格式符结尾。

关键词的基本格式是“ %aaaB”:aaa是修饰符,B代表一种转换格式符。例如%f用于将对应位置的参数转化为浮点数。如果要使用”%”号,则可以使用%%来实现。否则会将%后的字符作为关键词来处理。
valueX是变元。对每个变元来讲,其关键词可多达6部分:

  • 位置说明符
  • 标志
  • 字段宽度
  • 精度
  • 长度
  • 转换符

格式转换符:
在这里插入图片描述
格式标志符:
在这里插入图片描述
位置说明符i$表示从第i个变元取数值而不是根据通常的位置对应关系对应的变元。位置记数从1开始。
format例子:

#!/usr/bin/tclsh
set a 20
set b [format "%x" $a]           #将20转换为16进制
set c [format "%#08x" $a]        #将20转换为十六进制数,并添加前缀"0x"
								 #总宽度为8为,右对齐(默认),前导空格用0补齐
puts "The hexa value of A = $b"
puts "The hexa value of C = $c"

执行结果:

[Angel@linux ~] ./format.tcl
The hexa value of A = 14
The hexa value of C = 0x00000014

5.4 scan

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

[语法] scan string format var ? var ?

  • scan的格式描述几乎与format相同,但不用%u格式。%c的作用与format中的相反,是将一个ASCII 字符转换为对应的整数值
  • 应该注意scan命令中位置顺序和format中的不一样。format将多个目标变量转换成一个字符串,而scan则可将一个字符串分解为多个变量
  • scan格式包含有一种集合的概念。它使用方括弧来界定一组字符,这个集合匹配拷贝字符串的一个或多个字符到目的变量中去。这种集合的概念在以后的regexp正则表达式中应用更多。
  • 如果 scan命令中,没有指定输出变量,则它不返回成功转换个数,而是返回成功转换的结果,见下例说明。

scan例子:

#!/usr/bin/tclsh
set num [scan "abcABC" "%c%c"]
puts "The ASCII code is $num"

执行结果:

[Angel@linux ~] ./scan.tcl
The ASCII code is 97 98

5.5 string

字符串是Tcl 中的基本数据类型,所以有大量的字符串操作命令。一个比较重要的问题就是模式匹配,通过模式匹配将字符串与指定的模式(格式)相匹配来进行字符串的比较、搜索等操作。

常用的字符串操作有:

  • string match:字符串匹配(或者比较)
  • 大小写转换:tolower和totopper以及totile
  • equal 操作
  • string compare
  • string range
  • string replace

5.5.1 string常用命令

在这里插入图片描述
在这里插入图片描述

5.5.2 string字符串例子

1.string compare and equal
注意命令返回值:string compare在不同的情况下返回1,相同返回0。而string equal 和string match则恰恰相反,相等或者匹配时返回1 ,不同返回0。

#!/usr/bin/tclsh
set string1 abc
set string2 def
set string3 abc
#string compare
if {
    
     [string compare $string1 $string2] == 0 } {
    
    
	puts "string1 is same as string2"
} else {
    
    
	puts "string1 isn't same as string2"
}
#string equal
if {
    
     [string equal $string1 $string3] } {
    
    
	puts "string1 is same as string3"
} else {
    
    
	puts "string1 isn't same as string3"
}

执行结果:

[Angel@linux ~] ./string.tcl
string1 isn't same as string2
string1 is same as string3

2.string match
string match 命令沿用了各类UNIX shell 中所使用的文件名模式匹配机制。下表给出了匹配模式的三种结构。
在这里插入图片描述
例子:

set string1 abc
set string2 def
set string3 ab
#string match *
if {
    
     [string match a* $string1] == 1 } {
    
    
	puts "string is matched "
} else {
    
    
	puts "string isn't matched"
}
#string match ??   
#一个"?"对应一个字符,为了匹配两个字符,必须输入两个问号。
if {
    
     [string match ?? $string3] } {
    
         
	puts "string is matched string3"
} else {
    
    
	puts "string1 isn't matched string3"
}

# string match 以a或者b开头的字符串
if {
    
     [strimg match [ab]* bell] == 1 } {
    
    
	puts "string is matched"
} else {
    
    
	puts "string is not matched"
}

执行结果:

[Angel@linux~]$ ./string.tcl
string is matched
string is matched string3
string is matched

3.string replace
string replace可以用新的字符串代替字符串中指定范围内的字符,如果没有指定新字符串,则指定范围内的字符都会被删除。字符个数从0开始,另外注意:替换不改变原来字符串变量的值,只是返回更改后的新字符串

#!/usr/bin/tclsh
set var1 welcome
set var2 china
set var3 [string replace $var2 0 4 America]
set var4 [string replace $var1 0 2]
puts "$var1 $var3"
puts "$var4"

执行结果:

[Angel@linux~]$ ./string.tcl
welcom America
come

不常用的其他字符串命令此处没有详解,省略。

6、列表list

6.1 列表命令

在这里插入图片描述

6.2 创建列表list

一个列表可以包含子列表,即列表可以嵌套。
花括号内部代表的是子列表。当用list命令创建列表的时候,如果元素是单个的词,就不用大括弧括起来,但如果某个元素是以空格分割的字符串时,就将其看作一个子列表而用花括号括起来。注意实际的处理过程并不是这样的,要复杂一些。

#!/usr/bin/tclsh
set list1 [list clock1 clock2]     #list1 含有三个元素
set list2 [list $list1 clock3]     #list2 有两个元素,嵌套list1 用花括号括起来
set list3 [list "data1 data2" "data3"]  #list3当元素是字符串时,用花括号括起来
set str "data1 data2"
set list4 [list $str data3]        #列表是特殊字符串,与list3结果相同
puts "List1 is $list1"
puts "List2 is $list2"
puts "List3 is $list3"
puts "List4 is $list4"

set A are
set B [list {
    
    how $A you} Angel]    #花括号阻止变量替换
set C [list "how $A you" Angel]
puts "$B"
puts "$C"

执行结果:

[Angel@linux~]$ ./list.tcl
List1 is clock1 clock2
List2 is {
    
    clock1 clock2} clock3
List3 is {
    
    data1 date2} data3
List4 is {
    
    data1 data2} data3
{
    
    how $A you} Angel        
{
    
    how are you} Angel

6.3 concat/lappend/lindex等命令

  • concat 命令以空格为分隔符将多个列表拼装在一起形成新的列表。它和双引号的作用比较相似。
    list命令和concat命令都可以完成列表合并功能。list和lappend命令保留每个列表的结构,将每个列表作为一个整体生成新列表的元素来完成。而concat命令则要先把各个列表的最外层列表结构去掉,将其中的所有元素取出来作为新列表的元素来完成合并,即新列表的每个元素也是合并前列表的元素。这个区别在后面动态建立Tcl 命令的时候显得尤为重要。
  • lappend 命令用来将新元素追加到列表末尾。
  • llength 命令可以获得一个列表内元素的个。
  • lindex 命令返回列表中指定位置的特定元素。列表索引从0开始记数!
  • lrange 命令返回一个指定区段的列表元素,可以以end或者end-n作为索引(n为正整数)。
  • linsert 命令用来将元素插入到一个列表的由索引指定的位置。如果索引为0或者更小,则元素就会被添加到最前面。如果索引值大于或者等于列表长度,则元素被追加到列表尾部。其他情况元素被添加到指定位置之前。
  • lreplace 命令将一个指定区段的列表元素替换为新元素。如果没有指定新元素,则这个区域的元素就会被从列表中删除。注意:这两个操作不会改变原来列表的内容,而是返回一个新列表。
  • lsearch 命令在给定列表中搜索与匹配字符串匹配的元素,成功就返回正确的元素索引,否则返回-1。lsearch支持通配符格式,但可以使用-exact选项将其屏蔽而进行精确匹配。
  • lsort 命令实现对列表的排序。排序操作不影响原表,而是返回排序之后的新表。排序的方式有多种选择,可以通过-ascii、-dictionary 、–integer、-real 来指定基本排序类型,然后使用-increasing、decreasing指定排列方式,默认为-ascii、-increasing 。要注意ASCII排序时使用字符编码;而dictionary排序方式整合大小写,并将包含的数字以数值大小来处理。

tcl脚本示例如下:

#!/usr/bin/tclsh
##concat
set var1 {
    
    1 2}
set var2 [concat $var1 3]
puts "***The var2 value is $var2"

##lappend
set var3 [lappend var1 "3" {
    
    4} 5]
puts "***The var3 value is $var3"

##llength
set num [llength $var3]
puts "***The number of var3 element is $num"

##lindex
set var4 {
    
    55 aa bb cc}
set var5 [lindex $var4 1]
set var6 [lindex $var4 end]
set var7 [lindex $var4 end-1]         ##获得倒数第二个元素
puts "***the lindex value is $var5"
puts "***the last lindex value is $var6"
puts "***the last-1 lindex value is $var7"

##lrange
set var8 [lrange $var4 2 end]  #返回2到结尾的列表元素
puts "***lrange return value is $var8"

##linsert and lreplace
set var9 [linsert $var4 1 dddd]
set var10 [lreplace $var9 1 3 you are welcome]
set var11 [lreplace $var9 1 3]
puts "***linsert value is $var9"
puts "***lreplace value is $var10"
puts "***lreplace delete 1 to 3 value is $var10"
##lsort
set list "A Z n 100 200 M 20 hl"
set new1 [lsort -ascii $list]
set new2 [lsort -dictionary $list]
puts " The new1 = $new1\n The new2 = $new2"

执行结果:

[Angel@linux~]$./test.tcl
***The var2 value is 1 2 3
***The var3 value is 1 2 3 4 5
***The number of var3 element is 5
***the lindex value is aa
***the last lindex value is cc
***the last-1 lindex value is bb
***lrange return value is bb cc
***linsert value is 55 dddd aa bb cc
***lreplace value is 55 you are welcome cc
***lreplace delete 1 to 3 value is 55 cc
 The new1 = 100 20 200 A M Z hl n
 The new2 = 20 100 200 A hl M n Z

下面的例子是用lsearch和lreplace一起实现在list内删除所有和匹配值匹配的元素。proc是过程定义命令,定义了一个过程ldelete。

#!/usr/bin/tclsh
##lsearch
proc ldelete {
    
    list value} {
    
    
	set ix [lsearch -exact $list $value]
	for {
    
    } {
    
     $ix >=0 } {
    
    } {
    
    
      set list [lreplace $list $ix $ix]
      set ix [lsearch -exact $list $value]
    }
    return $list
}
set list1 [list 123 234 123 345 123 456]
set val 123
set list2 [ldelete $list1 $val]
puts "Delete result is $list2"

执行结果如下:

[Angel@linux~]$ ./lsearch.tcl
Delete result is 234 345 456

6.4 join和split命令

  • join 命令接收一个列表,并用指定的分隔符将列表元素整合成一个字符串
  • split 命令接收一个字符串,并根据给定的分割符将其分裂成列表,用于分割的字符应该在字符串中存在,否则split因为没有搜索到对应的字符而将整个字符串作为唯一的元素返回,即返回原字符串。
#!/usr/bin/tclsh
##join
set new [join {
    
    we are {
    
    working now}} :]
puts "The new string after join = $new"

##split
set str /tools/eda/synopsys/scl-2021.06
set s /
set list1 [split $str $s]
puts "The new list1 after split = $list1"

set str1 helloworld
set list2 [split $str1 {
    
    }]
puts "The new list2 after split = $list2"
[Angel@linux~]$ ./join.tcl
The new string after join = we:are:working now
The new list1 after split = tools eda synopsus scl-2021.06
The new list2 after split = h e l l o w o r l d

split 的默认分割符为空白符,包括空格符、制表符和换行符。如果分割符在字符串开始位置,或者有多个分割符相连,那么split命令就会产生空列表元素,并用{}表示,分割符并不被合并。
若打算将字符串的每个字符都区分开,即将每个字符都分割成列表元素,可以将分割符指定为空字符串{},这个方法对分析和处理字符串中的每个字符时比较有用。当遇到字符串内含有特殊的字符,如空格符时,split也将其作为一个字符元素处理,为了利于区别起见,用花括号将空格元素括起来。

7、数组array

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

7.1 数组定义与格式

array 命令在定义数组的同时可以定义其元素和元素值。需要注意元素索引(index-n)与元素值(valun-n)要成对输入,否则会出错。用命令array set arrName “”可以定义一个空数组。用普通变量值的获取方法——替换操作来获取数组元素值:

[语法]:array set arrName { index1 value1 index2 value2 …}

#!/usr/bin/tclsh
array set arr2 {
    
    1 a 2 b 3 c 4 d}
parray arr2      #输出数组全部内容

执行结果:

[Angel@linux~]$ ./array_test.tcl
arr2(1) = a
arr2(2) = b
arr2(3) = c
arr2(4) = d

7.2 数组变量

可以象使用普通变量一样来使用数组变量元素。如使用info exist来检测它是否存在,使用incr来递增它的值,使用lappend列表操作来追加列表元素等。

#!/usr/bin/tclsh
set arr(1) 10
if {
    
     $arr(1) == 10 } {
    
    
  incr arr(1)      #输出数组全部内容
}
puts "arr(1) = $arr(1)"
[Angel@linux~]$ ./array_test.tcl
arr(1) = 11

7.3 多维数组

自定义多维数组:

#!/usr/bin/tclsh
set arr(0,0) hello
set arr(0,1) world
parray arr
[Angel@linux~]$ ./array_test.tcl
arr(0,0) = hello
arr(0,1) = world

7.4 数组命令

就象字符串和列表一样,数组也有一套专门的操作命令。
在这里插入图片描述
另外还有:
array donesearch arr index 表示结束index标识的搜索
parray arr 用于打印arr的所有元素变量名和元素值

  • array get 命令提取数组索引、元素值对并将这些值对组织成一个列表。而array set命令则将一个列表(数据要成对)转换成一个数组
  • array names 命令返回所有元素索引名与模式pattern匹配的元素索引名列表。模式pattern和string match的模式格式相同。如果pattern没有指定,则返回所有数组元素索引名列表。
#!/usr/bin/tclsh
array set arr [list a AAA b BBB c CCC d DDD]   #定义数组并初始化
set num [array size arr]       ##数组元素个数
set list1 [array get arr]      ##数组元素组成列表
puts "The arr size = $num"
puts "The list of arr = $list1"
parray arr 

##array names
array set a [list "School,SH" "SH" "School,NJ" "NJ" "School,HIT" "HIT"]
puts "####Array names testing!!!####"
parray a
set b [array names a "School,*"]
puts "$b"
[Angel@linux~]$ ./array_test.tcl
The arr size = 5
The list of arr = d DDD a AAA 1 11 b BBB c CCC
arr(a) = AAA
arr(b) = BBB
arr(c) = CCC
arr(d) = DDD
####Array names testing!!!####
d(School,HIT) = HIT
d(School,NJ)  = NJ
d(School,SH)  = SH
School,NJ School,HIT School,SH

遍历数组:

#!/usr/bin/tclsh
array set arr1 [list 1 AAA 2 BBB 3 CCC 4 DDD]
set list1 [array names arr1]
puts "$list1"
foreach id $list1 {
    
    
	puts "arr1($id)= $arr1($id)"
}

[Angel@linux~]$ ./foreach.tcl
4 1 2 3
arr1(4)= DDD
arr1(1)= AAA
arr1(2)= BBB
arr1(3)= CCC

注意:array names返回的元素索引的时候,最后一个元素的索引被放置到了列表的第一个位置上。

8、控制结构命令

类似C语言,Tcl也有控制命令if、if/else、if/elseif、foreach、for、while和switch命令管理控制结构。
需要注意的是Tcl中所有的控制结构都是由命令实现,而C语言中则是一条控制语句。
此外,本节还会讲到catcherror命令,用于捕获命令执行状态和处理错误报告,还有return用于从一个过程语句返回。

8.1 if/else命令

[语法]
if { test expr 测试表达式} {
body1
} elseif {
body2
} else {
body3
}

说明:

1.语法中用以界定过程体的花括号一定要和if命令在同一行上!因为对Tcl 来讲,换行符就是命令结束符,所以如果在if表达式后直接换行,写成:

#这种写法会报错
if {
    
     test expr }
{
    
    
...
}

就会出错。Tcl 遇到换行后就认为命令结束,但找不到执行命令体,返回错误。其他的控制命令,还有以后的过程定义命令等等都存在这个问题。
2.如果if后面还有else/elseif命令,则要留意else/elseif的位置。else/elseif要跟在if执行命令体的后面一个花括号后,不能分行,要有空格间隔花括号和else /elseif
3.花括号括起的表达式、执行命令体或者其他内容相当于变量存在,所以前后与其他命令元素之前要有空格,否则Tcl 会返回语法错误。
4.可以使用多个elseif来创建一连串的条件命令控制结构。
5.表达式支持变量替换和命令替换
6.表达式的计算结果如果是”true”、”yes”和非零值就判断为真,如果结果
是”false”、”no”和零则判断为假。控制命令根据表达式结果来判断是否执行相应的执
行命令体。
简单例子如下:

#!/usr/bin/tclsh
set a 10
if {
    
    $a > 15} {
    
    
	puts "a is a valid value smaller than 10 !!!"
} elseif {
    
    $a > 7} {
    
    
	puts "a is a valid value bigger than 7 !!!"	
} else {
    
    
	puts "a is not a valid value!!!"
}
[Angel@linux~] ./test.tcl
iif is a valid value bigger than 7 !!!

8.2 for命令

for命令有四个变元,start是预置条件或者初始化命令;test expr 是条件布尔表达式,以决定是否执行循环体body,如果是真,则执行循环体,如果假则退出命令。如果表达式真,则在执行循环体后处理next 命令,即next是一个后置命令执行体
前三个变元可以选择置空,而将相应的处理放到循环体body中去。
语法:

for {start} {test expr} {next} {
body
}

例如:

#!/usr/bin/tclsh
for {
    
    set i 0} {
    
     $i < 10 } {
    
     incr i 2 } {
    
    
	if {
    
     $i == 4 } {
    
    
		continue    ##如果是4 忽略后面循环体的内容而执行洗一次循环,所以4的值不打印
	}
	puts "i = $i"
	if {
    
     $i >= 8 } {
    
    
		break     ##立刻从循环体退出
	}
}

执行结果:

[Angel@linux~]$ ./test.tcl
i = 0
i = 2
i = 6
i = 8

8.3 switch命令

switch命令通过将给定字符串与不同的匹配模式进行匹配从而选择执行多分支命令体。switch可基于模式匹配。命令格式为:

switch [option] string {
pattern-1 {body1}
pattern-2 {body2}

pattern-n {bodyn}
}

说明:

  1. option主要包括:
    -exact 用精确匹配(默认)
    -glob 用glob格式模式匹配
    -regexp 用正则表达式匹配
    – 标记选项结束或者说明不用选项
  2. 如果相邻的两个或者多个pattern-x的执行命令体是一样的,则可以只写出最后的一个执行命令体,而前面的执行命令体可以省略,并用“ -” 号来替代。
  3. 最后一个option一定是“–",这个选项不可缺少!
  4. 可以使用default匹配命令体来处理无法匹配模式。当其他模式都不匹配时,default命令体就会被执行。需要注意,default命令体要设置在最后,否则它就会被当作纯粹的字符串default而进行匹配。
  5. 对于switch执行命令体内的注释一定要小心。Tcl 语法器处理的注释应该和命令处于同一层次,即一个注释要占用一个命令的位置。这样就限制了在switch体内注释的位置。比如你不能将一条注释放在和pattern-n同一级别的位置,那样switch命令就会将此条注释也当成一个匹配模式来解释,这有可能引起意想不到的错误。所以,如果打算在 switch体内写注释的话,最好将注释放在相应的某个匹配模式的命令体body-n内

例如:

#!/usr/bin/tclsh
set result TRUE
switch -wxact -- $result {
    
    
	"TRUE" {
    
    
		puts "True"
	}
	"FALSE" {
    
    
		puts "FALSE"
	}
	"UNKNOW" -
	default {
    
     puts "nnknow or UNKNOW value"}
}

执行结果:

[Angel@linux~]$ ./test.tcl
True

8.4 catch/error命令

catch命令有两个元素,com_proc是命名体,res用来保存命令返回结果,或是出错时的错误信息,此变元为可选项。如果有错,catch返回1,无错返回0,命名体需要用花括号括起来。

[语法] catch {com_proc} ? res ?

error命令报告错误信息并终止脚本执行。
message_string是错误信息字符串,info变元用于初始化全局变量errorInfo,如果info没有提供,则error自身初始化errorInfo。变元code指定了一个机器可读的错误信息,会被存储在全局变量errorCode中,默认为NONE。

[语法] error message_string ? info ? error_code?

例如:

#!/usr/bin/tclsh
proc enum {
    
    } {
    
    
	error "1.Function enum report error" "2. Some error in function enum" 20
}
catch {
    
    enum} str   #用catch捕获错误信息,error输出的错误信息被保存在str中
puts "$str"
puts "$errorInfo"  #显示errorInfo内的内容
puts "$errorCode"  #显示errorCode的值,为error报告的code值
[Angel@linux~]$ ./test.tcl
1.Function enum report error
2. Some error in function enum
    (procedure "enum" line 1)
    invoked from within
"enum"
20

8.5 return/exit命令

return命令用来返回调用。return的位置可以根据各种条件和需要进行安排,而且一个过程中可以包含多条return命令,但当遇到第一个可执行的return命令时就返回。
exit命令用来终止脚本的执行。exit会终止并退出整个运行脚本的进程(退出Tcl shell),使用时要小心。如果在退时提供了一个整数数值,则它代表退出状态。

9、过程与作用域

9.1 proc命令

proc命令有三个参数,procName是定义的过程名字;{var1 var2 …}是输入、输出参数列表;body是过程执行命令体。body的界定大括弧和if等命令的命令执行体遵循相同的规则和注意事项。

【语法】 proc procName { var1 var2 … } {
body
}

  • 可以使用return在需要的时候返回调用程序。
  • 使用过程的时候,不一定输入所有的参数值。过程的输入参数可以有默认值。默认值由{默认参数名默认值}指定。
  • 如果参数列表中最后一个参数是args的话,则过程可以接收可变数目的输入参数。当调用过程时,除了指定参数以外的参数值都被args接收。如果参数列表中只有args一项,则args接收所有输入参数值
#!/usr/bin/tclsh
set a 10
set b 7
proc test1 {
    
     a b } {
    
    
	set c [expr $a*$b]
	puts "c = $c"
}
proc test2 {
    
    args} {
    
                    #定义一个只接收可变数目参数的过程
	puts "input value are: $args"
	array set inArr $args
	parray inArr
}

test1 5 6
test2                              #没有给定任何值
test2 -hostname "Angel" -ip "192.168.1.1" -date "2022.03.09"    #给定一组参数值

执行结果:

[Angel@linux~] ./test.tcl
c = 30
input value are: 
input value are: -hostname Angel -ip 192.168.1.1 -date 2022.03.09
inArr(-date)     = 2022.03.09
inArr(-hostname) = Angel
inArr(-ip)       = 192.168.1.1

9.2 作用域与global命令

对于变量而言,分全局变量局部变量。在所有过程之外定义的变量为外部变量,即全局变量,它的作用域为从开始定义到执行结束,除非中间有显式取消其定义。在一个过程体内定义的变量为内部变量,即局部变量,局部变量的作用域只限于过程内部使用,在此过程外面不能使用这些变量。

在一个过程内部,即可以使用自身的局部变量,又可以使用
全局变量,但是全局变量在过程内部不会自动可见,需要通过global 命令来事先声明。因为作用域不同,所以过程中的变量可以与全局变量、其他过程中的变量有相同的名字。

设置必要的全局变量可以为过程之间提供数据联系的一个渠道。因为全局变量可以供所有过程使用,所以如果在一个过程中改变了全局变量的值,就能影响到其他过程

在过程定义中的输入、输出参数列表中的参数为过程的内部参数。

例如:
global 命令使全局变量var_3对过程可见。全局变量的定义不一定要在过程外完成,可以在任何一个过程中完成,其效果是将一个局部变量的作用域进行了扩展。但是在引用全局参数的值之前全局参数应首先已被赋值。另外当一个过程中要使用与局部变量相同名字的参数时,用global 命令就要小心:你不能在定义完成一个局部变量后再用global 命令使用同名的全局参数,这会出错

#!/usr/bin/tclsh
set var_1 55
set var_2 aa
set var_3 cc
proc test3 {
    
     a } {
    
    
	set var_2 hello
	global var_3
	puts "Value of input parmeter var_1 is $a"
	puts "My var_2 is $var_2"
	puts "Global var_3 is $var_3"
}
proc test4 {
    
    } {
    
    
	global var1 
	set var1 100
	puts "var1 = $var1"
}
proc test5 {
    
    } {
    
    
	global var2
	puts "var2 = $var2"
}
proc test6 {
    
    } {
    
    
	set var3 30
	global var3
	puts "var3 = $var3"
}
test3
test4
puts "*******test proc for test5*******"
test5
puts "*******test proc for test6*******"
test6

执行结果:

[Angel@linux~] ./test.tcl
Value of input parmeter var_1 is var_1
My var_2 is hello
Global var_3 is cc
var1 = 100
*******test proc for test5*******
can't read "var22": no such variable
    while executing
"puts "var2 = $var2""
    (procedure "test5" line 3)
    invoked from within
 *******test proc for test6*******       
variable "var3" already exists
    while executing
"global var3"
    (procedure "test6" line 3)
    invoked from within
"test6"
    (file "./test.tcl" line 196)

9.3 upvar与rename命令

upvar命令是通过"引用"来使上层过程中的变量,他传递的是参数名而非值。语法如下:
【语法】:upvar ? level ? otherVar1 ?myVar1 ? otherVar2 ? myVar2…?
upvar命令将myVar1定义为otherVar1的一个引用(reference),otherVar是由level 指定的本过程调用栈中的向上level 层的变量。当定义好之后,本过程就可以通过myVar参数来使用otherVar参数。

#!/usr/bin/tclsh
proc SetPositive {
    
     varname varvalue} {
    
    
	upvar $varname myvar             #为输入参数名定义引用为myvar
	if {
    
     $varvalue <0 } {
    
    
		set myvar [expr - $varvalue]
	} else {
    
    
		set myvar $varvalue
	}
	return $myvar
}
set x 5
set y -5
puts "Before call SetPositive: x = $x, y = $y."
SetPositive x $x             #调用转换函数处理变量x
SetPositive y $y             #调用转换函数处理变量y
puts "After call SetPositive: x = $x, y = $y."
[Angel@linux~]$ ./upvar.tcl
Before call SetPositive: x = 5, y = -5.
After call SetPositive: x = 5, y = 5.

rename命令 可以用来更改命令名,这些命令名包括tcl自带的内建命令和读者自己定义的过程。
【语法】:rename oldFuncName newFuncName
例如:

#!/usr/bin/tclsh
proc oldname {
    
    } {
    
                       #定义一个过程
	puts "This is old function name."
}
oldname
rename oldname newname
puts "###After rename function name###"
newname                             #调用新的Function Name 打印输出
puts "###Cancel New function name###"
rename newname {
    
    }                   #通过rename取消newname命令
newname                             #再次调用newname发现命令已经不存在,调用失效
[Angel@linux~]$ ./rename.tcl
This is old function name.
###After rename function name###
This is old function name.
###Cancel New function name###
invalid command name "newname"
    while executing
"newname"
    (file "./upvar.tcl" line 28)

9.4 特殊变量

9.4.1 命令行参数

命令行参数是Tcl shell被调用时定义/初始化的。命令行参数有:

  • argc 命令行参数的数目,不包括执行脚本的名字。
  • argv0 脚本名。
  • argv 命令行参数列表
#!/usr/bin/tclsh
puts "The number of command line arguments is: $argc"
puts "The name of the scriptis: $argv0"
puts "The command line arguments are: $argv"
[Angel@linux~]$ ./arg.tcl
The number of command line arguments is: 0
The name of the scriptis: arg.tcl
The command line arguments are:

[Angel@linux~]$ ./arg.tcl a b c d e
The number of command line arguments is: 5
The name of the scriptis: arg.tcl
The command line arguments are: a b c d e

9.4.2 env环境变量数组

TCL提供了一事先就定义好的全局环境变量数组,这个数组叫做env。

#!/usr/bin/tclsh
puts "$env(PATH)"
#用parray命令打印和显示所有环境变量和变量值
parray env
#加入一条新的条目到PATH中
set env(PATH) "$env(PATH):/usr/sbin"
puts "$env(PATH)"
[Angel@linux~]$ ./env.tcl
/usr/local/bin:/bin:/depot/VNC/sun/bin:/usr/ccs/bin:/usr/sbin:/usr/bin:/usr/ucb:/etc:/usr/X/bin:/u/xiaoxue/bin:/u/regress/INFRA_HOME/bin:/depot/perforce:/depot/tools/ccollab/bin:/usr/lib64/qt3.3/bin:/bin:/usr/bin:/usr/X11R6/bin:/usr/local/sbin:/usr/sbin:/usr/local/bin:/opt/Citrix/VDA/bin:.:/u/xiaoxue/.dotnet/tools:/opt/Citrix/VDA/bin
nv(CITRIX_CLIENT_IP_ADDR)        = 10.133.180.21
env(CITRIX_CLIENT_PROD_ID)        = 1
env(CITRIX_DISPLAY)               = :120
env(CITRIX_REMOTE_DISPLAY)        = linux:120.0
env(CITRIX_SESSION_ID)            = 2
env(COLORFGBG)                    = 15;0
env(CTX_REMOTE_DISPLAY)           = linux:120.0
env(CVS_RSH)                      = ssh
env(DBUS_SESSION_BUS_ADDRESS)     = unix:abstract=/tmp/dbus-ibHixlgB1q,guid=556671ced232d27a888ebb560ee7709
env(DEPOT_TOOLS_PATH)             = /depot/perforce:/depot/tools/ccollab/bin
env(DISPLAY)                      = :120
...
...
/usr/local/bin:/bin:/depot/VNC/sun/bin:/usr/ccs/bin:/usr/sbin:/usr/bin:/usr/ucb:/etc:/usr/X/bin:/u/xiaoxue/bin:/u/regress/INFRA_HOME/bin:/depot/perforce:/depot/tools/ccollab/bin:/usr/lib64/qt3.3/bin:/bin:/usr/bin:/usr/X11R6/bin:/usr/local/sbin:/usr/sbin:/usr/local/bin:/opt/Citrix/VDA/bin:.:/u/xiaoxue/.dotnet/tools:/opt/Citrix/VDA/bin:/usr/sbin

9.5 eval命令

任何字符串或者列表,只要符合这个标准,都可以被计算和执行。而eval命令就可以完成计算和执行这种字符串或列表命令,从而允许程序可以动态地构造命令。
【语法】: eval var1 ? var2 ? …varN ?

#!/usr/bin/tclsh
set string "Hello World"
set cmd [list puts stdout $string]
puts stdout {
    
    Hello World}
eval $cmd

执行结果:

[Angel@linux~]$ ./eval.tcl
Hello World
Hello World

10、跟踪与调试

10.1 clock命令

用clock命令可获得当前系统时间,并能根据指定的格式处理时间字符串。clock相关命令如下表:
在这里插入图片描述

10.1.1 clock clicks /second 命令

clock clicks 返回高分辨率系统时钟计数器值,一般仅用于测量经过的时长。click分辨率取决于系统本身。如果使用了-milliseconds选项,则分辨率以毫秒为粒度。如下面的代码计算出若干秒之内时钟滴答次数:

#!/usr/bin/tclsh
proc click {
    
    period} {
    
    
set t1 [clock clicks]
#Wait for $period seconds
after [expr $period * 1000]
set t2 [clock clicks]
puts "[expr ($t2 - $t1)/$period] Clicks/Second"
}
click 10
set t3 [clock seconds]
puts "$t3"
[Angel@linux~]$./clock.tcl
000661 Clicks/Second
1646879795

10.1.2 clock format命令

clock format命令将整型时间值格式化为一个可读的日期字符串。这个时间值可以是clock seconds、clock scan命令,或者是带选项atime、mtime和ctime的file命令的返回值。日期字符串的格式可以由-format后的格式化字符串string确定。格式化字符串中使用一个或多个域描述符。域描述符由%后跟一个域描述符字符组成。有效的域描述符如下表所列:
在这里插入图片描述
在这里插入图片描述
注意:如果没有指定-format , 默认format string “%a %b %d%H:%M:%S %Z %Y” .

#!/usr/bin/tclsh
set Time [clock format [clock second]]
puts "$Time"

set Time1 [clock format [clock second] -format %T]
puts "$Time1"

#clock scan命令用来解析一个日期字符串并返回对应的时间值(以秒为单位)。此命令可
#以处理各种格式的日期,如果没有指明年份,则采用当前年份。
set Time2 [clock format [clock scan "1 week ago"]]
puts "$Time2"

执行结果:


[Angel@linux~]$ ./time.tcl
Thu Mar 10 10:46:27 CST 2022
10:46:27
Thu Mar 03 00:00:00 CST 2022

10.2 info命令

info命令允许Tcl 程序从Tcl 解释器获得有关当前解释器状态的信息。比如,通过info的子命令可以知道某个过程、变量或者命令是否在当前的解释器中存在,这样你就可以在使用一个变量或者调用一个过程的时候,先测试一下变量或者过程是否存在,从而避免操作不存在的变量、过程或命令而引起的错误。
在这里插入图片描述
在这里插入图片描述

#!/usr/bin/tclsh
proc fact {
    
    val} {
    
    
	set level [info level]
	puts "Current level: $level, val : $val"
	if {
    
     $level == $val} {
    
    
		return $val
	}
	set num [expr $val - $level] #将val值减去当前level值
	return [expr $num *$val]  #返回相乘之后的结果
}

set res [fact 3]
puts "$res"

#info exits 命令可以测试一个变量是否存在。在使用一个变量之前,用此命令先检测一下变
#量是否已经存在,从而避免因为使用了未定义的变量引起的错误。
set a [info exists b]
puts "$a"         #1:参数b存在 0:参数b不存在
[Angel@linux~]$ ./info.tcl
Current level: 1, val : 3
6
1

10.3 trace命令

trace命令用于变量操作跟踪,它注册一条命令到一个变量,只要这个变量发生指定的变化,如被读、写或者复位(unset)的时候,注册命令就会被调用来进行相关的处理。
[语法] : trace variable varName operations command
说明:
1.operations为变量操作选项,为下列选项的一个或者多个:

  • r 代表只读
  • w 代表只写
  • u 代表复位或unset操作
    operations 说明当变量发生这些动作时,命令就会被调用。

2.command为注册命令,它必须能够接收三个参数(。当变量发生operations中的某一个动作的时候,command就会执行:
command var1 var2 var3
其中,var1代表变量名或者数组名。var2是数组元素索引,如果跟踪的是普通变量(非数组变量),或者跟踪的数组被设置为复位跟踪且数组已经被复位,则此参数为空。var3是跟踪的动作,即满足options定义的某个选项对应动作。

3.可以多次调用trace variable为同一变量注册多条命令,这些命令会在指定条件满足时顺次执行。比如可以为一个变量的读、写和复位不同操作分别注册不同的命令,也可以为同一个操作注册多条命令。

#!/usr/bin/tclsh
proc traceP1 {
    
    args} {
    
    
puts "---Enter proc traceP1---"
puts " There are [llength $args] input variables for trace command"
puts "The input three variables' value are:"
set varName [lindex $args 0]
set index [lindex $args 1]
set action [lindex $args 2]
puts " varName: $varName \n index: $index \n action: $action"
puts "---Proc traceP1 end---"
}

proc traceP2 {
    
    varName arrIndex op} {
    
    
puts "---Enter proc traceP2---"
switch -exact -- $op {
    
    
w {
    
    set option "setted"; }
r {
    
    set option "read"}
u {
    
    set option "unsetted"}
}
puts " Three input variables's values are: "
puts " varName: $varName \n index: $arrIndex \n action: $op"
puts "Variable $varName was $option"
puts "---Proc traceP2 end---"
}

set a 1
puts "$a"
array set b {
    
     one Hello two World }
trace variable a rwu traceP1  #为变量a 的读、写与复位注册过程traceP1
trace variable a r traceP2    #为变量a的读注册过程traceP2
trace variable b rwu traceP2  #为数组b的读、写与复位注册过程traceP2
trace variable b r traceP1    #为数组b的读注册过程traceP1
puts "$a"

puts "$b(one)"                # 变量b的读也被注册了两个命令

set a 10
puts "$a"                     # 变量a的写只被注册了一个命令
[Angel@linux~]$./trace.tcl
1
---Enter proc traceP2---
 Three input variables's values are: 
 varName: a 
 index:  
 action: r
Variable a was read
---Proc traceP2 end---
---Enter proc traceP1---
 There are 3 input variables for trace command
The input three variables' value are:
 varName: a 
 index:  
 action: r
---Proc traceP1 end---
1
---Enter proc traceP1---
 There are 3 input variables for trace command
The input three variables' value are:
 varName: b 
 index: one 
 action: r
---Proc traceP1 end---
---Enter proc traceP2---
 Three input variables's values are: 
 varName: b 
 index: one 
 action: r
Variable b was read
---Proc traceP2 end---
Hello
---Enter proc traceP1---
 There are 3 input variables for trace command
The input three variables' value are:
 varName: a 
 index:  
 action: w
---Proc traceP1 end---
---Enter proc traceP2---
 Three input variables's values are: 
 varName: a 
 index:  
 action: r
Variable a was read
---Proc traceP2 end---
---Enter proc traceP1---
 There are 3 input variables for trace command
The input three variables' value are:
 varName: a 
 index:  
 action: r
---Proc traceP1 end---
10

从上例可以看出,为变量注册命令后,先执行注册命令,然后在执行对参数的具体操作。用 trace命令还可以作到对变量操作的限制,如可以限制变量为只读变量,当试图对变量进行其他操作时,注册命令就返回错误。

trace vdelete
trace vdelete 删除用trace variable为变量所做的一条注册命令。
[语法]:trace vdelete varName operations command
trace vdelete的语法和trace variable的语法一致。

trace vinfo
trace vinfo返回变量跟踪设置的信息。

11、文件操作与程序调用

11.1 文件操作

11.1.1 文件I/O

Tcl 支持缓存机制的文件I/O操作。最简单的文件操作是gets和puts,但当有大量数据需要读取时,read命令更有效,可以通过read命令将整个文件数据都读出来,然后用split命令将文件按行进行分割
常用的文件操作命令:open、close、puts、gets、read、seek、tell、eof和flush。
在这里插入图片描述
1.open命令

说明:

  • fileName是用于打开的文件名
  • access是文件存取模式,默认为读操作。
  • permission 是一个八进制整数,用于设置文件的访问权限,默认为rw-rw-rw(0666)。UNIX系统将文件用户分成三类:属主(master,文件的创建者)、组用户(group users)和其他用户(other users)。每类用户设置三位文件访问权限控制标识,分别指定了读、写和执行权限。如果对应位置为”-“,则表示此类用户没有对应的访问权限。在UNIX系统中,可以通过chmod命令来更改文件的访问控制权限
  • 在使用open命令打开文件的时候,应该使用catch命令来捕获错误信息。这样会使代码更安全。当调用成功时,文件描述符被保存到catch的变量中,否则catch变量保存错误信息。
    access变量说明:
    在这里插入图片描述
    文件访问控制权限说明:
    在这里插入图片描述
    举例说明:
#!/usr/bin/tclsh
if {
    
     [catch {
    
    open ./data.tcl w+} res ] } {
    
    
puts "Cannot open ~/data.tcl for write:$res"
} else {
    
    
puts $res "This is one data file."
flush $res
close $res
}

proc check_pattern {
    
     filename pattern } {
    
    
    set count 0

    puts "Search $filename for pattern \"$pattern\""

    set fid [open $filename r]   #创建文件句柄
    while {
    
    [gets $fid line] != -1} {
    
         #读取文件行
        incr count [regexp -all -- $pattern $line] #行号加1
		puts "count = $count"  #打印行号
    }
    close $fid  #关闭文件

    if {
    
     $count } {
    
    
        puts "Pattern \"$pattern\"found in $filename"  #如果检测行中有搜索的$pattern 则打印输出
    }

    return $count
}

check_pattern data.tcl data 

[Angel@linux~]$ ./ioopen.tcl
[Angel@linux~]$ cat data.tcl
This is one data file.
Search data.tcl for pattern "data"
count = 1
count = 2
Pattern "data"found in data.tcl

使用文件命令注意事项:

  • 对gets命令,无法区分空行和文件结束EOF,所以在使用此命令读文件时,需要用eof命令来判断文件EOF
  • 在TCL中,所有的数据都以ASCII 字符串形式保存,这意味着当读取一个二进制文件时可能产生不可预料的结果。
  • 文件I/O是有缓存机制的,当调用close命令后,会关闭指定文件并将相关通道缓存内容输出到文件。但是如果是非正常关闭,留在缓存中的数据可能无法保存到文件而丢
    失。
  • puts、gets、seek等命令中的变量fileID可以是如下几种:
    a) 由open命令返回的文件描述符
    b) 标准输入stdin
    c) 标准输出stdout
    d) 标准错误输出stderr

11.1.2 文件系统信息命令glob和file

glob命令和UNIX系统的ls命令相似,用于文件的匹配搜索,并返回一个与搜索模式匹配
的文件名列表。glob支持通配符。
[语法]: glob ?switches? pattern ?patternN?
switches选项有:

  • -nocomplain 当返回空列表时,glob不报错,不用此选项时,glob会在返回空
    列表时报错。
  • -directory directory在指定的目录中搜索。如glob –directory e:\masm e1.tcl。
  • -path pathVar 在指定路径内搜索。不可以和-directory同时使用。
  • – 结束switches

glob的匹配模式与string match命令的匹配规则相似:

  • “*” 通配0或多个字符;
  • {a,b,…}匹配a,b ,…中的任一个字符;
  • “?”通配单个字符;
  • [abc]匹配一组字符;
  • 如果pattern开始两个字符是 ~ /,则~将被用户路径环境变量值HOME 替代,如果
    文件是以 ~ 开始,最好加一个./前导来避免这种扩展(如./~foo)。

11.1.3 file命令集

在这里插入图片描述
在这里插入图片描述
例子:

#!/usr/bin/tclsh
file lstat la var1
file stat la var2
puts "#######test for var1##########"
parray var1
puts "#######test for var2##########"
parray var2

执行结果:

[Angel@linux~]$ ln -s data.tcl la

[Angel@linux~]$ ./ioopen.tcl
#######test for var1##########
var1(atime)   = 1646893421
var1(blksize) = 32768
var1(blocks)  = 0
var1(ctime)   = 1646893421
var1(dev)     = 70
var1(gid)     = 31
var1(ino)     = 69231190
var1(mode)    = 41471
var1(mtime)   = 1646893421
var1(nlink)   = 1
var1(size)    = 8
var1(type)    = link
var1(uid)     = 10040229
#######test for var2##########
var2(atime)   = 1646891502
var2(blksize) = 32768
var2(blocks)  = 0
var2(ctime)   = 1646893431
var2(dev)     = 70
var2(gid)     = 31
var2(ino)     = 69231189
var2(mode)    = 33188
var2(mtime)   = 1646893431
var2(nlink)   = 1
var2(size)    = 46
var2(type)    = file
var2(uid)     = 10040229

file stat命令数组元素说明:
在这里插入图片描述

11.2 程序调用

主要有两中方法在Tcl 中调用一个程序:

  • open…打开一个连接到文件描述符的进程管道来运行其他程序
  • exec …将一个程序作为子进程运行

用open命令打开一个进程管道:
[语法] open |progName ?access?
progName用双引号括起,可以包含变量,但开始一定是“ |” 。access表明操作选择,有”r”,”w”等,可参见上节的access表。
在这里插入图片描述

set fd [open "| sort /etc/passwd" r]   #sort命令的标准输出被重定向到文件描述符$fd
puts "Testing for fd $fd"
set str [split [read $fd] \n]          #读取管道内的全部信息并以换行符分割
puts "Testing for str $str"

close $fd
[Angel@linux~]$ ./ioopen.tcl
Testing for fd file6
Testing for str adm:x:4:4:Admin:/var/adm: bin:x:2:2::/usr/bin: daemon:x:1:1::/ {
    
    listen:x:37:4:Network Admin:/usr/net/nls:} {
    
    lp:x:71:8:Line PrinterAdmin:/usr/spool/lp:} {
    
    noaccess:x:60002:60002:No Access User:/:} {
    
    nobody4:x:65534:65534:SunOS 4.x Nobody:/:} nobody:x:60001:60001:Nobody:/:{
    
    nuucp:x:9:9:uucp Admin:/var/spool/uucppublic:/usr/lib/uucp/uucico}
root:x:0:1:Super-User:/:/sbin/sh sys:x:3:3::/: {
    
    uucp:x:5:5:uucp Admin:/usr/lib/uucp:} {
    
    }

用exec命令调用程序:
[语法] exec ?switches? arg1 ?arg2? … ?argN?
说明:

  1. 如果exec的开始的参数是以”-“开始,则这些参数被认为是命令的开关选项
    switches。开关选项有:
     -keepnewline 不要丢弃管道输出结果中尾部的换行符。通常换行符会被删除
     – 标识开关选项的结束。下一个字符串将被认为是参数arg1,即
    使它以”-“开始也不再被认为是开关选项
  2. arg1~argN可以是
     被执行程序名
     被执行程序的命令行参数
     I/O重定向指示

重定向指示标识说明:
在这里插入图片描述
在这里插入图片描述
如果没有用&来将exec命令放到后台执行,则该命令在执行期间将被阻塞。

pid命令
pid命令返回当前进程的ID。进程ID在每次进程调用时的值都会改变,所以可以用进程ID作为随机数生成的种子(seed)。也可以通过pid来查出与进程管道相关联的进程ID。

#!/usr/bin/tclsh
set fddd [open "|sort /etc/passwd" r+]
set pidid [pid $fddd]
set currentpid [pid]
puts "thread fd pid = $pidid, current pdi = $currentpid"

[Angel@linux~]$ ./pid.tcl
thread fd pid = 108942, current pdi = 108937

12、正则表达式

正则表达式的匹配器是用优化的C代码来实现的,因此进行模式匹配的速度很快。
有些Tcl 命令支持正则表达式的操作,比如前面提到的lsearch命令和switch命令都可以支持一个由“ -regexp” 为标志的基于正则表达式匹配操作。此外,还有两个单独的命令来解析正则表达式,它们是regexp和regsub命令。

12.1 regexp命令

regexp用来匹配正则表达式和字符串。

【语法】:regexp ? switches ? exp string ? matchvar ? subMatchVar …
subMatchVar?

说明:

  1. regexp命令比较字符串string是否与正则表达式exp部分或者全部匹配,并可以将字符串中的子字符串提取出来。如果字符串的某个子字符串和正则表达式匹配,则返回1,否则返回0。
  2. 如果在string变量后面,还有其它的变量(匹配变量match variables),则这些变量就保存了那些与正则表达式匹配的子字符串信息(如可能是子字符串的实际内容,或者是界定子字符串的起始、结束的索引数字)。matchVar保存了匹配exp的字符串,而subMatchVar依次存放了和exp各个中单个括号语法(子模式)匹配的子字符串。如下图regexp正则表达式匹配示意图:
    在这里插入图片描述
    上图 exp中的(A)、(B)和(C)是正则表达式的子模式,string中的strA等是与对应子模式匹配的子字符串。matchVar中存放了与正则表达式匹配的字符串的信息。subVar1存放了第一个匹配子字符串即strA的信息,subVar2存放了strB的信息,由于后面没有其它的变量,就忽略存放第三个匹配字符串。匹配变量 matchVar、subVarN中是存放实际字符串内容还是存放表明起始、结束范围的索引数值要regexp指明(用开关选项-indices)。
  3. regexp中的switches是命令开关选项,这些选项主要有:
选项 说明
-nocase exp中的小写字符可以匹配string中的大写和小写字符
-indices 返回界定string中匹配区间起始、结束的索引数值。否则返回匹配区间内字符串本身
-expanded 使用扩展语法
-line 等价于同时指定-lineanchor和-linestop
-lineanchor 将^和$的行为改为面向行的方式
-linestop 将匹配方式改变成和字符类不匹配换行符
-about 适用于调试,返回有关模式的信息而不是试图与输入进行匹配
-all 让正则表达式在string中匹配所有的匹配子字符串,返回匹配次数,而且将最后一次匹配结果存入匹配变量
-inline 将原来存放在匹配变量中的值以列表的形式返回,如果同时使用了-all,则返回所有满足匹配结果的值的列表。
-start index 用index指定exp在string中起始匹配位置。如果使用了-indices,返回的索引是从输入字符串string的绝对起始位置算起而不是从index指定位置算起
- - 结束选项,如果表达式以-开始, 则需先用此选项
#!/usr/bin/tclsh
#---------------(1)
puts "#---------------(1)"
set sample "Whre there is a will, There is a way"
puts "$sample"

set result [regexp {
    
    ([a-z]+) ([a-z]+) ([a-z]+)} $sample mStr var1 var2 var3]
puts "$result"
if {
    
     $result } {
    
    
	puts "Result: $result match:$sample:mStr=$mStr;var1=$var1;var2=$var2;var3=$var3"
}
#---------------(2)带有nocase选项的regexp命令
puts "#--------------(2)"
set result [regexp -nocase {
    
    ([a-z]+) ([a-z]+) ([a-z]+)} $sample matchStr var1 var2 var3]
if {
    
    $result} {
    
    
	puts "Result: $result match: $sample:\n
	matchStr=$matchStr;var1=$var1;var2=$var2;var3=$var3"
}
#-------------------(3) 指明需要返回索引数值而不是字符串本身
puts "#--------------(3)"
set result [regexp -indices {
    
    ([a-z]+) ([a-z]+) ([a-z]+)} $sample mStr var1 var2 var3]
if {
    
    $result} {
    
    
	puts "Result: $result match: $sample:\n mStr=$mStr;var1=$var1;var2=$var2;var3=$var3"
}
#-------------------(4) -inline和-all
puts "#--------------(4)"
set result [regexp -inline -all -- {
    
    ([a-z]+) ([a-z]+) ([a-z]+)} $sample]
puts "result"

执行结果:

[Angel@linux~] ./regexp.tcl
#---------------(1)
Whre there is a will, There is a way
1
Result: 1 match:Whre there is a will, There is a way:mStr=hre there is;var1=hre;var2=there;var3=is
#---------------(2)
Result: 1 match: Where there is a will, There is a way.:matchStr=Where there is;var1=Where;var2=there;var3=is
#---------------(3)
Result: 1 match: Where there is a will, There is a way.:mStr=1 13;var1=1 4;var2=6 10;var3=12 13 #返回的标识匹配区间的索引数值
#---------------(4)
{
    
    here there is} here there is {
    
    here is a} here is a

12.2 regsub命令

regsub命令基于正则表达式完成字符串匹配和替换。

【语法】:regsub ? switches ? exp string subSpec varName
说明:

  1. Switches是命令开关选项,主要有:
    - 开关选项-nocase、-expanded、-line、-linestop、、-lineanchor、-start index 和–
    与regexp命令开关选项作用相同。
    - -all 对所有满足匹配条件的字符串范围进行替换,返回匹配和替换的次数。没有此选项时,只匹配第一个满足匹配条件的匹配范围并用subSpec替换之。
  2. 替换模式subSpec可以是普通字符,也可以含有&和\x(x是一个数字)。当是这两个特殊字符时,&和\x就会被替换成与exp中的对应模式匹配的string中的匹配范围内的字符。
  3. varName存放替换后的字符串。

12.3 正则表达式语法

12.3.1 分支branch和原子atom

正则表达式由一个或多个分支(branch)组成,分支之间用符号| 来相连。每个分支则由零个或者多个原子(atom)组成。这些原子的形式主要有(标AREs为AREs支持功能):

  • (re) -----圆括号子表达式,并报告捕获子串
  • (?:re) -----同上,但屏蔽报告(AREs)
  • () ----- 空圆括号子表达式,捕获空字符串并报告
  • ( ?: )----- 同上,但屏蔽报告(AREs)
  • [chars]----- 方括号表达式,匹配指定的字符集中任意一个字符
  • . -----匹配任意一个字符
  • \k----- (k是非字母数字字符)将字符看作普通字符(关闭特殊解释),如\匹配反斜杠
  • \c -----(c是字母数字字符),AREs的换码(escape)语法(AREs)
  • { -----当后跟非数字字符时,表示匹配左花括弧;跟数字时,表示边界(bound,AREs)
  • x -----x是单个字符(包含空格),匹配字符本身

12.3.2 基本语法

12.3.2.1 匹配字符

大多数字符可以用来直接作为原子和自身进行匹配,如下例中的表达式匹配一个a和b字符组合:

#!/usr/bin/tclsh
regexp {
    
    ab} "this text talks about China." match
puts "$match"

regexp {
    
    a.} "this text talks about China." match
puts "$match"

执行结果:

[Angel@linux~] ./regexp.tcl
ab
al

12.3.2.2 限定匹配

字符匹配可以发生在字符串中的任何位置,一个模式不必分配整个字符串,在匹配地字符前面和后面都可以有未匹配的字符。可以使用定位符符(^和 ) 来 指 定 匹 配 位 置 : 将 匹 配 限 制 在 字 符 串 起 始 位 置 , )来指定匹配位置:^将匹配限制在字符串起始位置, 则限制在结尾。可以同时使用这两个符号来匹配整个字符串。如下例匹配所有以字母T或M 开始的字符串:

#!/usr/bin/tclsh
regexp {
    
    ^[TM]+} "This test talks about Chinese." match
puts "$match"
regexp {
    
    ^[TM]+} "Man and Woman" match
puts "$match"
regexp {
    
    ^[T]+} "Hello" match
puts "$match"

执行结果:

T
M
0

12.3.2.3 方括号表达式与字符集

通过使用方括号括起多个字符的方式[xyz],来指定匹配字符的范围(1)。这一方法使正则表达式可以对字符集中的任意一个字符进行匹配。

可以使用语法[x-y]来指定从字符x 到字符y的字符组成的字符集(2),这个时候要注意,不同的范围不能共享相同的端点,比如[a-e-z]就是错误的语法。字符集范围对字符排序顺序有较强的依赖性,易于移植的程序要尽量避免使用这样的语法。

还可以用语法[^ xyz]来指定字符集合的补集,即匹配字符为指定字符集以外的任意字符(3)。

#!/usr/bin/tclsh
regexp {
    
    [Hh]ello} "He said hello to me." match
puts "$match"
regexp {
    
    [Hh]ello} "He said Hello to me." match  #等价于[H|h]
puts "$match"
regexp –indices {
    
    -[a-dA-D]} “Effort is applied” match  #匹配a 到d和A到D之间的任意一个字符,将匹配位置放入match
puts "$match"
regexp -all -inline {
    
    [^a-dA-D]} "Effort is applied" #匹配所有非a到d和A到D的字符

执行结果:

[Angel@linux~] ./regexp.tcl
hello
Hello
10 10
E f f o r t {
    
    } i s {
    
    } P P l i e

12.3.2.4 量词

一个原子后面可以跟一个量词,来指定进行多次匹配。

  • * 表示重复多次或者零次
  • + 表示重复一次或者多次
  • ? 表示重复零次或一次

例如:

  • ba* ----表示匹配一个b后跟有零个或者多个字符a的字符串;
  • (ab)+ ----表示匹配具有一个或者多个ab序列的字符串;
  • .* ----可以匹配任意字符串或者空字符串。

这几个量词具有贪婪(greedy)的特性:它们尽可能多的匹配字符。ARE中增加了非贪婪
(non-greedy)匹配。在使用量词*和?的时候要特别小心,它们可以表示零个。因为零个的所有东西都可以匹配成功。

12.3.2.5 反斜杠

在这里插入图片描述

12.3.2.6 字符类

除了反斜杠字符序列,高级正则表达式还支持字符类匹配,字符类就是利用一个单词代表复杂意思,大部分的字符类与反斜杠序列含义相同,但也有一些字符类是特有的,比如匹配16进制字符的xdigit,几乎所有情况下只要使用字符类就必须将它们放在[[: :]]符号中,下面的表格列出了所有字符类:
在这里插入图片描述

12.3.2.7 扩展字符匹配

扩展的正则表达式语法
扩展语法中,我认为最为重要和方便的就是{}语法,它可以精确指定前面模式匹配的次数,{}语法有3种基本使用方法:

{m} 匹配前面模式的m次

{m,} 匹配前面模式最少m次,最多无限次

{m,n} 匹配前面模式最少m次,最多n次

在实际使用时还可以在{}语法后面加上 ? 号表示非贪婪匹配。

12.4举例子

#!/usr/bin/tclsh
set dumpout {
    
    17:14:24.927839 arp who-has 10.11.105.254 tell 10.11.105.10217:14:24.927936 arp reply 10.11.105.254 is-at 00:13:72:35:a6:fd}
set pattern {
    
    arp reply 10.11.105.254}
set st [regexp -- $pattern $dumpout match]
puts $match


#
set pcarp {
    
    

Address                  HWtype  HWaddress           Flags Mask            Iface

10.11.105.29                     (incomplete)                              eth0

10.11.105.19             ether   00:11:D8:35:13:84   C                     eth0}

 

set pattern {
    
    (10.11.105.29)+?.*?incomplete+?}

set patt "/u000A*/u000D*"

regsub -all -- $patt $pcarp {
    
    } pcarp

set st [regexp -- $pattern $pcarp match]

puts $match

执行结果:

[Angel@linux~] ./regexp.tcl
arp reply 10.11.105.254
10.11.105.29                     (incomplete

猜你喜欢

转载自blog.csdn.net/sinat_43629962/article/details/123253696