TCL学习心得(2)Tcl语言的语法

  • Post author:
  • Post category:其他


要编写Tcl脚本,必须学会两件事。第一:学会Tcl的语法。第二掌握在脚本中的独立命令。

脚本,命令和单词

Tcl脚本包含一条或更多的命令。命令通过换行符或分号隔开。每一条命令都包含一个或多个单词,第一个单词是命令名,其他的单词是命令的参数。

set a 1
set b 2

set a 1 ;set b 2

上述代码可以两行表示,也可以用换行符隔开,一行表示。

处理命令

Tcl处理一个命令分为两步:解析和执行。具体的Tcl处理一条命令的步骤见下图。

在解析这一步,Tcl解释器将命令分解为单词,并执行替换。对每个命令进行解析的方法是一样的。在解析阶段,Tcl解释器不认为各单词的值有任何具体意义。Tcl只是进行一系列简单的字符串操作。

执行阶段,命令中的每个单词都有了具体的意义。Tcl把第一个单词作为命令名称,检查此命令是否存在或者被定义过,并且查找完成该命令功能的命令过程。

双引号引用

双引号取消其中的单词和命令分隔符的特殊解释。如果一个单词的第一个字符是双引号,那么这个单词就由另一个双引号标记结束。双引号并不是单词的一部分,而是此单词的定界符而已。如果一个单词包含在双引号中,那么其中的空格,制表符,换行以及分号都作为普通字符处理。

如果单词不是以双引号开头,那么单词中的双引号就会被当成普通字符处理,而不是定界符。

在双引号中,变量替换,命令替换以及反斜线替换正常工作。如果想要在由双引号括起来的单词中包含双引号字符,则应该使用反斜线替换。

大括号引用

大括号提供了更彻底的引用形式,它会取消其中所有特殊字符的特殊意义。如果从一个单词以左大括号开头,那么直到与它配对的右大括号为止,所有字符都将被原封不动地识别为这个单词的值。

和双引号不同,大括号可以嵌套使用。在过程定义和命令流控制中。经常会用到嵌套的大括号来把一个或多个参数表达为需要处理的脚本。大括号最重要的应用之一就是“延期处理”。延期处理意味着特殊字符不会被Tcl解析器立即处理,作为参数传递给命令过程。 例如下面这个过程,它统计在列表中某个特定值出现的次数。

整个过程块都括在大括号中,它会原封不动地传递给proc。在解析proc命令时变量list的值不会被替换进来。正常工作时,在每次调用这个过程时$list都需要读入不同的值。

下面将演示这句Tcl脚本运行时会发生什么,如下图所示:

occur的第二个参数由大括号引用,这会把整个数字列表作为一个单词传给occur。Tcl过程管理机制又会把整个过程块传给Tcl解释器进行处理。在Tcl解析该过程块中的foreach命令时,变量list的值就会替换进来,但这时foreach的最后一个参数(循环体)还在一个大括号当中,此时不会进行替换。foreach命令过程会把变量el依次赋值为列表中的命令,替换变量el和value的值。在上图中,if判断通过,处理incr命令。

参数展开

通常来说,列表的值可能是变量或命令替换的结果。但是因为Tcl解释器是从左向右进行替换的,而列表的值被视为一个单词,即列表中嵌入的空格不会被视为单词分隔符。在某些情况下,单层替换规则有害无益。这个脚本就不能删除所有以.o结尾的文件的任务。

file delete [golb *.o]

glob命令返回的是符合*.o形式的文件名列表。例如a.o b.o c.o。然而,在整个文件名称列表中是作为一个参数传递给file delete的,file delete会因为找不到名为a.o b.o c.o的文件而失败。想要file delete正常工作,可以通过参数展开来完成这一任务。如果一个单词以字符串{*}开头,之后紧接着非空白字符,Tcl会移除开头的{*},把该单词的剩余部分作为含有单词分隔符的语句进行解析与替换。在替换之后,Tcl会再此解析这些单词,但不进行替换,校验确定它们的确是一个或多个语法完整的单词。如果校验通过,这些单词会被独立地加入命令行进行处理;否则,Tcl会报语法错误。因此,下面两条语句是相同的。并且可以使用eval命令解决刚才file delete遇到的问题。

file delete {*}[glob *.o]
#由Tcl解释器进行解析和参数展开后,与下面这条语句相同:
file delete a.o b.o c.o 
#或者使用eval命令解决file delete的问题
eval file delete [glob *.o]

注释

如果一条命令的第一个非空白字符是#,那么这一行将视为注释而忽略。注意注释符必须出现在Tcl预期将获得命令的第一个字符的位置上。如果注释符出现在其他地方,会被看作一个普通字符,看成一个命令单词的一部分。

#This is a comment 
set a 100
wrong #args:should be "set varName ?newValue?"
set b 101     ;#This is a comment

第二行的符号#就没有被视为注释符,因为它出现在一条命令的中间。其结果是,第一个命令set接收到6个参数,产生了错误。最后一个#被视为注释符,因为它紧接在标记一条命令中止的分号后面。Tcl的语法虽然简单而统一,但也产生一些意料之外的结果。它要求作为注释符的#必须出现在预期将获得命令的第一个字符的位置上,这就导致下面这个示例中的#行不是注释。

set example {
    #This is not a comment
}

大括号之间的所有字符都视为一个参数处理,作为字符串值赋给set指定的变量,相反,考虑下面这个示例:

if {$x < 0} {
 #This is a comment
puts "The result is negative."
}

这里,Tcl解析器把两个大括号里的内容作为两个参数传递给if命令。if命令会把第一个参数视为布尔型表达式处理,如果结果为真,它就调用Tcl解释器将第二个参数作为Tcl脚本处理。在Tcl解释器再次进行解析时,以#开头的这一行才被识别注释。Tcl的一致语法引起另一个后果是,在运行代码时,出现注释中的大括号常常会导致错误。参考下面这个示例:

proc countdown {x} {
puts "Running countdown"
#Incorrectly comment out this code block {
 while {$x >=0} {
    puts "x =$x"
    incr x -1
 }
  }
}

当Tcl解释器解析这个命令时,第一行末尾的左大括号标记着一个单词的开始。标记单词结束的右大括号出现在最后一行。这个单词中包含着什么脚本与这一步解析无关,Tcl解释器出现只是把命令行解析为单词集,对单词中嵌套的其他大括号只作为普通字符对待。然后Tcl将单词传递给proc命令,该命令会把这些单词作为countdown过程的实现脚本存储。试图运行countdown过程时,Tcl解析并运行这个脚本。首先执行puts命令,然后Tcl忽略整个第二行(包含左大括号),将其视为注释。然后处理while命令,它的脚本参数由incr命令后面那个右大括号标记结束。然后Tcl期待找到下一条命令,却在该行开头取到右大括号,它将这个右大括号视为要处理的命令名,结果导致如下错误:

countdown 3
#开始运行countdown过程
Running countdown
x    =    3
x    =    2
x    =    1
x    =    0
#将运行到x=0;就会跳出while循环块,将获得一个新的命令incr后的右大括号
#右大括号视为处理的命令,结果会导致错误
invalid command name "}"

Notes:一般来说,尽量避免在注释中出现大括号,如果确实需要在注释中使用大括号,确保它们时配对的;对于每一个左大括号,都保证有一个正确匹配的右括号。

正常返回和异常返回

Tcl命令可能以多种方式完成。正常返回时最常见的结果,这意味着 命令正常执行完成,返回一个字符串值。Tcl还支持命令的异常返回。错误是异常返回的最常见形式。当错误返回时,就意味着命令未能按照所期望的功能执行。该命令被放弃,它后面的脚本中的命令被略过。

Tcl替换机制

1.Tcl解析一条命令时,只从左向右解析一次,进行一轮替换。每一个字符只会被扫描一次。

2.每一个字符只会发生一层替换,而不会对替换后的结果再进行一次扫描替换。



版权声明:本文为qq_45072498原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。