#

變數是大部份程式語言都會提供的功能,你可以把變數想像成是一塊儲存資料的空間,它可以讓你儲存程式執行時的資料。在這篇文章裡我們將介紹Tcl的變數相關命令及作用範圍的觀念。雖然說明的方法可能不是很正式,但觀念上是正確的。

2.1 設定變數

一般來說變數的操作不外乎就是「設定」、「取用」及「清除」這三種。其中設定是指把資料存入變數,取用是指由變數中取出資料,最後的清除是指釋放變數或是銷毀變數。接下來就讓我們要先來看設定變數的方法。在Tcl的世界裡你可以使用set命令來設定變數,例如,下面的程式設定了一個名稱叫birthday的變數,而且把字串「11/15」儲存進去。



請先注意到set後面的第一個欄位,它的功能主要是用來指定變數的名稱。在這個例子中birthday就是一個變數的名稱,你可以想像變數名稱就像是一塊儲存空間的名牌,依據這個名牌,往後你才能夠正確的找出儲存在裡面的資料,或是釋放這塊空間。另外在Tcl裡變數的名稱是不能夠亂取的,而是需要遵守一些命名的規則,否則會導致語法上的錯誤。

關於變數命名的規則是這樣子的。首先我們需要了解Tcl會區分大小寫,所以說dai和Dai是指不一樣的兩個變數。然後,變數的名稱裡請不要出現奇怪的符號,要儘量使用英文字母、數字或底線來構成變數的名稱,而且要儘量讓字面上看起來是有意義的,最好是看到名稱就知道變數裡面儲存了什麼樣的資料。最後一點儘量不要用全部大寫的字母來組合變數名稱,因為全部大寫字母組成的變數名稱,通常具有特殊的意義,它可能被用來儲存系統的重要資料,或是不希望被隨便修改的資料。

§ 關於變數的命名

事實上Tcl對於變數的命名幾乎沒有什麼限制,你可以非常自由的使用任何的文字串、符號甚至是關鍵字來命名,而且Tcl真的允許你這麼做。但對於一個有經驗的程式設計師,他通常只會使用底線、數字及英文字母來組成變數的名稱,而且變數的名稱儘可能有意義,事實上大家也都建議這麼做。

正常的情況下,你可以使用set去設定非常多的變數,Tcl並沒有限制你變數的上限數量。例如:下面的程式設定了三個變數,分別是name、birthday及phoneNumber。



在上面的例子裡,每個打算儲存進變數裡的資料都用雙引號夾了起來。實際上如果它們的內容沒有包含「空白」字元,雙引號是可以省略的,例如,下面的寫法也是正確的。



但是,如果要存入的資料有包含「空白」字元,那麼就一定要加上雙引號。例如,下方是錯誤的寫法,因為set後面最多只能跟隨兩個參數,但下面的程式會被判斷為3個參數,所以是不正確的用法。



正確的寫法應該像下面一樣:



§ 關於變數的使用

雖然說正常的情況下你可以拚命的設定變數,但說真的還是省省用的好,除非你嫌記憶體太多或太便宜。另外一點,對於已經用set設定過的變數,同樣可以用set來重新設定變數的值。

2.2 取出變數的內容

一旦設定好變數後,接下來就可以任意次數的使用它。取用變數裡的資料是很容易的,只需要在變數名字的前方多加上一個「$」符號就可以了,例如,使用puts命令來輸出變數的內容:



執行程式後應該會有如下的輸出:

11/15
dai
0938938938

Tcl有一個很方便的方法可以讓你在某一個文字串中插入已經設定好的變數。例如,下面的程式把name、birthday及phoneNumber三個變數夾在一串中英文組成的字串中輸出:



輸出的結果是:

Hi! 我是 dai 出生於 11/15 號,電話是 0938938938 有空call我哦!!

§ 為什麼要使用變數

剛開始使用變數的初學者時常會有一個疑問,「為什麼要使用變數?直接使用原來的文字串不好嗎?」。嗯~這是個好問題。其實有些資料不是在寫程式時就可以知道的。例如,程式執行到一半的時候請使用者輸入的資料,或是某些程式碼產生的運算結果。這些資料都是要等到程式執行時才可以決定的,所以我們沒辦法在寫程式的時候,就把這些資料直接寫在程式裡,唯一的方法就只有透過變數記下它們然後操作它們了。另外你也可以用另一個角度思考。如果你寫了一個10000行的程式,裡面總共出現了100次電話號碼,也就是說有100個0938938938散落在這10000行之中,假設有一天你換了電話號碼,那你就需要在這一堆程式碼中找出舊的電話號碼,然後一一修改,雖然你可以用文字編輯器的取代來處理,但難保不會有什麼意外。若改用變數的話,那就變成只需要修改儲存電話號碼的變數就好了,這樣有效率多了。所以就算是寫程式時就知道的資料,有時透過變數來操作資料也會有一定的好處。

2.3 清除變數

清除變數的功能可以讓你釋放變數佔用的記憶體,然後一併清掉變數裡的資料,如果你真的確定不需要用到某些變數了,可以使用unset命令來清除變數:



上面的例子把之前設定的name、birthday、phoneNumber等3個變數給清除掉。清掉以後就不可以再取用它們了,除非你重新設定它們的值,才可以再度使用。

§ 關於清除變數

一般來說你不需要在程式結束前手動把變數清除掉,正常的情況下Tcl會在你的程式結束前自動的處理善後動作。

2.4 變數的作用範圍

為了讓變數的使用更有彈性,Tcl提供了一套機制讓程式設計者可以決定每一個變數的作用範圍,這麼做的目的主要是為了避免多人一起開發程式時,不小心用到相同的變數名字,而導致程式發生錯誤。其主要觀念是:「在不同的變數作用範圍裡,就算是一樣的變數名字,它們還是屬於不同的個體,彼此不會互相影響」。變數作用範圍的建立及使用方法以後會慢慢的出現,目前我們只需要記得兩個重點:

  1. 凡是以兩個冒號開頭的變數名字(變數名字中間沒有出現其它的雙冒號),即表示是一個全域性的變數在任何地方都可以使用它。
  2. 沒有使用兩個冒號開頭的變數名字,即表示是區域性質的變數,它只可以用在特定的程式區塊,一但離開了所屬的區域就會被自動清除。

下面的例子說明建立全域變數的方法。var1以雙冒號開頭所以它是一個全域的變數,而var2則是一個區域性質的變數。



全域變數的取用和清除方法和普通變數是一樣的,所以你同樣可以冠上「$」字符號取出它的值,或用unset清除它。提醒一點。第3、4行「;#」後面的都是註解,不是程式。

2.5 全域變數的程式範例

接下來我們要來寫一個簡單的問安程式,這個程式可以讓你輸入姓名,然後按下按鈕後,它會跟你問好。

2.5.1 問安程式




程式執行後會出現一個文字輸入方塊及一個按鈕,你可以在文字方塊上輸入你的姓名,然後按下"Say Hello"按鈕,然後這個程式就會向你問安。

圖 2-1 問安程式


2.5.2 程式說明

第1行的entry是Tcl/Tk製作單行輸入方塊的命令。其中.txtName是指定給文字方塊獨一無二的ID,緊接在後面的-textvariable可以用來指定一個變數,這個變數將用來儲存輸入在文字方塊上的內容,之後的::name即是指定的變數,它是一個叫name的全域變數。簡單的說使用者在文字方塊上輸入的內容,都會被儲存在::name變數裡。

第2行的button是建立按鈕的命令。其中.btnHello是他獨一無二的ID,緊接著的-text可以用來指定要顯示在按鈕上的文字,這邊我把它指定為「Say Hello!!」。然後-command參數可以用來指定按下按鈕要執行的動作,大括號裡的內容即是要執行的動作。在這裡我用puts命令輸出了::name變數的內容再加上「您好」兩個字。由於之前設定了::name會反應文字方塊上輸入的內容,所以真實輸出的結果就是文字方塊上的文字加上「您好」這兩個字。

第3、4行,只是很單純的把文字方塊及按鈕排放在螢幕上顯示。

本章回顧

變數是一塊資料儲存的空間,建立變數的方法像這樣:

set 變數名稱 變數的值

Tcl對於變數名稱的規定不是很嚴格,但是請儘量使用英文字母、數字及底線來組合變數名稱。另外,變數名稱有區分英文大小寫,所以dai和Dai是兩個不同的變數。

當變數建立後,使用set可以重新設定變數的值,使用unset可以釋放變數佔用的空間,例如,使用下面命令可以釋放變數:

unset 變數名稱

在變數的前面加上一個「$」號,就可以取出變數的值。例如,下面的puts會印出儲存在dai變數裡的內容:



凡是以兩個冒號開頭的變數名字(變數名字中間沒有出現其它的雙冒號),即表示是一個全域性的變數,這種變數可以在任何地方使用它,方法就和一般變數一樣。下面是一個簡單的例子:

32 個意見

Terry | 2010年10月25日 下午4:27

Hi Dai

是否可以在linux console下使用對話框?
譬如執行tcl script檔時會先要求輸入使用者名稱, 然後在screen print "Hi Terry"

dai | 2010年10月31日 下午2:01

對話框是不行的,但是可以用gets命令,由console裡請使用者輸入資料
ex.

puts -nonewline "請輸入姓名:"
gets stdin name
puts $name

isPeter | 2010年11月8日 下午4:02

Linux Console 上的對話框可以考慮使用 Dialog

shawn.chen | 2010年12月20日 上午10:21

不好意思
我又來問問題了

我想做類似oo可以呼叫的傳遞參數
裡面傳進去的參數並不是拿來當值用
而是繼續當參數使用
但我想拿到 當參數使用後的值
這有解嗎?

例:
proc prarameter {abc def} {
set $abc{$def} 1
puts "$abc{$def}= $abc{$def}的值"
﹂==>我要 1這個值
}
但我要怎麼取到這個1的值呢?

shawn.chen | 2010年12月20日 上午11:46

經高人指點 解出來了
用eval指令去解決

eval "set $abc\($def\) 1"
eval "puts \" $abc\($def\) = \$$abc\($def\) \""

幾米 | 2010年12月24日 上午9:41

Hi Dai,
請問如何使用ttk:notebook 的方法,讓serial console的訊息秀出來,
就像是ezdit 左下框框的顯示那樣?
我是有好幾個tcl檔案,勾選到的button要依序顯示出來,
但一直都沒有成功,請dai幫忙指引,謝謝

dai | 2010年12月25日 上午10:24

Hi shawn.chen

看起來你要的功能是,傳陣列及索引到程序裡,

請參考下面的程式


proc test {arrName key} {
upvar $arrName arr2
set arr2($key) 1
puts "arr2($key)=$arr2($key)"
}

array set arr [list dai 1 ccu 2 jky 3]

test arr dai

dai | 2010年12月25日 上午10:29

Hi 幾米

ttk::notebook只能用來管裡切換頁面的功能,他本身並不具備有console的功能,也就是說你可能需要用text先自己寫console(包含輸入輸出的功能),然後再把你用text寫出來的console嵌到notebook的頁籤裡。

另外 "我是有好幾個tcl檔案,勾選到的button要依序顯示出來" 這個...我抓不太到你想要表達的功能,要請你多描述一點。

幾米 | 2010年12月28日 下午6:33

thanks for dai,
1.那怎麼把log導到一個text(包含輸入輸出的功能),然後再把text寫出來的console嵌到notebook的頁籤裡?我真的不懂@@
2.我使用一些checkbutton,跑不同的script,如何跑完一個接著一個後,consol訊息仍然會顯在notebook的頁籤裡,就像ezdit一樣。

吉米 | 2010年12月30日 下午12:06

Hi 幾米

notebook的用法這邊的教學文件有
32.TK - 分頁框 (notebook)
http://blog.got7.org/2010/01/32tk-notebook.html

text的用法請參考
http://www.tcl.tk/man/tcl8.5/TkCmd/text.htm
#以下例子 建立名為 .t 的視窗元建text , 印出Hello
pack [text .t]
.t insert end "Hello"

要嵌到notebook裡只要把它加到notebook裡就好了
pack [ttk::notebook .nb]
text .nb.t
.nb add .nb.t -text "page 1"

如果你要顯示的是script執行的log,可以把印字的部分寫在scipt裡,這樣執行時就能看到了

如果是要console的功能
請參考ezdit的source code
採用tkcon這個套件,這部份我還沒看懂 orz

dai | 2011年1月2日 上午10:29

hi 幾米

1.

Tcl/Tk裡沒有功能可以像linux命令的「>」一樣把puts的結果「導入」text widget(我也沒看過其它的程式有這樣的功能),而是必需要使用text widget的insert命令才可以把文字「插進文字方塊(text)」,像這樣:

ttk::notebook .nb
pack .nb -expand 1 -fill both

text .nb.t
.nb.t insert end "你要插進文字方塊的文字"

.nb add .nb.t -text "Console"

2.

幾米....我真的不太懂要怎麼用checkbutton跑script?? 是指勾選到某一個checkbutton後就自動執行對應的某個script嗎?

瘋狂伊凡 | 2011年10月31日 上午12:56

Dai大,
因為剛碰Tcl,所以很多都不懂!
能在Google大神的協助下,找到Dai大的教學,真是太開心了!
所以就照著教學的程式一步一步學,可是到了2.5.1的問安程式時,出現問題了!
視窗有秀出來,文字也像你敘述的都出現了,可是當我輸入變數的值(name)後,在"執行"的視窗內並沒有出現任何反應, why?就好像是button內的 -command 沒有執行puts的動作
我是在window的OS上run.麻煩你解惑一下

dai | 2011年10月31日 上午10:03

HI 伊凡,

你感覺沒有任何反應是因為,puts 的功能是把 文字串 輸出到 「console」 也就是「命令提示字元」

所以你會感覺不到任何反應~

你可以在命令提示字元,用類似下面的命令執行程式 就會出現看到puts的輸出

C:\> tclsh.exe 2.5.1.tcl (假設你的程式放在c:\,然後確定你的tcl直譯器有在系統的PATH搜尋路徑裡)

祝你成功

瘋狂伊凡 | 2011年10月31日 下午3:41

這console是不是在IDE的介面下才會出現呢?puts的功能不是就是在命令提示後輸出字串嗎?(教學檔範例一), 所以不是應該在windows的命令提示視窗中就應該出現程式結果嗎?
我沒有安裝ezdit!但是後來我有試出來,但是我有點無法理解....XD,我把我試出來的過程說明如下:
我的console要跳出來,要先進入windows的命令提示(star-->run-->Key in :cmd)
然後先Key in : wish --> console的window才會出現
再 Key in : wish XXX.tcl-->視窗出現 -->把name的變數值輸入後, 再按"Say Hello"的button
這時候console還是不會出現文字,要把視窗關掉,"XXX你好"才會出現!
不好意思,因為我不是資工出身的,所以很多不了解,如果可以的話可以麻煩你幫我釐清一下怎麼回事嗎?
謝謝你的幫忙^^

dai | 2011年10月31日 下午4:46

Hi 伊凡,

是這樣的 ~ 在Windows下wish有自己的console,所以如果你用wish來啟動xxx.tcl

所有在xxx.tcl裡用puts輸出的內容都會跑到wish自己的console而不是windows的「命令提示指元」

在這樣的情況下 你可以在程式的開頭加一行

console show

這樣wish內建的console就會馬上反應出puts執行的結果

若你想要把puts的結果顯示在「命令提示字元上」 就需要用tclsh來執行程式

在這種情況下,你只要有用到Tk的功能(圖形介面)就需要在程式的開頭加上一行

package require Tk

然後再用下面的方式 啟動程式

C:\> tclsh xxx.tcl

瘋狂伊凡 | 2011年10月31日 下午11:42

Dai大,
太感謝你了~~~~兩種方法都成功了!!

不過我想再繼續發問,希望你不會嫌煩!因為只有問,才能越清楚明白

希望我可以繼續站在巨人肩膀上學習~~~^^

1.就我從Dai大你的教學檔一開始的認知, tclsh或wish都是一個程式,用來執行程式編輯者編輯出的程式(.txt檔),而tclsh應該主要就是文字處理, 而wish應該就是用來處理圖形的(因為在教學檔第一章,我check過error message,看起來好像就是tclsh讀不懂button這個命令). 我的理解是不是正確的呢?

2.既然如此,那wish不就是"圖形介面"了嗎?那又跟Tk有什麼不同?是不是tclsh要用到圖形的功能(Tk),就必須先執行"package require Tk"這個命令,而wish要看到類似命令提示字元的視窗(console),就必須下"console show"這個命令?
簡單的說其實tclsh和wish就是兩個軟體,但是tclsh要有圖形的功能就得另外下命令,而wish想要有類似命令提示字元的視窗,也是要另外下命令.

真的很不好意思,我提了這兩個問題也許根本在問題上就有錯誤,希望你可以指正一下!真的很謝謝你熱心的回答我的問題, 我對Tcl真的還滿有興趣的,雖然剛起步,也沒辦法馬上感受到它得好處,不果我想我會再繼續學習下去的!可能就要再繼續麻煩你囉^^

瘋狂伊凡 | 2011年11月1日 上午1:00

Dai 大
感謝你熱心幫忙!真是太感謝你了!站在巨人肩膀上學習真是幸福^^
另外我還是有些問題想請教....希望可以指點迷津
1.tclsh & wish的差異是什麼?
2. Tk & wish又是怎麼回事?看起來都是圖形介面
3.是不是如果要用tclsh又有圖形介面就必須把Tk給啟動?(命令提示字元就是windows本身的)
4.是不是如果要用wish又有命令提示字元就必須把console給啟動?(當然這個console是wish自己獨立出來的)
如果我上面的問題有點奇怪或是牛頭不對馬嘴,麻煩你指正一下!
我現在對Tcl滿有興趣的,雖然還無法感受到他的好處,但是我會持續學下去的,所以可能就要繼續麻煩Dai大囉^^, Thanks a lot!!

匿名 | 2012年3月25日 下午9:56

不好意思,我在設定變數名稱過程中,設定了清單名稱list$c,c的內容執會改變
然而在取出清單內容時,如 : lrange $list$c 0 3,當c = 3
卻無法讀到真正lsit3的內容,想請問Dai大大,有沒有其他種命名的方式:D

dai | 2012年3月25日 下午11:01

改用陣列就好了,像這樣:

set l(3) [list a b c]
set i 3
lrange $l($i) 0 2

匿名 | 2012年8月17日 下午4:06

試著做以下..
puts -nonewline "請輸入姓名:"
gets stdin name
puts $name

tclsh 執行結果:
畫面一直沒反應,直到key 123 後,
畫面顯示如下,
123
請輸入姓名123

請教,有哪個地方疏忽了嗎?
謝謝!!

Todozen | 2013年5月2日 上午10:05

Dear Dai~請問如下列兩行程式,若使用puts我該如何輸出Hello呢?thanks.
set aaa "123"
set bbb_$aaa "Hello"

dai | 2013年5月2日 下午1:31

這樣就可以了

puts [set bbb_$aaa]

Todozen | 2013年5月6日 下午5:08

Dai~我了解,不過想再請問一下,若此種變數(Ex:bbb_$aaa)的設定方式若想使用於下列程式碼,我又該如何宣告-values和-textvariable的部分呢?讓combobox可以抓到正確的值,thanks.
proc run {w opt}
set ::ws_latest_$opt "linux34"
set ::ws_list_$opt [exec ypcat hosts | awk {/\t/ {print $2}} | grep linux | sort -u]
...
ttk::combobox $w.f1.ws.box -width 12 -state readonly -values ?::ws_list_$opt? -textvariable ?$::ws_latest_$opt?
...
}
run $w "ant"

dai | 2013年5月6日 下午6:20

像這樣:

-values [set ::ws_list_$opt] -textvariable ::ws_latest_$opt

匿名 | 2013年8月1日 上午12:03

感謝dai, 讓我能加快學習的腳步, 萬分感謝!!

匿名 | 2014年2月21日 上午12:52

Dear Dai
想使用grep來作條件式判斷同一個檔案內要過濾的字串如下:
case 1.
assign bist_in_c10[0] = bist_in_a00[0]
case 2.
//assifn_fix assign bist_in_c10[0] = bist_in_a00[0]
上述有兩點case,分別為case 1 & case 2,
只想要過濾case 1.是否存在此內容,而非case 2.,
請問要如何grep "assign "不要同時都出現case 1 & case 2的內容,
得到最後的結果assign bist_in_c10[0] = bist_in_a00[0]
謝謝

匿名 | 2014年12月25日 下午10:18

Hi 版主,
我是初學者, 我想請問一個問題請你指導一下.
我的code如下
set x(Dir) "$windowsr/bin"
這x變數的路徑會在程式執行完畢CREATE出來還是什麼時間點
因為我執行完畢時去windows下找bin這個資料夾去找不到, 所以請你教導一下 謝謝

卡恩 | 2016年1月25日 下午5:10

Dear Dai:
我是初學者,最近想用Tcl/tk寫tool,但以下的程式會出現以下錯誤,但看似有執行成功我要的結果
找了很久不曉得這個錯誤應該如何修正

package require Tk
console show
entry .txtIPMI -textvariable ::ip
button .exec -text "Execute" -command {
set cmd {D:\utility\windows_ipmitool\ipmitool}
exec $cmd -H $::ip -U admin -P admin mc info
#cd D:\utility\windows_ipmitool
#set var [ipmitool -H $::ip -U admin -P admin]
#exec {*}$var sdr list
}
pack .txtIPMI .exec


錯誤:
0 [main] ipmitool 7988 tty_list::allocate: No tty allocated
while executing
"exec $cmd -H $::ip -U admin -P admin mc info"
invoked from within
".exec invoke"
("uplevel" body line 1)
invoked from within
"uplevel #0 [list $w invoke]"
(procedure "tk::ButtonUp" line 24)
invoked from within
"tk::ButtonUp .exec"
(command bound to event)

Hu Mike | 2017年5月3日 上午11:28

問一個簡單的變數問題,我要如何這樣使用?

code:
set i 0
set date$i 123
puts $date$i

error:
can't read "date": no such variable
while executing
"puts $date$i"

Hu Mike | 2017年5月3日 上午11:30

補充一下,沒有用到array的原因是因為我外面還需要一個array,但似乎tcl沒辦法data($i)($j),這樣使用。

匿名 | 2017年6月5日 下午5:01

要改成 puts [set date$i]
array的話要改成 :
set day "date${i}"
puts [set ${day}($j)]

匿名 | 2017年6月5日 下午5:11

卡恩的問題看可不可以改成:
catch {exec $cmd -H $::ip -U admin -P admin mc info}

留下您的意見

Theme Design by devolux.org. Converted by Wordpress To Blogger for WP Blogger Themes. Sponsored by iBlogtoBlog
This template is brought to you by : allblogtools.com | Blogger Templates