Tcl 编程/简介

维基教科书,自由的教学读本

简介[编辑]

Tcl 到底是什么?[编辑]

Tcl 这个名字派生自“工具命令语言(Tool Command Language)”,英文发音为“tickle”,中文发音为“太酷”。Tcl 是简单明了、彻底解释执行的开源编程语言,提供常见的设施,如变量、过程、控制结构,还有数量众多的有用特性,这些特性在其他主要语言种很难找到。几乎每个现代操作系统都可以运行 Tcl,包括 Unix、Macintosh、Windows、甚至 Windows Mobile。

可以想象 Tcl 的灵活性可以使其胜任几乎所有的应用程序作业,但他也擅长诸多关键领域:与外部程序自动交互、以库的形式嵌入应用程序、语言设计和通用脚本。

Tcl 在 1988 年由约翰・奥斯特豪特创立,以 BSD 风格的许可协议发布。(这种协议允许你做任何 GPL 所允许的,加上可以闭源。)当前的稳定发布版 8.6.0 发布于 2012 年 12 月。

Tcl 第一个主要的 GUI 扩展是 Tk,它的侧重于快速的 GUI 开发。这就是为什么 Tcl 现在通称 Tcl/Tk 的缘故。

Tcl 提供简单的 far-reaching 内省和符号,与人们常见的 Forrtran/Algol/C++/Java 大大不同。尽管 Tcl 是基于字符串的语言,但还是有诸多面向对象扩展,如 Snitincr TclXOTcl,还有更多。从 8.6 起,语言内核提供一个基础 OO 架构,称作 TclOO,incr Tcl 新版的后端已经切换为 TclOO。

Tcl 原本便是作为可重用的命令语言用于实验性的计算机辅助设计工具。解释器以 C 库的形式实现,于是 Tcl 便可以链接到任何应用程序中了。为 Tcl 解释器添加新功能也是非常容易的,因此 Tcl 被视为可以轻松集成的理想可重用“宏语言”。

不过 Tcl 作为一种语言本身可被视作以下语言的结晶:

  • LISP/Scheme(主要是尾递归)
  • C(控制结构关键字,expr 语法)
  • Unix shell(强大的组织能力)

小身材,大语言[编辑]

你可能推测一种“万物皆命令”的语言会是“过程式”“命令式”的。不!Tcl 的灵活性允许他轻松扩展成函数式或者面向对象风格。视下。

另外,在 Tcl 中寻乐子实现其他语言也是挺方便的。或名之“计算机科学实验室(Computer Science Laboratory)”。举例来说,下面是 Tcl 中计算数字列表平均值的做法(实现了类 J 的函数式语言,见下文 Tacit 编程):

Def mean = fork /. sum llength

或者在一种类似 FORTH 或 PostScript 的 RPN 语言中:

mean  dup sum swap size double / ;

但是在更传统的“过编程语言”中会是这样

proc mean list {
   set sum 0.
   foreach element $list {set sum [expr {$sum + $element}]}
   return [expr {$sum / [llength $list]}]
}


另外一种风格(列表长的时候速度会慢,但没有除 Tcl 外的其他依赖)。使用在列表上 join + 来构建表达式,并最终求值:

proc mean list {expr double([join $list +])/[llength $list]}

Tcl 8.5 起,数学操作符被暴露为命令和扩展操作符,这种风格比上一种好些:

proc mean list {expr {[tcl::mathop::+ {*}$list]/double([llength $list])}}

或者你已经导入了 tcl::mathop 的操作符,那直接

proc mean list {expr {[+ {*}$list]/double([llength $list])}}

注意这些全部都在 Tcl 中,只有前两种要求实现 Def 和 ':'。

更实际的方面是 Tcl 对“面向语言编程”很友好,当解决问题是,定义一个新的小领域特定语言,最简洁地定义并解决问题,然后——去实现那个语言!

为什么我要用 Tcl?[编辑]

问得好!常见回答是:“选择对的工具”。手艺高超的木匠会有一套好工具,并明白如何最好地使用他们。

Tcl 是其他脚本语言的有力竞争对手,如 Perl, Python, PHP, Visual Basic, Lua, Ruby 等等等等。每种都有优势的缺点,不过如果都适合,那就纯粹成为了口味问题。

喜欢上 Tcl 的理由有:

  • 最简单的语法(扩展相当容易)
  • 跨平台:Mac,Unix,Windows
  • 强力国际化支持:所有东西都是 Unicode 字符串
  • 强健且通过测试的代码基础
  • Tk 图形用户界面工具包说 Tcl 语言
  • BSD 授权,和 GPL 一样允许开源,但也允许闭源
  • 很友好的社区,可通过新闻组、维基或即时通讯联系到 :-)

Tcl 不是每种问题的最好解决方案,但学习它对于发现 Tcl 有用之处也是很有帮助的。

实战:微型 Web 服务器[编辑]

在填鸭式学习之前,这样一个稍微长点的例子可能会有用,嗯,用来感受 Tcl 看起来如何。下面四十一行代码是一个完整的小型静态 Web 服务器(HTML 页面、图片),但也有小部分 CGI 功能:如果 URL 以 .tcl 结尾,Tcl 解析器就会启动,并返回结果(动态生成的页面)。

注意这里不需要任何附加包,Tcl 的 socket 命令可以相当漂亮地处理此类事宜。一个套接字就是一个可被 puts, gets 读写的频道(channel)。fcopy 命令在后台异步复制一个频道到另外一个频道,这些频道可以是进程管线也可以是打开的文件。

这个服务器在 200MHz Windows 95 下测试工作正常,调制解调器为 56k,并发地服务客户端。另外,因为下面代码很简洁,所以这个例子对于 HTTP (之部分)如何工作也是有教育意义的。

# DustMotePlus - CGI 子集支持隨付
set root      c:/html
set default   index.htm
set port      80
set encoding  iso8859-1
proc bgerror msg {puts stdout "bgerror: $msg\n$::errorInfo"}
proc answer {sock host2 port2} {
    fileevent $sock readable [list serve $sock]
}
proc serve sock {
    fconfigure $sock -blocking 0
    gets $sock line
    if {[fblocked $sock]} {
        return
    }
    fileevent $sock readable ""
    set tail /
    regexp {(/[^ ?]*)(\?[^ ]*)?} $line -> tail args
    if {[string match */ $tail]} {
        append tail $::default
    }
    set name [string map {%20 " " .. NOTALLOWED} $::root$tail]
    if {[file readable $name]} {
        puts $sock "HTTP/1.0 200 OK"
        if {[file extension $name] eq ".tcl"} {
            set ::env(QUERY_STRING) [string range $args 1 end]
            set name [list |tclsh $name]
        } else {
            puts $sock "Content-Type: text/html;charset=$::encoding\n"
        }
        set inchan [open $name]
        fconfigure $inchan -translation binary
        fconfigure $sock   -translation binary
        fcopy $inchan $sock -command [list done $inchan $sock]
    } else {
        puts $sock "HTTP/1.0 404 Not found\n"
        close $sock
    }
}
proc done {file sock bytes {msg {}}} {
    close $file
    close $sock
}
socket -server answer $port
puts "Server ready..."
vwait forever

这是个我用来测试的小“CGI”脚本(储存为 time.tcl):

# time.tcl - 微型 CGI 腳本
if {![info exists env(QUERY_STRING)]} {
    set env(QUERY_STRING) ""
}
puts "Content-type: text/html\n"
puts "<html><head><title>Tiny CGI time server</title></head>
<body><h1>Time server</h1>
Time now is: [clock format [clock seconds]]
<br>
Query was: $env(QUERY_STRING)
<hr>
<a href=index.htm>Index</a>
</body></html>"

何处获得 Tcl?[编辑]

在大多数 Linux 系统上,Tcl/Tk 是预带的。你可以在命令提示符(xterm 一类)上输入 tclsh 来检查,如果出现了 "%" 提示,就说明你已经有 Tcl 了。为了确认,在 % 提示后输出输入 info pa 查看补丁级别(如 8.4.9),info na 查看可执行文件在文件系统中何处被放置。

Tcl 是开源项目,如果你想要自行编译,可访问 http://tcl.sourceforge.net/ 下载。

对于所有主流平台,你可以从 ActiveState 直接下载二进制的 ActiveTcl 发行版。除了 Tcl 和 Tk 本身,还包括了许多流行扩展,被称作权威“内含电池”的发行版。

你也可以下载 Tclkit:单文件的已包装 Tcl/Tk 安装,无需解包。运行时文件会挂载为一个虚拟文件系统,允许对其所有部分的访问。

2006 年一月,eTcl 发布——前途光明的单文件虚拟文件系统发行版。可从 http://www.evolane.com/software/etcl/index.html 下载的免费二进制文件,面向 Linux, Windows, and Windows Mobile 2003。特别是在口袋 PC 上,eTcl 提供一些目前其他移植没有的功能:套接字,后台窗口,并且可以被一个启动脚本或纯 Tcl 库扩展。

第一步[编辑]

为了检查你的安装是否工作,请将以下代码保存为 hello.tcl ,然后运行。(在 *NIX 控制台上键入 tclsh hello.tcl,在 Windows 上双击):

package require Tk
pack [label .l -text "你好世界!"]

应该会出现一个小灰窗口,上面还有一句“你好世界!”

为了让脚本可以直接被执行(*NIX 和 Windows 的 Cygwin),在第一行写(# 顶格):

#!/usr/bin/env tclsh

或(老旧,奇异,而且不推荐):

#! /bin/sh
# the next line restarts using tclsh \
exec tclsh "$0" ${1+"$@"}

这样壳就能确认使用哪个可执行文件执行脚本了。

不管是初学者还是 Tcl 专家,更简单并且高度推荐的方式是,直接交互式启动 tclsh 或 wish。你将在控制台看到 % 提示,能在其中键入命令,并能看到回应。此处就算是错误信息也很有帮助,也不会让程序中止——不断尝试!不惧尝试!示例:

$ tclsh
% info patchlevel
8.4.12
% expr 6*7
42
% expr 42/0
divide by zero

你甚至可以直接交互式地写程序,学一下单行人(喜欢写一行代码的人):

% proc ! x {expr {$x<=2? $x: $x*[! [incr x -1]]}}
% ! 5
120

更多示例见“快速游览”。


句法[编辑]

句法是语法如何组织的规则。汉语中简单的句法可以这样表示(暂时忽略标点):

  • 文章是很多句子
  • 句子是很多字

很简单,并且很好地描述了 Tcl 的句法——把“文章”替换成“脚本”,把“句子”替换成“命令”。另外 Tcl 的“字”可以包含脚本或命令,这也是一个区别。因此

if {$x < 0} {set x 0}

是由三个字组成的句子:if、括号中的条件、括号中的命令(亦由三字组成)。

   

也是正确的 Tcl 命令:调用(必须在前文定义),并传递三个参数“此”“为”“例”。命令如何解析参数是他自己的事。如:

puts acos(-1)

将给标准输出频道写出字符串“acos(-1)”,并返回空字符串 {},但

expr acos(-1)

会计算 -1 的反余弦,返回 3.14159265359(π 的粗略值),或

string length acos(-1)

将调用 string(字符串)命令,分发参数到 length(长度)子命令,计算得出第二个参数的字符串长度,最终返回 8。

简短总结[编辑]

Tcl 脚本 是一系列命令组成的的字符串,以新行或分号分隔。

命令 是个字的列表,以空白分隔。第一个字是命令名,其余作为参数传递。Tcl 中,“万物皆命令”——甚至其他语言中的所谓定义、声明、控制结构亦然。命令可以以其想要的方式解释参数,特别是他可以实现另一门语言,如 expr

A word is a string that is a simple word, or one that begins with { and ends with the matching } (braces), or one that begins with " and ends with the matching ". Braced words are not evaluated by the parser. In quoted words, substitutions can occur before the command is called:

  • $[A-Za-z0-9_]+ substitutes the value of the given variable. Or, if the variable name contains characters outside that regular expression, another layer of bracing helps the parser to get it right:
puts "Guten Morgen, ${Schüler}!"

If the code would say $Schüler, this would be parsed as the value of variable $Sch, immediately followed by the constant string üler.

  • (Part of) a word can be an embedded script: a string in [] brackets whose contents are evaluated as a script (see above) before the current command is called.

In short: Scripts and commands contain words. Words can again contain scripts and commands. (This can lead to words more than a page long...)

Arithmetic and logic expressions are not part of the Tcl language itself, but the language of the expr command (also used in some arguments of the if, for, while commands) is basically equivalent to C's expressions, with infix operators and functions. See separate chapter on expr below.


man 页面:十一律[编辑]

这是 Tcl (8.4) 的完整 man 页面——十一律。(8.5 始,其十二者有焉,指述 {*} 之能。)

下约束定 Tcl 言语句法语义。

律之一、命令 Tcl 脚本者,文字串有命令者之合也。除下另述之境况,则分号、新行,命令分隔也。若非引用者,闭括号,命令替换之时之命令终止也。

律之二、求值 命令求值于二步。其始,Tcl 解释器断命令为诸字,行下述替换。是替换行如命令也。首字定其名,余字定其参。命令之过程,可以其自己法解释其参,可为整数、变量名、列表、Tcl 脚本。异令异解,纷然不一。

律之三、字 异字为空格所割。(然而,新行者,命令之隔也。)

律之四、双引号 若字首为双引号("),则字止于次者。分号、闭括号、空白(亦谓新行)见于其中,则待之如常符。下所述命令替换、变量替换、反斜线替换将行于其间。后,双引号不留。

律之五、花括号 若字首为开花括号({),则字止于匹配之闭者。Braces nest within the word: for each additional open brace there must be an additional close brace (however, if an open brace or close brace within the word is quoted with a backslash then it is not counted in locating the matching close brace). No substitutions are performed on the characters between the braces except for backslash-newline substitutions described below, nor do semi-colons, newlines, close brackets, or white space receive any special interpretation. The word will consist of exactly the characters between the outer braces, not including the braces themselves.

律之六、命令替换 If a word contains an open bracket ([) then Tcl performs command substitution. To do this it invokes the Tcl interpreter recursively to process the characters following the open bracket as a Tcl script. The script may contain any number of commands and must be terminated by a close bracket (``]). The result of the script (i.e. the result of its last command) is substituted into the word in place of the brackets and all of the characters between them. There may be any number of command substitutions in a single word. Command substitution is not performed on words enclosed in braces.

律之七、变量替换 If a word contains a dollar-sign ($) then Tcl performs variable substitution: the dollar-sign and the following characters are replaced in the word by the value of a variable. Variable substitution may take any of the following forms:

$name

Name is the name of a scalar variable; the name is a sequence of one or more characters that are a letter, digit, underscore, or namespace separators (two or more colons).

$name(index)

Name gives the name of an array variable and index gives the name of an element within that array. Name must contain only letters, digits, underscores, and namespace separators, and may be an empty string. Command substitutions, variable substitutions, and backslash substitutions are performed on the characters of index.

${name}

Name is the name of a scalar variable. It may contain any characters whatsoever except for close braces. There may be any number of variable substitutions in a single word. Variable substitution is not performed on words enclosed in braces.

律之八、反斜线替换 If a backslash (\) appears within a word then backslash substitution occurs. In all cases but those described below the backslash is dropped and the following character is treated as an ordinary character and included in the word. This allows characters such as double quotes, close brackets, and dollar signs to be included in words without triggering special processing. The following table lists the backslash sequences that are handled specially, along with the value that replaces each sequence.

\a
Audible alert (bell) (0x7).
\b
Backspace (0x8).
\f
Form feed (0xc).
\n
Newline (0xa).
\r
Carriage-return (0xd).
\t
Tab (0x9).
\v
Vertical tab (0xb).
\<newline>whiteSpace
A single space character replaces the backslash, newline, and all spaces and tabs after the newline. This backslash sequence is unique in that it is replaced in a separate pre-pass before the command is actually parsed. This means that it will be replaced even when it occurs between braces, and the resulting space will be treated as a word separator if it isn't in braces or quotes.
\\
Literal backslash (\), no special effect.
\ooo
The digits ooo (one, two, or three of them) give an eight-bit octal value for the Unicode character that will be inserted. The upper bits of the Unicode character will be 0.
\xhh
The hexadecimal digits hh give an eight-bit hexadecimal value for the Unicode character that will be inserted. Any number of hexadecimal digits may be present; however, all but the last two are ignored (the result is always a one-byte quantity). The upper bits of the Unicode character will be 0.
\uhhhh
The hexadecimal digits hhhh (one, two, three, or four of them) give a sixteen-bit hexadecimal value for the Unicode character that will be inserted.

Backslash substitution is not performed on words enclosed in braces, except for backslash-newline as described above.

律之九、注释 若井号(#)见于命令首字,则以后文为注释,弗问遽捐,止于新行。注释符惟有其用于命令之首。

律之十、替换次序 Each character is processed exactly once by the Tcl interpreter as part of creating the words of a command. For example, if variable substitution occurs then no further substitutions are performed on the value of the variable; the value is inserted into the word verbatim. If command substitution occurs then the nested command is processed entirely by the recursive call to the Tcl interpreter; no substitutions are performed before making the recursive call and no additional substitutions are performed on the result of the nested script. Substitutions take place from left to right, and each substitution is evaluated completely before attempting to evaluate the next. Thus, a sequence like

set y [set x 0][incr x][incr x]

will always set the variable y to the value, 012.

律之十一、替换与字界 替换不预命令字界。举例而言,变量替换之中,全为单字,不问空格。

注释[编辑]

The first rule for comments is simple: comments start with # where the first word of a command is expected, and continue to the end of line (which can be extended, by a trailing backslash, to the following line):

# This is a comment \
going over three lines \
with backslash continuation

One of the problems new users of Tcl meet sooner or later is that comments behave in an unexpected way. For example, if you comment out part of code like this:

# if {$condition} {
    puts "condition met!"
# }

This happens to work, but any unbalanced braces in comments may lead to unexpected syntax errors. The reason is that Tcl's grouping (determining word boundaries) happens before the # characters are considered.

To add a comment behind a command on the same line, just add a semicolon:

puts "this is the command" ;# that is the comment

Comments are only taken as such where a command is expected. In data (like the comparison values in switch), a # is just a literal character:

if $condition {# good place
   switch -- $x {
       #bad_place {because switch tests against it}
       some_value {do something; # good place again}
   }
}

To comment out multiple lines of code, it is easiest to use "if 0":

if 0 {
    puts "This code will not be executed"
    This block is never parsed, so can contain almost any code
    - except unbalanced braces :)
}