July 12,2005

tcl/tk 介紹

◇ tcl/tk 介紹

大家好,
一提到直譯語言,令人印象最深刻的,想必是BASIC了。沒錯,
    BASIC 陪伴很多人進入PC的奇妙世界,甚至到今天,Visual
Basic 在Windows 下依然魅力十足,把Interpretor 的優點發揮
的淋淋盡致─程式發展快、方便易學。在UNIX的世界裡,C幾乎
是程式語言的代名詞,BASIC 早已被人們所淡忘。但是,UNIX下
是否有像BASIC 一般簡單易學,功能強大的解譯語言呢?答案是
肯定的,而且功能皆有過之而無不及,舉凡lisp、prolog等人工
智慧語言,或者像Perl、tcl/tk 皆是,而且都有提供方便的圖
型界面,使得在X window下設計程式方便不少。以下文章就是在
下寒假研究tcl/tk的心得,提供大家參考;並且強力推銷tcl。

******
一、什麼是tcl?什麼是tk?

tcl 是Tool Command Language 的縮寫,而tk是一個X window的
Tool Kits,是tcl在X Window System 的應用。tcl 是一種解譯
語言,也是一套C的函式庫。為什麼這樣說呢?因為tcl 的解譯
器被設計成一個C的函式庫,提供基本的命令與控制結構,並且
使用tcl 的任何程式皆可以根據tcl 的規格撰寫C程式與之鏈結
增加新的命令,以提高關鍵程式的效率、或增加新的特色。如tk
就是這樣子的示範。廢話少說,以下先來個示範:

┌───────────────────────────────┐
│ tk的解譯器叫wish,是WIndowing SHell 的簡稱。只要在提示號 │
│ 下(xterm 下)輸入wish就可以了。接下來你可以看到一個空白 │
│ 的視窗出現,xterm 下的提示號也變成了wish的提示號。此時, │
│ 在提示號輸入以下兩行指令,就可以見到最簡單,最讓人驚奇的 │
│ tk程式了: │
│ button .b1 -text "Hello,World!" -command exit │
│ pack .b1 │
│ 下完第二個指令後,原本空白的視窗就變成一個印有Hello,World │
│ 的立體按鈕,而且滑鼠移近時會變成高亮度。但是別急,且慢按鍵 │
│ 在輸入兩個命令看看: │
│ │
│ button .b2 -text "Hello,TCL/TK" -command "destroy .b2" │
│ pack .b2 │
│ │
│ 此時,螢幕上會出現第二個按鈕。以下兩個命令可以更改顏色: │
│ .b1 configure -background red │
│ .b2 configure -foreground green │
│ 按下第二個按鈕會使第二個按鈕消失,而第一個按鈕會結束程式。 │
│ │
│ 每次這樣寫很麻煩,但是你也可以照UNIX的規矩把程式寫成一個檔 │
│ 案hello,再執行之。( 當然要先chmod +x hello) │
│ hello的內容:(第一行的內容可由`which wish`指令得知) │
│ #!/usr/local/bin/wish -f │
│ button .b -text "Hello,World!" -command exit │
│ pack .b │
└───────────────────────────────┘

*註1:請查詢系統管理者詳細的路徑。
註2:tcl/tk原始碼可以在NCTUCCCA:/X/contrib/下找到。
註3:cc.ntu.edu.tw的使用者其wish的路徑為/remote/bin/wish
註4:交談式的wish的命令行編輯非常原始,使用者可以用fep 或ile
兩個front end 程式達到類似tcsh/bash/ksh 的行編輯。
註5:Linux/386BSD的使用者(應該說XFree86的使用者)應該安裝時就有
tcl/tk了。

二、關於tcl

因為tk是基於tcl 語言而來的,因此我們有必要先了解tcl 。
與tk相同,tcl 也附了一個交談式解譯器tclsh ,可供線上學習tcl之用。
tcl 的語法非常簡單,基本上就是與Shell的語法類似。學過一點點shell
programing的人非常容易進入狀況:

A.一個tcl 程式是由好幾個tcl 敘述組成的。
B.一個tcl 敘述與平常在shell 下面的命令一模一樣,如前面
hello,world 的例子一樣,第一個字是命令,剩下的全部是
該命令的參數。
C.tcl 除了命令外就只有變數。變數與shell 變數一樣,只有
一種型別:字串。錢號可以取出變數的值。
D.tcl 對每一個敘述最多只做一次變數代換。而被大括號括住
的部份不做任何處理。
E.tcl 會優先執行被方括號括住的敘述,並將其結果當成原來
命令的一部份。這與shell 的重音符號相同(mkdir `echo Hello`)

以下就是一些範例:

unix% tclsh
tcl% set x 100 輸出 100
tcl% set y 200 200
tcl% expr $x + $y 300
tcl% set z [expr x+y] 300, z = 300
tcl% set a [set b 100] a = b = 100
tcl% expr (3>4)||(6<=7) 1
tcl% expr 14.1*sin($x)
tcl% set organization "Taiwan University" (兩句同意,只是 )
tcl% set organization {Taiwan University} (引號會做變數代換)
[以下省略tcl的提示號]
tcl的procedure:
proc power {base p} {
set result 1
while {$p > 0} {
set result [expr $result*$base]
set p [expr $p-1]
}
return $result
}
power 2 6 可得 64
power 1.15 5 2.01136

仔細觀察procedure的demo,其實tcl 並沒有procedure結構的語法。
proc只是一個命令,接受4個引數:
proc 新命令名字 參數 一段tcl程式碼
其中,參數與tcl程式碼用大括號括起來的原因是我們不希望tcl 現在
就執行這些程式碼,而是當procedure被呼叫時才執行。while結構也是
如此:
while 判斷 程式碼
因為我們希望每次while執行時$result,$p的值都會變。如果不用大括號
括起來,則所有的值在tcl解譯的時候就固定了,while迴圈永遠也不會
結束。

與大括號相反,eval命令可以把一個字串當成tcl 命令執行:
eval {set x 123} 等於 set x 123
eval "set x 123" 同上
eval可以造成tcl 對同一敘述parse兩次,解決一些難纏的問題:
exec rm [glob *.o]
會告訴你:
"a.o b.o c.o" not found
正確的解法是叫tcl 再parse一遍命令行:
eval "exec rm [glob *.o]"

tcl 的array:不須宣告,直接用即可,但是只有一維陣列而已。
set days_of_a_month(Jan) 31
set days_of_a_month(Fab) 28
多維陣列可用單維陣列模擬:
set matrix(1,1) 100
set matrix(3,9) 50
set matrix($x,$y) 66
set z $matrix(6,6) 77

陣列的index其實為 "1,1" 、 "3,9" 與 "$x,$y"

相關的命令:
set var value
append var value [value2 vaule3 ...]
incr var [increament] /* default = 1 */
unset var [var2 var3 ...]

tcl 還有一種之料結構叫list

set x {Sun Mon Tue Wed Thu Fri Sat}
lindex $x 1 輸出 Mon
lindex {a b {c d e} f g} 2 輸出 "c d e"
concat {a b} {c d} e 輸出 "a b c d e"
list {a b} {c d} e "{a b} {c d} e"
llength { {a b} e f} 3
llength {} 0
llength a 1
linsert $x 2 a b c Sun Mon a b c Tue ...
linsert $x 0 a a Sun Mon ...
lreplace $x 0 a a Mon Tue ...
lrange $x 0 1 Sun Mon
lappend $x a b c
lsearch $x Sat
lsearch -glob $x S* /* Wild Cards */
lsearch -regexp /* regular expression */
lsort [-decreasing|-integer] $x
Strings & Lists
set x a/b/c
set y /usr/local/bin/wish
split $x / 輸出 a b c
split y {} usr local bin wish
反函數為join
Lists & Commands:
其實tcl 語言本身就是一個list,瞧,最後一個是command或是
list:
button .b -text "Reset" -command {set x 0}

list可以解決一些難以構成的命令:
假設有一個情況,我們寫了下列命令:
button .b -text "Reset" -command "set x $InitValue"
此命令的情況是我們希望Reset button按下後把 x設回
InitValue,可是天不從人願,如果$InitValue是"tcl tk"
則Command變成set x tcl tk,引述個數不對了。
如果改成:
button .b -text "Reset" -command {set x $InitValue}
則x 值取決於按鈕時的InitValue,而非真正的InitValue
所以可用下列方法解決:
button .b -text "Reset" -command [list set x $initValue]

控制結構:
if 判斷 [then] 敘述 elseif 敘述 elseif 敘述 [else] 敘述
then 與else 皆可省略。
while : (make b the reverse of a)
set b ""
set i [expr [llength $a] -1]
while {$i >= 0} {
lappend b [lindex $a $i]
incr i -1
}

for:
set b ""
for {set i [expr [llength $a] -1]} {$i >=0} {incr i -1} {
lappend b [lindex $a $i]
}

foreach:
set b ""
foreach i $a {
set b [linset $b 0 $i]
}

注意,受限於tcl 語法,大括號不能獨立一行:
while {}
{
}
break與continue也都有效。
switch命令:
switch $x {
Mon {incr days(Mon)}
Tue {incr days(Tue)}
default {...}
}
亦可寫成:
switch $x Mon {...} Tue {...} default {...}

switch $x \
Mon {...} \
Tue {...} \
default {...}
如果動作相同可用 - 代表。
switch $x {
1 -
3 -
5 -
7 -
9 {incr odd}
default {incr even}
}
副程式:
同csh,tcl也有source 命令:
source tclInit.tcl
procedure:
proc name ArgList Body
定義一個叫做name 的procedure,
如果ArgList的最後一個為args,則此procedure
為不定引數函數,而args為一list。
global name1 name2 ...
使用global中的name1 name2變數,而非自定local變數
return value
uplevel [level] script1 script2...
類似inline函式,把stript1 script2 ...串起來
然後在上一層中執行,而非在procedure自己的stack
內執行(可以更改上一層的變數)。
upvar [level] name localname [name1 localname1] ...
引用上一層的變數name,但是在本procedure內用
localname存取之。(call by reference)
uplevel例:
proc do {varName first last body} {
upvar $varName v
for {set v $first} {$v <= $last} {incr v}
uplevel $body
}
}
set a {}
do i 1 5 {
lappend a [expr $i*$i]
}
set a 顯示 1 4 9 16 25
如果不用uplevel,則$body就不可能存取到 a變數了。
Errors& exceptions:

catch {
tcl 程式碼
} messages
如果程式碼有錯,catch return 1,否則為0,messages為實際的錯誤訊息。

以下沒力氣打中文,寫不下去了,抄兩個玩具給大家欣賞 :-b

#!/usr/local/bin/wish -f
proc power {base p} {
set result 1
while {$p > 0} {
set result [expr $result*$base]
incr p -1
}
return $result
}
entry .base -width 6 -relief suken -textvariable base
label .label1 -text "to the power"
entry .power -width 6 -relief sunken -textvariable power
label .label2 -text "is"
label .result -textvariable result
pack .base .label1 .power .label2 .result -side left -padx 1m -pady 2m
bind .base {set result [power $base $power]}
bind .power {set result [power $base $power]}
# End of File

註: -relief sunken的意思是凹陷的輪廓。

本程式產生一個視窗:
┌──────────────────────────┐
│〔 A 〕to the power 〔 B 〕 is 〔 〕│
└──────────────────────────┘
只要輸入A,B就可以得到A的B次方。

#!/usr/local/bin/wish -f
set id 0
entry .entry -width 30 -relief sunken -textvariable cmd
pack .entry -padx 1m -pady 1m <- 顯示輸入行
bind .entry { <- 當.entry 收到這個event時
set id [incr id]
if {$id > 5} {
destroy .b[expr $id -5] <- tcl的變數名也可以用湊的
└> 刪除第5次前的命令
}
button .b$id -command "exec <@stdin >@stdout $cmd" -text $cmd
pack .b$id -fill x <- 顯示按鈕,且水平(x)方向填滿。
.b$id invoke <- 模擬按鈕被按下
.entry delete 0 end <- 清除輸入行
}

#end of file

本程式產生一個輸入行,可以下命令,並且把過去的5個命令記錄下來,
用按鈕就可以執行。

***

看到這裡,您是否同意tcl/tk是UNIX世界的BASIC呢?

本篇文章是我閱讀一本書:tcl and tk toolkit的部份心得。這本書已於1994年由
Addison-Wesley Publishing Company,Inc.出版(ISBN 0-201-63337-X)。此書
網路上有postscript檔案,但是我不認為大家有閒情逸致印個500頁左右的
書吧---用10ppm的雷射印表機也要50幾分鐘、實際上用Postscript輸出更慢,
還會卡紙哩:)最好是找找看有沒有進口。

tcl/tk在USENET上有自己的討論群:comp.lang.tcl各位可以參考其FAQ。FAQ可在
NCTUCCCA:/USENET/FAQ/comp/lang/tcl拿到。

UNIX下的Interpretor種類繁多,功能複雜,但願這篇文章能收到拋磚引玉之效
使有人願意寫些中文文件來介紹與X Window 整合的其他Interpretor,如tkperl
、Prolog等。甚至是一些Windows的程式設計工具,如SUIT,xvwindow等library
使得 X Windows Programing對入門者不再是夢靨。

以上所提及的軟體更是網路上可以免費取得的合法軟體,特別是Linux下全部都有。

ps: 上學期末在天龍書局看到Larry Wall的Programing Perl,對perl有興趣者可
以去買,保證不會後悔!

pps: tk目前的版本無法處理16bits的字集,即Botton..無法看到中文,可能要
等到tk 4.0。

======================================================




Posted by yam_kelvins at 樂多Roodo! │23:11 │回應(0)引用(0)參考文章
樂多分類:工作/職場 共同主題:教學分享 工具:編輯本文
Ads by Roodo! 

引用URL

http://cgi.blog.roodo.com/trackback/264051