TCL 语言简介

Tcl 语言

  • 没有固定的(fixed)文法
  • 由解释器(分析器)和执行命令的过程来定义
  • 大小写敏感

Tcl 基础

  •  
    • 分析: Tcl 不给字的的值提供任何意义。它只是完成简单的字符串操作,例如,变量替换。Tcl 只进行一遍替换(每个字符被严格的扫描一次)。一次替换的结果不为了进一步的替换而被扫描。
    • 执行: 为命令的参数提供意义。Tcl 假定字序列的第一个字是命令,检查是否定义了命令,并且定位一个命令过程 来执行。
    •  

        set a 5
        set b a+8

        set a 5
        set b [expr $a+8]

      第一条命令把字符串 5 赋给变量 a。第二条命令把字符串 a+8 作为新值存储在 b 中。要得到值 13,你必须显式的去求值,比如:

    • $ 变量替换: 不声明变量 -- 它们都是任意长度的字符串类型。变量替换可以在一个字中的任何地方发生(例如 button .b$num)。
    • [ ] 命令替换: 在方括号中任何东西被作为一个 Tcl 脚本来求值。
    • / 反斜线替换: 转义符,用于 $、换行、引号等。
    • 双引号: 空格,tabs,换行,分号被作为普通的字符对待(通常的 $,命令,和反斜线替换正常发生)。
    • 花括号: 所有特殊字符失去特殊意义(不发生替换)。花括号延期 求值。
    • 注释: 如果第一个非空字符是 '#',知道换行的所有东西都是注释。如果 '#'在其他任何地方出现,它被当作通常的字符。

    脚本, 命令, 和字(word)

    Tcl 是基于字符串的解释型命令语言。它在语法上的简单性和语义上的通常的感官性的方式(common sensical approach)使这门语言易于学习和熟练。这个简短的教程将集中于最基本的概念,使你能尽快的起步。

    Tcl 脚本由一个或多个命令组成,用换行或分号分隔。

    command arg1 arg2 arg3 ...

    命令是基本的执行单元(element)。一个命令跟随着一个或多个字,这些字是(命令)参数(parameters or arguments), 字之间用空格或 tab 分隔。

    ex: puts stdout "My first Tcl script."
    
    = > My first Tcl script.

    求值(Evaluating)一个命令

    两步过程: 分析执行

    注意: 参数在缺省时是被引用的 -- 如果你想求值,你必须显式的提出要求
    例如:

    每对方括号调用一次附加的求值。对于 Tcl 你必须记住的一件事是它只做你认为它将要做的事。求值的模型很直接了当。有一个单独的命令和零或多个参数。这些参数可以依次是必须被求值的有参数的命令。这些命令的返回值变成要被求值的最初的命令的参数。

     

    变量,命令和反斜线替换

    在 Tcl 中通过使用变量来维护状态,变量用 "set" 命令来声明和实例化(instantiate)。

    例如:

    set loopCounter 0

    注意分开的变量声明是没有必要。使用美元标号来检索变量值,这对 shell 程序员和 Perl 开发者是很熟悉的,在何时使用与何时不使用美元标号的相关规则上有细微的区别。

    例如:

    if
    
    ($loopCounter >  10) {
    
     do something
    
    }

    引用和注释

变量

  •  
      set yearTotal 0
      
      foreach month {Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec} {
      
         set yearTotal [expr $yearTotal+$earnings($month)]
      
      }
      set matrix(1,1) 140
      
      set matrix(1,2) 218
      
      set matrix(1,3) 84
      
      set i 1
      
      set j 2
      
      set cell $matrix($i,$j)
      
      => 218

      * 注意这里没有空格,三个元素名是 1,1 、1,2 和 1,3。 所以:

      matrix(2,5)
      不同于
      matrix(2, 5)

    简单变量 有一个名字和一个值(被作为一个字符串存储)。

    数组是一组元素(element),每个元素有它自己的名字和值。数组名和元素名可以是任意的字符串 (有时叫做关联数组 来与要求元素名是整数的数组相区别)。

    例如:

    Tcl 只实现了一维数组,但是多维数组可以通过通过连接多个索引成一个单一的元素名来模拟。

表达式

  •  
    1. 通过通常的 Tcl 分析机制
    2. 通过表达式求值器(evaluator) 在求表达式的值时完成附加的一轮变量和命令替换

      expr { 2*sin($x) }

      语法: expr arg ?arg arg ...?

    • 关系操作符 (<, <=, >, >=, ==, !=) 返回 0 (假) 或 1 (真)。
    • 位操作符 (&, |, ^, <<, >>, ~) 要求操作数是整数。

        << 和 >> 使用右操作数作为移位记数(单位是位)。在左移时,零被移入低位(low order) 字节。右移是“算术右移”,对正数移入零,对负数移入一(不象 ANSI C,它是依赖机器的)。~ 是一的补码(one's complement)操作符,它翻转所有位。

    • 选择操作符是三重(ternary)操作符 ?: ,如果第一个操作数是真,求第二个操作数的值,否则求第三个操作数的值。

        语法: expr { ($a < $b) ? $a : $b}

      set pow 1
      
      while {$pow<$num} {
      
         set pow [expr $pow*2]
      
      }
      set x 0
      
      set y 00
      
      if {$x==$y} {
      
         ...
      
      }

      将使用算术比较并且这个测试将求值成 1。要强制对数值操作数进行字符串比较,使用象 string compare 这样的命令。

    • real 和 int: int => real
    • 非数值字符串 和 int/real: int/real => 字符串
    • 整数 (int 类型): 32 位精度
    • 实数 (double 类型): 64 位精度
    • 在求一个表达式的值时数值始终被用内部形式保持,只在必要时才转换回字符串。
    • 转换实数成字符串缺省是 6 位有效数字(significant digits)。设置 Tcl 全局变量 tcl_precision 可以变更有效数字。注意:在一个使用 IEEE 浮点数的机器上设置 tcl_precision 成 17 将保证转换出的字符串不丢失精度(例如,当一个表示式被转换成字符串并在以后要在一个数值计算中使用时)。

    表达式组合操作数和操作符来建立一个新值。

    操作符

    替换

    对于表达式操作数替换以两种方式发生。

    例如:

    花括号致使 Tcl 分析器不做对 做变量替换。当变量求值器遇到美元号的时候,它自己完成变量替换。这对 expr 命令是非常有用的,而且对其他的象 while 这样的的命令也是有用的, while 重复的求一个表达式的值并希望每次都有不同的结果。

    例如:

    如果省略了{$pow<$num} 的花括号,while 的参数将是一个不变的表达式如 1<44,这将导致无限的循环。

    字符串操纵

    Tcl 允许一些操作符有字符串操作数,如: <、 >、<=、>=、 == 和 != 。对所有其他的操作符,操作数必须是数值。如果一个或两个操作数不能做数来分析,Tcl 将使用字符串比较。

    例如:

    类型和转换

    Tcl 求表达式的值尽可能的得出数值。只对一个或两个操作数不象是数值的关系操作符进行字符串操作。对有着不同类型的操作数自动进行转换:

    还有,分别的使用 doubleint 和 round 来转换 int 成 real 和转换 real 成 int。

    精度

列表

译注:在 Tcl 中用一对问号表示其中的部分是可选的,在其他语言的语法描述中这项任务通常用方括号完成。

    一个列表是一组有序的元素(用空格分隔),每个元素都可以有任意的字符串值。列表可以是嵌套的。

    命令 结果
    concat ?list list ...? 连接多个列表成一个单一的列表(每个list 中的所有元素都变成结果列表中的元素)并返回这个新列表。
    join list ?joinString? 用 joinString 作为分隔符把列表的元素连接到一起并返回结果, joinString 缺省是空格。
    lappend varName value ?value ...? 把每个 value 作为列表的元素添加到变量 varName 上并返回这个变量的新值。如果变量不存在则建立它。
    lindex list index 从 list 返回第 index 个元素 (0 参照第一个元素)。
    linsert list index value ?value ...? list 的第index 个元素的前面插入所有的 value 并返回这个新列表(0 参照第一个元素)。

    例如:
    set x {a b {c d} e}
    => 
    a b {c d} e
    linsert $x 2 X Y Z
    => a b X Y Z {c d} e

    list ?value value ...? 返回一个元素是所有的 value 参数的列表。
    llength list 返回在 list 中的元素数。
    lrange list first last 返回一个list 的从 first 到 last 元素组成的新列表。如果 last 是 end,它选择list 的直到最后的所有的元素。
    lreplace list first last ?value value ...? 用零个或多个新元素替换 list 的从 first到 last 的所有元素,value 参数就是这些新元素,返回这个新列表。
    lsearch ?-exact? ?-glob? ?-regexp? list pattern 返回在 list 中匹配 pattern 的第一个元素的索引,如果没有则返回 -1。 选项开关选择一项模式匹配技术(缺省是: -glob)。
    lsort ?-ascii? ?-integer? ?-real? ?-commandcommand? ?-increasing? ?-decreasing? list 返回把 list 的元素进行排序的一个新的列表。开关决定比较函数和排序的顺序 (缺省是: -ascii -increasing)。
    split string ?splitChars? 用 splitChars 的实例来分割 string 并把这些实例之间的字符转变成一个新列表的元素并返回这个新列表。

控制流

    if test1 body1 ?elseiftest2 body2 elseif ...? ?else bodyn?  
    while test body  
    for init test reinit body  
    foreach varName list body  
    switch ?options? string pattern body ?pattern body ...?
    switch ?options? string { pattern body ?pattern body ...? }
    选项: -exact (精确匹配), -glob (同于字符串匹配), 或 -regexp (正则表达式匹配)。

    如果一个脚本是 '-', switch 使用下一个模式的脚本 (比如说,多个模式可以执行同一个脚本)。

    default 模式用于捕捉不匹配(unmatched)的模式。

    eval arg ?arg arg ...? 用于建立和执行 Tcl 脚本的通用的建造块。它接受任意数目的参数,用空格作为分隔符把所有的 args 连接起来,接着把结果作为一个 Tcl 脚本来求值并返回它的结果,这在你需要进入(force)另一分析层次时很有用。

过程

  •  

      proc procedure_name arguments ?args? body

      proc plus {a b} {expr $a+$b}
      proc inc {value {increment 1}} {
      
       expr $value+$increment
      
      }
      
      incr 42 3
      
      => 45
      
      incr 42
      
      => 43
      proc sum args {
      
         set s 0
      
         foreach i $args {
      
            incr s $i
      
         }
      
         return $s
      
      }
      
      sum 1 2 3 4 5
      
      => 15
      
      sum
      
      => 0
      proc parray name {
      
         upvar $name a
      
         foreach el [lsort [array names a]] {
      
            puts "$el = $a($el)"
      
         }
      
      }
      
      set info(age) 37
      
      set info(position) "Vice President"
      
      parray info
      
      => age = 37
      
         position = Vice President

      upvar #0 other x

      upvar 2 other x

      proc do {varName first last body}{
      
         upvar $varName v
      
         for {set v $first} {$v <= $last} {incr v}
      
            uplevel $body
      
         }
      
      }
      
      set squares {}
      
      do i 1 5 {
      
         lappend squares [expr $i*$i]
      
      }
      
      set squares
      
      => 1 4 9 16 25

    过程是在一个可以重用的包装模块中的通常的 Tcl 命令。在任何时候都可以定义过程,并可以用传值或传引用来传递参数。除非显式的使用 return 命令返回,否则返回的值是过程体的最后一条语句的结果。

    语法:

    例子:

    参数

    在一个过程中使用的参数是本地的。要引用全局变量,在过程的生存期间 global 命令在过程中绑定变量(译注:就是访问全局变量)。参数可以有缺省值并象下面这样指定:

    如果有的话,缺省的参数必须是过程的最后一个参数。如果没指定一个缺省则要求有这个参数。

    用 args 来支持可变数目的参数,它被放在参数列表的最后。过程使用 args 参数作为一个列表,它的每个元素都是额外的参数。如果没有额外的参数,则设置 args 为空串。

    引用调用

    upvar 命令提供一个访问一个过程上下文外部的变量的通用机制。可被用于访问在一些其他的活跃的过程中的全局或本地变量,但它最常用于传递数组(因为没有数组的值,只有数组的元素)。

    例子:

    upvar 也可以被用于访问在调用栈的不同层次上的变量。

    通过本地变量 x 来使全局变量 other 可访问(#0 指定访问全局变量 other 而不管调用栈的层次)。

    使在当前的过程的调用者的调用者中的变量 other 作为本地变量 访问(2 指定在调用栈中两层之上)。

    建立新的控制结构

    uplevel 是一个 eval 和 upvar 之间杂交的命令。它象 eval 那样把它的参数作为一个脚本来求值,但象upvar 那样在一个不同的栈层次的变量上下文中求脚本的值。使用 uplevel,可以定义作为 Tcl 过程的一个新的控制结构。

    例子:

    象 upvar 一样,uplevel 接受一个可选的初始参数来指定一个显式的栈层次。

字符串操纵

  •  

      string match pattern string

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

      regexp ?-nocase? ?-indices? {patterninput_string ?variable ...?

      regexp {([0-9]+) *([a-z]+)} "Walk 10 km" a b c

      regexp -indices {([0-9]+) *([a-z]+)} "Walk 10 km"a b c

      regsub ?-nocase? ?-all? pattern input_string replacement_value new_string

      format "The square root of 10 is %.3f" [expr exp(10)]
      => The square root of 10 is 3.162

      %s 字符串
      %d 十进制整数
      %f 实数
      %e 科学计数法(mantissa-exponent)表示的实数
      %x 十六进制数
      %c 字符

      scan parse_string format_string ?variable ...?

      例子:

      scan "16 units, 24.2 margin" "%d units, %f" a b
      => 2

      string index "See Spot run." 5
      => p

      string range "See Spot run." 5 8
      => Spot

      string range "See Spot run." 5 end
      => Spot run.

    Tcl 是完全 8 位的(不只是 ASCII 7 位子集)。Tcl 不为 ASCII 子集外的字符提供任何解释。Tcl 存储字符串使用一个 null (零)字符作为终结,所以不可能在字符串中存储零字符。要表示二进制数据,要把它转换成包括非零字符的形式,例如,把字节转换成相应的十六进制的值。

    通配符-式样(Glob-style)模式匹配

    Tcl 模式匹配的最简单的形式。

    语法:

    匹配则返回 1,不匹配返回 0。在匹配中使用特殊的字符串

    * 匹配零个或多个字符的任何序列。
    ? 匹配任何单一字符。
    [chars] 匹配在 chars 中的任何单一字符。如果 chars 包含一个 a-b 样式的序列,在 a 和 b 之间的包括的任何字符都匹配。
    /x 匹配单一字符 。这提供了避免解释模式中的字符 *?[]/ 的一种方式。

    用正则表达式的模式匹配

    正则表达式可以有多层的结构。基本的建造块叫做原子,并且最简单的正则表达式的形式由一个或多个原子组成。一个正则表达式要匹配一个输入字符串,输入中必须有一个子串(substring),每个正则表达式的原子(或其他成分)匹配子串的相应的部分。例如,正则表达式 abc 匹配包含 abc 的任何字符串比如 abcdef或 xabcy

    举个例子,下面的模式匹配要么是十六进制数要么是十进制数的任何字符串。

    语法:

    如果没有匹配则返回 0,如果有一个匹配则返回 1。

    注意,模式必须被包裹(enclosed)在花括号中,这样字符 $[、和 ] 可以被传递给(pass through to)regexp 命令而不触发变量或命令替换。

    如 果调用 regexp 有在输入字符串之后的参数,每个参数都被作为一个变量的名字对待。第一个变量被添入匹配整个正则表达式的子串。第二个变量被添入与模式中最左的圆括号中的 (parenthesized)子表达式(subexpression)相匹配的子串的相应的部分,第三个变量被添入匹配下一个子表达式并以次类推。如果 有变量名比圆括号中的子表达式多,则额外的变量被设置成空串。

    例子:

    变量 a 将被持有值 "10 km", b 将持有 "10" 而 c 将持有 "km"。

    开关 -nocase 指定匹配大小写不敏感。开关 -indices 指定增加的变量不应被添入匹配的子串的值,而是给出在输入字符串中子串的范围的最先和最后的索引的一个列表。

    例子:

    变量 a 将被持有值 "5 9",b 将持有 "5 6" 而 c 将持有 "8 9"。

    字符 意义
    . 匹配任何单一字符。
    ^ 匹配在行首的 null 字符串。
    $ 匹配在行尾的 null 字符串。
    /x 匹配字符 
    [chars] 匹配任何在 chars 中的单一字符。如果第一个字符是 ^, 匹配不是在 chars中的其余字符的任意单一字符。在 chars 中的 a-b 样式的序列被作为在a和 b 之间包含的所有 ASCII 字符的速写。如果在 chars 的第一个字符(可能跟随着 ^) 是 ],它被作为字符(literally)对待(作为 chars 的一部分而不是一个终结符)。如果一个 - 出现在 chars 的最前面或最后面,它被作为字符对待。
    (regexp) 匹配与正则表达式 regexp 匹配的任何东西。用于组织和标识匹配子串的各部分。
    * 匹配有零个或多个前面的原子的匹配的一个序列。
    + 匹配有一个或多个前面的原子的匹配的一个序列。
    ? 匹配要么是空串要么是前面的原子的一个匹配。
    regexp1 | regexp2 匹配与 regexp1 或 regexp2 匹配的任何东西。

    为替换而使用正则表达式

    语法:

    给 regsub 的第一个参数是正则表达式模式。如果在输入字符串中发现一个匹配,regsub 返回 1, 否则它返回 0 (同于 regexp 命令)。如果模式被匹配,输入字符串中的子串被用第三个参数所替换并且新串被存储在第四个参数中。如果没发现匹配,第四个参数包含原始的输入串。使用了两个开关: -nocase-all 导致在输入串中所有匹配的子串都被替换。 等价于 regexp 命令的 nocase 开关,

    格式化输出

    format 命令提供了象 ANSI sprintf 那样的设施。

    例子:

    其他的格式指定符(specifier):

    格式化命令也被用于改变一个值的代表。例如,用 %c 格式化一个整数生成这个整数代表的 ASCII 字符。

    用 scan 分析字符串

    语法:

    字符函数

    字符串操纵命令是 string 命令的选项。

    查找和比较

    用 first 或 last 查找一个子串返回子串的第一个字符的位置(以 0 开始,对应输入串中第一个字符以)。如果没发现匹配则返回 -1。

    string first th "The trains were thirty minutes late this past week"
    => 16

    string last th "The trains were thirty minutes late this past week"
    => 36

    如果串匹配 Compare 返回 0 ,如果第一个比第二个排序在前返回 -1, 如果第一个比第二个排序在后返回 1。

    string compare twelve thirteen
    => 1

    string compare twelve twelve
    => 0

    长度, 大小写转换, 修剪(trimming)

    string length "not too long"
    => 12

    string toupper "Hello, World!"
    => HELLO, WORLD!

    string tolower "You are lucky winner 13!"
    => you are lucky winner 13!

    string trim abracadabra abr
    => cad

    string trim 接受一个要修剪的字符串和可选的一组修剪字符,并且从它的参数字符串的开头和结尾删除所有的修剪字符的实例,返回修剪后的字符串作为结果。trimleft 和 trimright 选项除了只是在字符串的开头或结尾删除之外,以相同的方式工作。trim 命令最常用于删除额外的白空格,如果没指定修剪字符,它们的缺省是白空格(空格、tab、换行、回车、和 form feed)。

资源

上面的许多例子取自 John Ousterhout 的书:
Tcl and the Tk Toolkit
作者: John K. Ousterhout
出版商: Addison Wesley, 1994

尽管有许多关于 Tcl 语言的书,依我看这本书是最好的之一。

----------------------------------------------------------------------------------

背景补充: http://doc.linuxpk.com/43225.html


你可能感兴趣的:(TCL 语言简介)