Tcl语法概要

Tcl的语法规则算是比较简单的。简单来说,可以用下面几条规则来归纳:

  1. Tcl脚本由命令序列组成。
    • 命令以换行符或者分号;结束。
    • 解析器每次只解析一条命令,然后执行这条命令。
  2. Tcl命令由“单词”(Word) 序列组成
    • 单词之间通过空白字符(空格,Tab)分隔
    • 第一个单词是命令的名字,剩余的单词是命令的参数
    • 参数的具体含义仅取决于命令自身的定义
  3. 单词(Word)可以通过分组(Group)以允许特殊字符(比如空白字符)的出现
    • 双引号:"..."中的内容允许替换的发生
    • 大括号:{...}中的内容不允许替换的发生
    • 方括号:[...]中的内容作为一条命令执行,并整体替换为执行结果
  4. 单词(Word)在解析过程中会发生替换
    • 变量替换:比如set var "value = $name" 中的$name
    • 命令替换:比如set var "value = [expr 3+4] 中的[expr 3+4]
    • 转义替换:比如\n表示换行符,\t表示制表符,等
  5. 字符#开始的行是注释

0x01: Tcl脚本由命令序列组成

一个简单的Tcl脚本看起来如下

set name "noyesno"
puts "My name is $name"

set x 3 ; set y 4
puts [expr $x+$y]

可以看出通常情况下,命令写在一整行里,即“命令以换行符结束”。但有时希望一行可以写多个命令,这是可以通过分号;进行分隔,即所谓“命令以分号结束”。

Tcl解释器,每次只解析并执行一条命令。这意味着,后续的命令是否有错误(无论是语法错误还是执行错误)不影响当前命令的解析和执行。

0x02: Tcl命令由“单词”(Word) 序列组成

set x 0
while {$x < 9} {
  incr x
  puts $x
}

比如这条while命令,在Tcl里,从根本上来说,它只是一条普通命令,而不像在其他语言里那样,是一种语法规定。只不过while循环如此常见和常用,Tcl的发行包里提供了这条命令的默认实现。

在这条命令里,总共有3个单词(Word),第一个是命令名,后两个分别是循环的条件判断和循环的执行内容。需要指出的是,这里后两个参数所代表的意义实际上是针对while这个命令来说的。如果while命令的含义不像我们所理解的那样是一个循环的话(而这在Tcl里实际上是允许的),那么后两个参数的含义是不明确的。这也就是所谓的“参数的具体含义仅取决于命令自身的定义”。

0x03: Tcl命令单词的分组

在上面的介绍里,我们提到了三种分组,即分号,大括号和方括号。而实际上方括号虽然有分组的作用,实质上则更接近替换的概念。因此,我们说分组时,实际上主要是指分号和大括号这两种。而分号和大括号的区别则主要在于是否允许替换。

上面已经提到的while循环的例子里已经用到了分组,是用的大括号。我们可以看到在大括号中出现了变量,也出现了换行符。变量因为出于大括号中没有被替换,换行符也因为出于大括号中而没有“结束”当前命令。

如果把上面例子中的大括号换成双引号呢?

set x 0
while "$x < 9" "
  incr x
  puts $x
"

这在语法上是没有错误的。while命令仍然接受了两个参数,只是条件判断部分变成了0 < 9,执行内容部分变成了了 incr x ; puts 0。看到这里,就比较容易意识到,我们的while循环发生了死循环,并且每次循环都会输出同样的数字。它等效于:

set x 0
while {0 < 9} {
  incr x
  puts 0
}

方括号在提供替换功能的同时,实际上也起到了分组的作用。最简单的例子比如

set numbers [list 1 2 3 4 5 6]

这条命令,有三个单词(Word),而不是更多。其中第三个[list 1 2 3 4 5 6]就是所谓的命令替换形式的分组。

0x04: 单词(Word)在解析过程中会发生替换

Tcl里的替换从概念上说有三种:变量替换,命令替换,转义替换。

可以用一个简单的例子来同时看一下这三种情况。

set name "noyesno"
set text "My name is $name.\nMy age is [expr 3+4]."

这个例子中的$name即变量替换,\n即转义替换,[expr 3+4]即命令替换。

对于替换,除了大括号会阻止替换的发生外,其他的形式——双引号或者没有任何括号——都允许替换的发生。

0x05: 字符#开始的行是注释

在Tcl中,默认的注释方式是字符#开始的行。这很容易理解。但在Tcl里会有一些“奇怪”的情形。

比如下面的例子里,注释符号#之前的分号;是必须的。

set name "noyesno" ;# this is a comment

分号的本身含义是命令的结束,或者分隔两条相邻的命令。从这个角度讲,注释符号#似乎可以看作是一个特殊的命令。

下面的例子,则通过一个行尾的反斜线云逊注释行跨行。只有第二个puts命令会得到执行。第一个puts行则变成了注释的一部分。

# comment cross lines \
puts 123
puts 456

这个特性在需要将Tcl脚本和Shell脚本结合时,会显得很有用。

程序的注释虽然乍听之下都不复杂,但却是编写可维护的程序重要部分,更多的关于注释的话题,可以参见 Tcl中的注释技巧