跳至內容

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 :)
}