#

檔案I/O是寫程式時常使用的功能,例如,寫個文字編輯器一定要會讀寫文字檔案, 寫影像處理程式就要會讀寫影像檔,甚至是一般的小程式,也時常使用檔案來儲存程式的設定值。這一篇文章會簡單的說明Tcl裡操作檔案的方法。

11.1 讀取檔案

一般來說操作檔案需要經過3個步驟:

  1. 開啟檔案。
  2. 讀取或寫入檔案。
  3. 關閉檔案。

在Tcl裡你可以使用open命令來開啟指定路徑的檔案,它的語法如下:

open fileName access

open命令開啟fileName指定的檔案路徑,如果成功的開啟檔案open命令會回傳一個channelId(通道代碼),往後的檔案讀寫操作都要以這個channelId為依據。channelId的觀念就像是開啟一個通往檔案的通道,你可以透過通道去讀取或寫入檔案。第2個參數access可以用來指定開啟檔案時通道的模式,以下是access可以指定的值:

r 以「讀取」模式開啟檔案。在這種模式下檔案一定要預先存在,而且只能對檔案作讀取的操作,而不能做寫入的操作。
r+ 以「讀取」+「寫入」模式開啟檔案。在這種模式下檔案一定要預先存在,但可以允許對檔案做讀取及寫入的操作。
w 以「寫入」模式開啟檔案。在這種模式下如果檔案已存在,它的內容自動被清空,如果檔案不存在則會自動建立新檔案,而對於被開啟的檔案只能對檔案作寫入的操作,而不能做讀取的操作。
w+ 以「寫入」+「讀取」模式開啟檔案。在這種模式下如果檔案已存在,它內容自動被清空,如果檔案不存在會自動建立新檔案,這種模式可以允許對檔案做讀取及寫入的操作。
a 以「附加」模式開啟檔案。在這種模式下如果檔案已存在,所有輸出到檔案的內容會被串接在尾巴,如果檔案不存在則會自動建立新檔案,而對於被開啟的檔案只能對檔案作寫入的操作,而不能做讀取的操作。
a+ 以「附加」+「讀取」模式開啟檔案。在這種模式下如果檔案已存在,所有輸出到檔案的內容都會被串接在尾巴,如果檔案不存在則會自動建立新檔案,這種模式可以允許對檔案做讀取及寫入的操作。

以下是一個讀取檔案的範例:



這個例子是一個非常典型的讀檔程式,程式的第1行使用讀取模式開啟/Users/dai/a.txt這個檔案,當然這個檔案要預先存在不然程式會發生錯誤,如果開啟成功,檔案的channelId會被儲存在fd變數裡。第2行使用read命令從指定的channelId讀取所有的檔案內容,讀取到的內容最後會被儲存在data變數裡頭。第3行很單純的把data變數的內容輸出到螢幕上,然後最後一行使用close命令把不再使用的channelId關起來,以免佔用系統的資源。

§ 關於開檔案

在開啟檔案時你可以指定絕對路徑或相對路徑,並不一定只能用絕對路徑。另外,一個程式同時間內可以開啟的檔案數量是有限制的,所以檔案使用完畢後請立刻把它關上。

如果檔案的內容不是很大,上面的例子真的是一個不錯的方法,因為整個程式真的很簡單,但如果檔案的內容非常的大,上面的例子就不是一個好方法了,試想如果a.txt有2G元位組這麼大,那麼data變數將會佔用非常大的記憶體空間。下面是一個改進的方法,它可以讓我們一次讀4K個字元的資料進來處理,而不是一次讀整個檔案的內容進來。



對於檔案操作大部份的程式語言都有一個特性,就是如果你這次由檔案中讀取或寫入了N個位元組的資料,除非你有特別指定,否則下一次的讀寫的位置將從第N+1個位元組開始,以此類推如果再讀了N個位元組,那再下次就是由2N+1的位置開始。寫程式的人只要了解這一點再配合迴圈,就可以很方便的讀取整個檔案的內容。

程式第2行中的eof命令可以用來判斷指定的channelId是否已經讀到檔案的結尾,如果是的話eof命令回傳1,否則會回傳0,所以while條件式的意義是「如果檔案還沒讀到結尾就執行後面的程式區塊」。

第3行我們在read命令的最後面加上了4096用來指定一次讀取4096個字元,當然你也可以指定別的數值來設定一次讀取的字元數。第4行很單純的把讀取的資料輸出。最後一行把channelId關起來,以免佔用系統資源。

除了一次讀取N個字的方法外,Tcl還提供了一次讀一行的功能,它也可以與迴圈配合來讀取整個檔案。如下是程式範例:



這個程式和前一個程式只有第3行不同,gets可以由指定的channelId一次讀入一行,然後把讀到的資料儲存在後面的data變數裡。在使用上gets命令會自動判別檔案的斷行字元,所以不管在Windows、Linux或Mac OSX下都可以正常運作。注意!! gets會把斷行字元自動去除,所以讀到的資料將不會包含換行字元。

§ 關於gets命令

一般的情況下gets命令只適用在讀取純文字檔案,對於包含binary資料的檔案並不適用,原因是binary檔案裡的換行字元其目的可能並不是真的要換行,而可能是有意義的資料內容。

11.2 寫入檔案

寫入檔案不像讀取檔案有好多種方式,Tcl裡寫入檔案的方法只有一種,而且它使用的命令就是我們之前使用過的puts命令,以下是寫入檔案的範例:



程式的第1行以寫入模式開啟/Users/dai/b.txt這個檔案,如果檔案存在的話,檔案裡的內容會被清空,如果檔案不存在的話會自動建立。第2行用puts命令把指定的字串輸出到指定的channelId裡(即$fd的值),如同之前puts的用法,輸出到檔案的內容會自動加上換行,若不想要在輸出時自動加上換行,可以像第3行一樣指定-nonewline參數。第5行把channelId關起來,以免佔用系統資源。

程式執行完b.txt的檔案內容如下:

這是第1行,而且會自動斷行
這是第2行,但不會自動斷行,這會接在尾巴。


如果你不想要把已存在的檔案清空,又想要把新的資料寫入檔案,這時可以使用「附加」的模式開啟檔案。在附加模式裡所有新寫入的資料預設都會串接在檔案的尾巴。以下是一個簡單的例子。



11.3 檔案指標

上面的章節裡有提到,大部份的程式語言在檔案操作上都有一個特性,就是如果你這次由檔案中讀取或寫入了N個位元組的資料,除非你有特別指定否則下一次的讀取或寫入將從檔案中的N+1個位元組開始操作。事實上大部份的情況下我們都是使用這樣的特性在讀寫檔案,但如果你真的想要自己指定檔案讀寫的位置,那你可以使用下面的命令:

seek channelId offset ?origin?

seek 命令用來設定檔案的讀寫位置。offset是您要移動的偏移量,偏移量必需為整數,origin是您要移動的參考點,您可以指定下列的參考點:

start從檔案的開頭,offset必需是正整數。
current從目前的讀寫位置,若offset的值是負整數表示由目前位置向前移動。
end從檔案的結尾,offset必需是負整數,用來表示由檔案的尾巴向前移動。

若不指定origin參數,那它的預設值是start。注意!!seek預設是以字元為單位移動哦。

讀取檔案兩次的範例:



讀取檔案最後10個位元組的範例:



上面程式的第3行,把檔案的讀寫位置移到「檔尾的前10個位元組」,然後在第4行用read命令讀取10個位元組,如此檔案最後的10個位元組就可以讀進data變數了。注意哦!! 這個程式的重點在示範如何移動讀寫位置,所以data裡的內容不太有意義。

這個程式還有一個重點是在第2行,因為jpg是一種binary格式的檔案內容,它沒辦法當成一般的純文字檔案來處理。所以我們需要把$fd的通道設定為binary模式,為什麼要這麼做呢? 讓我們用一個例子來說明,現在假設a.txt是一個純文字檔案,然後你用下面的程式去讀取a.txt:



請問read命令讀進了多少位元組? 其實這個問題是無解的,因為read是以字元為單位來讀取檔案,也就是說在UTF-8的環境下,讀入一個字可能會有3個位元組,在其它的環境可能又是另一種狀況,也就是說read讀了多少位元組的資料是要看情況而定的。

看了上面的問題後,現在讓我再問一個問題「請問對於jpg這種圖檔read命令要怎麼正確的一次讀入1個字元?」。可想而知的圖檔資料並不是純文字資料,以字元為單位讀取肯定會完蛋對吧!! 為了解決這個問題所以我們要在讀取資料之前,先把通道設定為binary模式,這樣可以讓通道的操作變成以位元組為單位了,如此一來資料的讀取就不會發生錯誤了。

最後Tcl也有提供讓你取得目前讀寫位置的命令,語法如下:

tell channelId

tell命令用來取得指定channelId目前的讀寫位置。

11.4 binary檔案操作

前面的幾個小節介紹了檔案讀寫的操作,其實上面介紹的方法只適用在純文字檔案,換句話說如果你想要操作binary檔案的話,上面的程式就不適用了。對於binary檔案的讀寫,Tcl希望你手動去設定channelId讓它可以去操作binary的檔案內容,例如下面是一個複製jpg檔案的程式,請注意到第2及第6行,如果你要讀寫的檔案包含了binary資料,請在開始讀取及寫入資料前使用fconfigure命令的-translation參數及-encoding參數指定channelId為binary模式。



這個程式假設01.jpg是很小的檔案,如果你要複製較大的檔案,可以參考下面的程式:



這個程式配合迴圈由來源的檔案一次讀入4k位元組的資料,然後逐一寫入目的檔案。

§ 關於binary檔案

如果你不知道怎麼判別檔案內容是不是屬於binary的資料。一個簡單方法是,用記事本打開檔案,如果檔案內容看起來是一堆亂碼那就應該是binary的資料了。

11.5 字元編碼

Tcl對字元編碼有很好的支援,而且Tcl的內部預設是以UTF-8編碼系統來處理資料。Tcl為了讓內部的資料有統一的編碼方法,所以當你在讀取或是寫入檔案之前,Tcl會自動幫你做字元編碼轉換工作,而轉換的規則是依據目前作業系統預設的編碼方式然後轉入或轉出為UTF-8編碼。舉例來說:假設你目前的作業系統是採用BIG5的編碼方式,那麼你在讀取檔案內容時,Tcl會假設檔案的內容是BIG5的編碼,然後把檔案的內容通通做BIG5轉UTF-8的的動作。另外在寫入檔案時Tcl則會幫你把要寫入的資料由UTF-8轉回BIG5再儲存到檔案裡。

一般的情況下Tcl的自動編碼轉換都不會有問題,除非你是在目前的編碼系統下使用另一種編碼系統。例如:目前的作業系統預設是用UTF-8編碼,但你要操作的檔案內容是BIG5編碼。在這種情況下編碼的轉換就會出錯,因為Tcl會把BIG5的檔案內容當作是UTF-8的檔案內容,然後執行轉換的動作。若要處理好這個問題,我們就需要手動指定編碼轉換方法。以下是一個在UTF-8環境下讀取BIG5檔案的範例:



這個程式的重點在第2行,我們使用fconfigure的-encoding參數來指定channelId的編碼將採用BIG5。在上面的例子裡data變數儲存的內容將是BIG5轉換為UTF-8後的結果,簡單的說data的內容是UTF-8編碼的資料。

反相思考,如果你想要寫一個轉換檔案編碼的程式,例如:BIG5轉UTF-8則可以這麼寫:



程式先以BIG5編碼的方式讀取index.html檔案,並把資料儲存在data變數裡,然後再以UTF-8的編碼方式把data變數的內容寫入檔案,所以檔案的內容最後就變為UTF-8的資料了。

123 個意見

Midas | 2009年7月6日 下午12:40

請問有關 open pipe line
http://www.tcl.tk/cgi-bin/tct/tip/304.html

要如何使用 ? 有沒有 把 Win32 cmd.exe 導向 到 tk::Console 去 ?

COMMAND PIPELINES
If the first character of fileName is ``|'' then the remaining characters of fileName are treated as a list of arguments that describe a command pipeline to invoke, in the same style as the arguments for exec. In this case, the channel identifier returned by open may be used to write to the command's input pipe or read from its output pipe, depending on the value of access. If write-only access is used (e.g. access is w), then standard output for the pipeline is directed to the current standard output unless overridden by the command. If read-only access is used (e.g. access is r), standard input for the pipeline is taken from the current standard input unless overridden by the command.

Dai | 2009年7月6日 下午3:06

關於pipe你可以參考ezdit原始裡lib/ttexec.tcl這個檔案,裡面我有實際使用pipe的地方。
或著你也可以參考CrowTDE原始碼裡lib/crowExec.tcl這個檔案,裡面用不一樣的方法處理pipe。
另外你說的導到tk::Console,這是沒問題的,像想由pipe讀取資料,再輸出到Console,是可以理解的。

Midas | 2009年7月6日 下午11:34

#Dai 大大你好:

#謝謝你的指點 . 我終於找到了.
set chan [open "|c:/windows/system32/cmd.exe" r+]
#如此一來就可以 把 dos console 掛在 chan 這個 channel/Handle 了.

#再來 仿 RS232 的設定模式 設定 這個 channel 的屬性 如下.
fconfigure $chan -translation binary -buffering none -blocking 0


#用 tcl 指令 下個 "dir+[Enter]"
puts $chan "dir/w"

#取回 結果
read $chan
#嗯 都如 預期....

#我想輸入 "test" [不含 Enter]
#但出現了兩個問題...

#第一 無[Enter]指令下達後出去.讀不到. 這樣有些 menu mode 的 dos api 屆時會無法有效偵測執行.
puts -nonewline $chan "test"
#但 "read -nonewline $chan" 讀不到"test".沒反應...
#一定要 puts $chan "onemoretime" 不帶 "-nonewline" 才會 show 出來.
puts $chan "onemoretime"
#取回 結果
read $chan
#result = "testonemoretime"
#P.S: 我試 RS232 並不會有這個狀況 .

#第二 . 在 $chan cmd.exe 下. 如執行 ipconfig or ftp or telent ...
puts $chan "ipconfig"
#他會自動再跑出一個 dos視窗出來.... 不受 tcl 控制....

#以上 二 點 . 有大大 有 時間麻煩再 指點迷津 . 感恩 .

Dai | 2009年7月7日 上午11:00

關於第一個問題(test)也許可以試試執行一下flush看會不會改善:

puts -nonewline $chan "test'
flush $chan

關於ifconfig、ftp....可以直接開一個pipe過去而不要透過cmd.exe嗎? (我沒試過哦!!)

Midas | 2009年7月7日 下午1:03

謝謝大大 :
好感動喔.
剛再 試過了幾次 .
puts -nonewline $chan "test"
flush $chan

gets $chan
read $chan 1
read -nonewline $chan
都讀不到 ...
除非 下了 puts $chan "";

我再猜 是否為 cmd.exe 裡面 的 . 設定問題..

查了一下 "cmd /?" , 裡頭的 "/Q"

set chan [open "|cmd.exe /Q" r+]

裡頭所有 keyin 的字 完全沒出來.
但 output 都有 .
所以 我想 這個 應該是 cmd.exe pipe stdin 要有 Enter 才能 觸發 cmd.exe stdout event update $chan 的 buffer . 不過這只是假設而己.

Dai | 2009年7月7日 下午1:28

恭喜你成功了。

嗯!! 看來我了解你的問題了。你說的對cmd.exe是一次一行的命令,也就是說你一定要有斷行字元他才會正確的判斷「這是一個完整的指令」。

對了有興趣的話也許你可以參考BLT套件,他對exec有額外的處理。

TerryWang | 2009年10月14日 下午4:34

D大..我有TCL程式碼的bug問題...

但程式碼太多行了...可否寄信給你

幫忙解惑一下...功力不夠深找不出問題..

Dai | 2009年10月14日 下午4:41

那請你寄到 adai1115@yahoo.com.tw

TerryWang | 2009年10月14日 下午5:53

D大...我寄了..在麻煩您抽空看一下吧

若沒收到再跟我說

Dai | 2009年10月14日 下午7:33

已經回信了。

JIN | 2009年12月10日 下午10:23

板主你好:
請問,如果檔案格式下 a.txt (可能十幾mb這樣的字串格式)
1260320722.757681 0.4011294738166946 2.094770144384971 -0.1369512647921918
我該怎指定 各個 column 進行處理後 然後在組合輸出 ?
是否使用迴圈 ? 因為詳讀您i/o 後還是不知道怎下手..

dai | 2009年12月10日 下午10:49

如果a.txt裡面有斷行的話,可以這樣:

以下假設每個欄位用1個空白分隔

set srcFd [open a.txt r]
set destFd [open b.txt w]

while {![eof $srcFd]} {
gets $srcFd buf
set data ""
foreach item [split $buf " "] {
# item 就是每一個欄位的值
# 這裡執行你轉換item的程式
append data "轉換後的 $item" " "
}
puts $destFd [string trimright $data]
}

close $destFd
close $srcFd

如果整個檔案都沒有斷行:

set srcFd [open a.txt r]
set destFd [open b.txt w]

set val ""
while {![eof $srcFd]} {
set ch [string trim [read $srcFd 1]]
if {$ch == "" && $val != ""} {
# val 就是每一個欄位的值
# 這裡執行你轉換val的程式
# 以下假設每個欄位用1個空白分隔
puts $destFd "$val "
}
if {$ch == ""} {set val "" ; continue}
append val $ch
}

close $destFd
close $srcFd

嗯~~邏輯上大概是這樣。

PS.我沒有測試上面的程式哦!!

JIN | 2009年12月11日 下午5:42

Hello 板主你好:
感謝你的提示,我已經完成轉換程式,但是s3輸出時會多雙引號,
{20091210 093414.999542} 22.94548829051449 120.04581672528869
如何改寫改寫成
20091210 093414.999542 22.94548829051449 120.04581672528869

請問我的寫法哪些是多餘的,如果我文字檔案超過10M byte,該如何提高效率?
感謝您囉!!


寫法如下:
set srcFd [open a.txt r]
set destFd [open b.txt w]

while {![eof $srcFd]} {
gets $srcFd buf
set data " "
foreach item [split $buf " "] {
set x1 "$buf"
set time1 [lindex $x1 0]
set lat [lindex $x1 1]
set long [lindex $x1 2]
set s1 [expr int($time1)]
set s2 [expr (round($time1*1000000))%1000000]
set s3 [clock format $s1 -format "%Y%m%d %H%M%S"].$s2
puts $s3
set lat1 [expr ($lat/(3.14159265/180))]
set long1 [expr ($long/(3.14159265/180))]
set total [list $s3 $lat1 $long1]

}
puts $destFd [string trimright $total]

}

close $destFd
close $srcFd

dai | 2009年12月11日 下午6:30

看來你的a.txt裡,每一行就是一筆記錄,這樣的話程式可以這樣寫 :

set srcFd [open a.txt r]
set destFd [open b.txt w]

while {![eof $srcFd]} {
gets $srcFd buf

# 也許你需要在這邊把空的或是無效的記錄跳開
if {[string trim $buf] == ""} {continue}

lassign $buf time1 lat long

set s1 [expr int($time1)]
set s2 [expr (round($time1*1000000))%1000000]
set s3 [clock format $s1 -format "%Y%m%d %H%M%S"].$s2

puts $s3

set lat1 [expr ($lat/(3.14159265/180))]
set long1 [expr ($long/(3.14159265/180))]
set total "$s3 $lat1 $long1"

puts $destFd [string trimright $total]
}

close $destFd
close $srcFd

JIN | 2009年12月21日 下午4:24

Hello Dai :
剛從海上實習回來,謝謝你的指導,不過還有一個問題在
set s2 [expr (round($time1*1000000))%1000000)
我取用這數字時,第一個如果是0,在 set3時整合時0會消失掉?我想應該是程式中數字處理的問題,我該如何做可以把0保留呢?
20091210 015409.716520 (這是結果!)
20091210 015409.0716520 (這才是我要的)

dai | 2009年12月21日 下午6:52

是這樣子的,下面這一行的結果一定只會出現最多6個數字

set s2 [expr (round($time1*1000000))%1000000)

如果你希望在前面補0,例如,補滿7位數,那可以這樣做:

set s2 [string range 0000000$s2 end-6 end]

如此一來s2的前面就會補0而且會有7位數

sam | 2010年1月13日 下午4:41

Dai大大,請問一下,有沒有辦法儲存puts指令的結果到一個變數中呢?
不知道有沒有這種的語法結構,謝謝大大。

dai | 2010年1月13日 下午7:59

是不是要類似這樣的指令(假的哦) :

set ret [puts "abc"]

如果沒想錯的話...我會改寫puts命令,像這樣:

rename ::puts ::__puts

proc ::puts {args} {
::__puts {*}$args
return [lindex $args end]
}

set ret [puts "abc"]
puts $ret

這樣的話"abc"也會被儲存在ret變數裡

sam | 2010年1月15日 上午10:40

Dai大大,這就是我要的菜,我知道改寫puts命令的方法,不過就是不知道關鍵的"::__puts {*}$args"這一句要怎麼寫出來,想請問一下Dai大大,在參數前加一個{*}是執行puts兼導入至$args嗎,那我只想將結果導入至$args裡,而不要真正的puts出結果,那我是不是要變更puts指令的channel(I/O device such as a file, serial port, or TCP socket),不知道有沒有類似變更到"空channel"的方法能達到這個目的,或有其它方法能達到這個目的嗎,謝謝大大,感恩。

dai | 2010年1月15日 下午1:22

{*}的功能是展開串列的意思,例如:

lappend l {*}[list a b c d]

被展開後,可以看成:

lappend l a b c d

所以在我的例子裡,如果使用者執行:

puts -nonewline $fd "abc"

傳到puts程序展開後就會類似像這樣:

::__puts -nonewline $fd "abc"


你的意思是說...把某個數值puts到另一個變數嗎? (set 不能嗎??) 你說的空的channel是不是指on memory的channel ? 如果是的話tcl8.5以以的版本chan create命令可以建立 "script level channel" 這種channel你可以透過refchan去實作屬於自己的read、write、seek...等方法。

sam | 2010年1月19日 上午9:02

嗯,我的主要用意大概是要把puts指令的結果顯示在VC的EDIT BOX中,而執行TCL_EVAL()時,只要碰到puts的指令就會有類似can't find stdout的錯誤,原因就是沒有redirect puts channel,我目前的作法是先將puts的結果導出到一個txt file再load進EDIT BOX中,我理想的作法是能直接存起來不用再額外建立一個txt file,而Dai大大rename的方法可以儲存puts的結果,但還是會用到真實的_puts(原puts)指令,所以終究還是要redirect puts channel,Dai大大提的tcl8.5版以上的chan我有空來試試再來跟大大報告結果,3Q...

dai | 2010年1月19日 下午12:22

你是要redirect output到文字方塊裡嗎? 在ezdit裡有用到類似的方法,ezdit在執行外部程式的時候會把外部程式的輸出重新導向到文字方塊(tkcon)裡顯示,如果你有興趣的話可以參考ezdit原始碼裡的run.tcl。

匿名 | 2010年9月16日 上午11:48

我想請問一下,如果我要讀進來的檔案是ANSI碼的話,我要run的tcl程式就沒題;但是,如果我要把讀進來的檔案轉成UTF-8的話,就會有錯誤了。請問我要怎麼解決這個問題呢?謝謝。我要補充一下的是,我讀進來的檔案必須要傳成UTF-8,因為檔案裏有兩、三種國家語言,如果把檔案轉成ANSI碼的話,它顯示的字串就會錯了。

sam | 2010年9月20日 上午9:28

Hi 您好:

我對這個不太熟,若有說錯請指正,我查了一下發現有人有跟你一樣的問題,答者的答案如下:

"ANSI" is probably some of the windows encodings. In a non-English
world, you need to convert (for example Norwegian has a slashed o).
And of course the file has been opened. Then use "fconfigure -encoding"
to explicitly set the input encoding.

The following snippet converts a file from latin-1 encoding to UTF-8:

set fdr [open "l1t.txt" r]
fconfigure $fdr -encoding iso8859-1

set fdw [open "utf8t.txt" w]
fconfigure $fdw -encoding utf-8

while { [gets $fdr line] >=0 } {
puts $fdw $line
}

close $fdw
close $fdr

For Winansi you have to look up the right encoding name.

我又試了一下:

test1.txt內容如下(以ANSI存檔):
-------------------
HELLO WORLD
大家好
-------------------

myTran.tcl內容如下:
-------------------
set fd1 [open C:/test1.txt r]
set fd2 [open C:/test2.txt w]
fconfigure $fd1 -encoding big5 ; #註1
fconfigure $fd2 -encoding utf-8 ; #註2
while {![eof $fd1]} {
set data [read $fd1 4096]
puts -nonewline $fd2 $data
}
close $fd1
close $fd2
-------------------
ansi似乎非encode的一種方式,它應該是指當地國家windows所使用的encode方式,如台灣就是big5,如上文中所提到,你必需先找出正確的encoding name(如台灣為big5),
另外,在註1的地方改成"ansi"會有錯誤,錯誤訊息為"不明的encoding名稱",
若改成"big5"就可正常的轉換成utf-8。
(上文中是以拉丁文(當地國家windows的ANSI)轉成utf-8)。

Terry | 2010年10月25日 上午11:43

Hi Dai
請問如果介由TCL/TK來建立大量的檔案, 每一檔案大小為2MB, 要用什麼command? 嘗試用dd來執行, 不過系統回報 "invalid command dd"

dai | 2010年10月31日 下午1:58

一個簡單的方法是這樣:

建立X MB的檔案

set fd [open xxx.dat w]
for {set i 0} {$i < 1024} {incr i}
puts $fd "要丟到檔案的內容(長度要1K)"
}
close $fd

當然你可以改迴圈的圈數,建立不同大小的檔案

然後再用 file copy 把這個xxx.dat複製成其它檔案就好

匿名 | 2010年12月31日 下午4:27

在 11.2
code:
set fd [open /Users/dai/b.txt w]
puts $fd "這是第1行,而且會自動斷行"
puts -nonewline $fd "這是第2行,但不會自動斷行"
puts $fd ",這會接在尾巴。"
close $fd

程式執行完b.txt的檔案內容如下:
---------------------------------------
這是第1行,而且會自動斷行
這是第2行,但不會自動斷行,這會接在尾巴。
---------------------------------------

輸出的內容應該是要多一行,最後的puts沒有加nonewline
---------------------------------------
這是第1行,而且會自動斷行
這是第2行,但不會自動斷行,這會接在尾巴。

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

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

修正了謝啦~

匿名 | 2011年1月14日 下午4:46

請問大大 在tcl下有沒有類似C下面getch的指令,等待讀取一個字元,輸入的值不顯示在螢幕上?

dai | 2011年1月16日 上午10:55

有的,請先安裝tcllib在裡面有term的套件使用例如下:


package require term::receive
package require term::ansi::ctrl::unix
puts -nonewline "輸入一個字元:"
flush stdout
::term::ansi::ctrl::unix::raw
set ch [::term::receive::getch stdin]
puts "你輸入了了:$ch"

匿名 | 2011年1月18日 上午10:53

HI 大大 我裝了tcllib然後執行上述的code出現以下訊息
The external requirements for the use of this package (tput, stty in $PATH) ar
e not met.
while executing
"::term::ansi::ctrl::unix::INIT"
(file "C:/Tcl/lib/tcllib1.12/term/ansi/ctrlunix.tcl" line 78)
invoked from within
"source C:/Tcl/lib/tcllib1.12/term/ansi/ctrlunix.tcl"
("package ifneeded term::ansi::ctrl::unix 0.1" script)
invoked from within
"package require term::ansi::ctrl::unix"
(file "getch_test.tcl" line 2)

是不是我有什麼沒裝到 還是缺了什麼東西 @v@

吉米 | 2011年1月19日 上午10:10

看起來是少了tput和stty這兩個command,應該是unix下的指令吧
要把echo關掉可以用另外一個packet "Expect"
以下是ActiveTcl裡的一個範例
package require Expect
exp_stty -echo
exp_send_user "Password: "
expect_user -re "(.*)\n"
set password $expect_out(1,string)
exp_stty echo

匿名 | 2011年1月19日 下午5:07

大家好,請問一下:

Tcl目前有沒有辦法做到執行完A.exe後,
再自動的執行B.exe,執行完B.exe後,再自動的執行C.exe。
(註:exe為C++ Compiler後產生的執行檔,而非Tcl的執行檔)

如果Tcl沒有辦法做的到,Expect有辦法做到嗎?
謝謝。
(我是有聽到別人說可以做到這樣子,但我不知道如何下手)

dai | 2011年1月28日 上午12:37

謝謝吉米的幫忙~

在Tcl裡執行外部程式是非常容易的,像這樣:

exec A.exe
exec B.exe
exec C.exe

這樣就會依序執行了

Jin | 2011年3月29日 上午6:20

Hi 版大:
你是否能在 binary code 讀/寫檔部分多加說明一下 ? 目前遇到一個問題是要擷取a檔(binary) 轉換成 b(binary) 格式, 請問有哪些資源可以查到相關範例 ?

dai | 2011年4月2日 上午8:36

Hi Jin,

你要不要說說看詳細的轉換功能,再看有沒有幫得上忙的地方。

不然的話你也可以到Tclers Wiki爬看看,這是Tcl最大的資源站。

匿名 | 2011年6月13日 下午6:42

你好
請問下面這行
set fd [open /Users/dai/a.txt r]
路徑 /Users/dai/a.txt 的上一層應該是哪裡?
是C槽根目錄 或 Tcl安裝的資料夾 或是檔案所在的同層目錄?

dai | 2011年6月14日 下午8:10

hi

/Users/dai 是Mac檔案系統的路徑,如果你用的是windows可以指定 c:/a.txt 或是 d:/a.txt。

匿名 | 2011年11月4日 下午2:51

Hi Dai,

使用以下程式寫入檔案
set fid [open "test.txt" w] ;
puts $fid "hello" ;
close $fid
我發現在第一行程式就會產生一個test.txt的檔案
但是程式要到第三行關閉fid後 text.txt才會出現hello的文字
在關閉檔案以前則一直都是空白的

因此 如果我的程式用puts $fid的方式來寫入log
則直到我程式跑完關閉fid為止都看不到那些log
想請問有什麼方法可以即時更新呢? (當然 每寫一行就關掉再打開也是可以 只是很麻煩)
謝謝 ^^a

匿名 | 2011年11月4日 下午3:05

自問自答: 用flush $fid 即可 XDrz

匿名 | 2011年11月27日 下午6:43

版主:
我用的是linux系統,頭一次使用TCL。也寫了很多C++原始碼檔案,想要把這些寫好的C++程式,放在TCL視窗程式上面使用。通常是怎樣寫的?
例如:C++原始檔案名稱test.cpp , 編譯後為test 。想要放在一個按鈕上面,按下去就有這功能,然後結果顯示在欄位上。
版主的教學網頁實在太棒,有空可以聯絡一下。
我的msn:crazystudent.tw@yahoo.com.tw

匿名 | 2011年11月30日 下午5:25

Hello Dai 你好

我想用tcl script中呼叫外部的c程式(myprogram)執行其他工作
呼叫後tcl script不等待外部c程式的執行結果繼續執行
如何讓外部c程式不因tcl script的結束而終止呢

exec myprogram & <--這行執行後 myprogram會變成tcl shell的subprocess
tcl會繼續執行script中的其他指令
當tcl執行exit的時候 myprogram也被終止掉了

dai | 2011年11月30日 下午8:10

那就....讓tcl在背後偷偷執行,直到偵測到 myprogram 結束時才離開,這樣可以嗎?

匿名 | 2011年11月30日 下午10:07

恩 不行耶 因為其實myprogram就是用來看tcl shell是不是結束了
如果結束的話要寫一個檔案告訴其他程式說這個tcl shell結束了
此外 myprogram 的另外一個責任是偵測別的程式有沒有生出要tcl shell結束的檔案
如果有的話 myprogram 要向tcl shell發出kill的signal

dai | 2011年11月30日 下午10:52

你的方法好像怪怪的哦~~

如果 myprogram 是child process 用他來 kill parent process ?

是不是應該反過來 由myprogram來啟動tcl shell ?

另外 ~ 用 tcl script 自己偵測你說的外部檔案來結束自己可行嗎?

tcl script 在自己要exit前寫入另一個檔案 告訴別人說自己結束了 好像也可以

andyto202 | 2011年12月30日 下午12:31

您好
我有灌
ActiveTcl 8.5.11.0
請問tcl可以透過windows用ssh 2連到設備下指令嗎??
謝謝

dai | 2011年12月30日 下午1:06

請參考 :
http://wiki.tcl.tk/11542
http://wiki.tcl.tk/201

紋淇 | 2012年3月7日 下午6:01

老師~~

我是紋淇
想請問一個問題

我要把RS232裝置傳回來的訊息
存到文字檔"b.txt"

但是還是無法成功
文字檔都是空的 ><~~~

應該是我沒有抓到RS232那邊傳回來的值~~~~


#open serial port
set fh [open COM1: RDWR]
fconfigure $fh -blocking 0 -mode 115200,n,8,1 -translation binary -translation cr -buffering line

set fd [open b w]

# make a text window for incoming communication
text .t -width 100 -height 20
pack .t -expand y -fill both
fileevent $fh readable {.t insert end [read $fh] ;.t see end}

puts $fh "show ip int"
gets $fh line
puts $fd $line
close $fd

dai | 2012年3月7日 下午7:24

妳怎麼玩起Tcl了.... (Tcl操作RS232...非常的適合!!)

先給妳一個簡單的例子試試~~ 先確定可以讀到資料,再儲存到檔案

text .t -width 100 -height 20
pack .t -expand y -fill both

set fd [open COM1: r+]
fconfigure $fd -blocking 0 -mode 115200,n,8,1 -translation binary -encoding binary

# 下一行讓tcl不要自動輸出斷行,所以你可以在字串的尾巴自己加 \n , \r 或是 \n\r , \r\n
puts -nonewline $fd "show ip int\r"
flush $fd

# delay 1.5秒 再讀
after 1500
set data [read $fd]

#把讀到的長度 還有資料加到text的尾巴
.t insert end [string length $data],$data

close $fd

紋淇 | 2012年3月8日 上午11:46

yaya~~~謝謝~~~~~~
終於看到console了~


我最近在寫Automatic Web Testing(用sahi~ 我覺得好有趣)
也一邊試一下CLI
本來是用Aspect (Procomm) 做CLI測試
可是Aspect (Procomm)沒辦法整個command tree
我想要把整個command tree 抓出來
然後再下每一個command


我還有一個小小的問題
就是RS232傳回來的值
是不是不能用 while {![eof $fd]} 去分行



我的一小部分command tree長得像樓下這樣

Console(config)#?
Configure commands:
aaa Authentication, Authorization and Accounting settings
access-list Access lists
arp Address Resolution Protocol
authentication Authentication method
auto-traffic-control Auto traffic control configuration
Console(config)#aaa ?
accounting Accounting configurations
authorization Authorization configurations
group AAA group definitions
Console(config)#aaa accounting ?
commands Runs accounting for command service request
dot1x Runs accounting for 802.1X service request
Console(config)#aaa accounting commands ?
<0-15> Privileged level
Console(config)#aaa accounting commands 0

紋淇 | 2012年3月12日 上午10:13

謝謝~~~~~~

上次好像沒有留言成功

我最近在做web auto testing
一邊試試看掃cli 所有的command


像這樣一層一層的指令進去掃
但是我只能讀到第一層 ><~~~

Console#con
Console(config)#?
Configure commands:
aaa Authentication, Authorization and Accounting settings
access-list Access lists
arp Address Resolution Protocol
Console(config)#aaa ?
accounting Accounting configurations
authorization Authorization configurations
group AAA group definitions
Console(config)#aaa accounting ?
commands Runs accounting for command service request
dot1x Runs accounting for 802.1X service request
exec Runs accounting for EXEC service request
update Enables accounting update records
Console(config)#aaa accounting commands ?
<0-15> Privileged level
Console(config)#aaa accounting commands 0 ?
WORD Named accounting list
default The default list of methods for accounting services
Console(config)#aaa accounting commands 0 test ?
start-stop Records start and stop accounting notices without waiting
Console(config)#aaa accounting commands 0 test start-stop ?
group Uses a server group
Console(config)#aaa accounting commands 0 test start-stop group ?
WORD A server group name
tacacs+ Uses the list of all TACACS+ hosts
Console(config)#aaa accounting commands 0 test start-stop group radius ?

Console(config)#aaa accounting commands 0 test start-stop group radius

我的程式如下 嗚嗚><
本來想要用遞迴讓他去下一層找""

可是失敗了
請問應該要怎麼寫比較好呢?

proc test {command i } {
set fd [open COM1: r+]
fconfigure $fd -blocking 0 -mode 115200,n,8,1 -translation binary -encoding binary
puts -nonewline $fd $command ; flush $fd
after 1500

while { [gets $fd line]>=0 } {
set previousCmd $command
set temp [string first " " $line]
set temp2 [string first ":" $line]
global arr
if {$temp == 0} {
if {$temp2 < 0} {
set temp [string wordstart $line 0]
set index [string first " " $line 2]
set arr($i) [string range $line 2 $index]
set temp [string first "" $arr($i)]
puts $arr($i)
#if { $temp >=0 } {
# close $fd
# return test $arr($i) $i
#} else {
# close $fd
# return test "? \r" $i
#}
set i [expr $i+1]
}
}
}
}

set command "? \r"
set i 0
global arr
set num [test $command $i]

紋淇 | 2012年3月13日 下午1:12

謝謝~~~~

上次的留言好像內容太多沒留成功 ><
我現在在做web auto testing
也一邊試試看cli的
但cli 一直失敗

我想要把所有cli的command 都跑過一遍
https://picasaweb.google.com/wenew657/Console#5719216119871142498
但是碰到好多問題><、

https://picasaweb.google.com/wenew657/Console#5719216118432947682
出現這一行的時候,不會自動輸入a
--- [Space] Next page, [Enter] Next line, [A] All, Others to exit ---

老師可以幫我看一下嗎? 嗚嗚



set fd [open COM1: r+]
fconfigure $fd -blocking 0 -mode 115200,n,8,1 -translation binary -encoding binary

proc sendcmd {fd command i} {
puts -nonewline $fd $command; flush $fd
after 1500
global arr
#puts [read $fd]
while { [gets $fd line]>0 } {
if {[string first " All, Others to exit" $line] >=0 } {
puts $fd "a" #不會自動輸入"a"
}
set check [ regexp {(^[\s]{2}[^\s]{1,})} $line ]
if {$check == 1} {
regexp {(^[\s]{2}[^\s]{1,})} $line match line
regexp {([^\s]{1,})} $line match line

set arr($i) $line
#set temp [regexp {} $arr($i) ]
#if {$temp ==0} {
# append arr($i) " ?"
#}
puts "$i: $arr($i)"
set i [expr $i+1]
}
}
}

global arr, i
sendcmd $fd "?\r" 0

dai | 2012年3月14日 上午1:47

嗯~~ 看起來你好像是要改這...

if {[string first " All, Others to exit" $line] >=0 } {
puts -nonewline $fd "a\r"
flush $fd
after 1500
}

也許你需要的是這個: expect

http://wiki.tcl.tk/201

Bravo | 2012年4月27日 下午3:19

Dear Dai :
老師您好 ,感謝您之前的回覆,再寫入檔案中時有些疑問,
set fid [open $SCRIPTDIR/roi.txt a]
puts $fid [LineSet_Inside._SelectLineSet getNumLines ]
close $fid

這邊我將我的目標函數NumLines寫入,不過他是由

for {set i 0.2} {$i<=1} {set i [expr $i+0.2]} {
for {set j 0.2} {$j<=1} {set j [expr $j+0.2]} {
for {set k 20} {$k<=70} {incr k 5} {
這個loop所得到
我希望可以將i j k等等變數寫到NumLines之後 ,想請問老師該如何修改最上述的程式碼才可得到我下例的結果
我的NumLines為一個實數值 ,希望可以得到的結果例如
140 i=0.2 j=0.2 k=20
150 i=0.4 j=0.2 k=20
..
.
.
.
.
感謝老師不吝指導 By Bravo

Bravo | 2012年4月27日 下午3:36

自問自答= =
set fid [open $SCRIPTDIR/roi.txt a]
puts $fid "[LineSet_Inside._SelectLineSet getNumLines ] i=$i j=$j k=$k"
close $fid

匿名 | 2012年5月14日 上午10:35

a檔案:
k=l
a====ijb
c=d
=m
e=f
i=j
g = h
現在會印出b:
k is l
a is ijb
c is d
illegal statement =m
e is f
i is j
g is h
想請問2個問題,一個是我要如何修改才能把g is h中間的空白拿掉
一個是要怎樣才能只刪掉一個等於其他等於保留ex: a====ijb印出a===ijb
謝謝~不好意思 麻煩您
set srcFd [open a.txt r]
set destFd [open b.txt w]
puts "Enter a varible:"
gets stdin ::input
while {![eof $srcFd]} {
gets $srcFd buf
set data ""
set buf2 [string trim $buf " "]
set lines [split $buf2 "="]
set first [lindex $lines 0]
set last [lindex $lines end]
if {$first==""} {
puts $destFd "illegal statement =$last"
} else {
append data "$first is $last"
puts $destFd [string trimright $data]
}
if {$input == $first} {
puts "$first = $last"
}
}
close $destFd
close $srcFd

匿名 | 2012年6月9日 下午8:49

你好
我是 Fuusenka

我嘗試了下面的tcl :
本來以為 read $fd1 8 的 8 是指 8 個二進位 的 位元
可是從結果看起來並不是.
所以我想請教你兩個問題:
1. 如何指定讀入二進位檔時,
一次只讀入四個 二進位 的位元?
2. 寫入檔案時,
如何把四個 二進位 的位元 轉換成 二個 十六進位 的 位元,
然後才寫入檔案?
不知道 tcl 可以做得到嗎?

set fd1 [open file_input r]
set fd2 [open file_output w]
fconfigure $fd1 -encoding binary -translation binary
while {![eof $fd1]} {
set data [read $fd1 8]
puts $fd2 $data
}
close $fd1
close $fd2

dai | 2012年6月10日 下午6:20

hi Fuusenka ,

Tcl 沒辦法以位元為單位讀取檔案哦,最小的單位是 byte 位元組!!

不過您可以讀入N個位元組,再用位元運算子遮出期望的位元值。

匿名 | 2012年7月5日 下午5:54

您好:
請問有沒有辦法直接抓取目前電腦的serial port。
例如~目前有com1,com2,com5,執行程式後即可找出。

匿名 | 2012年7月5日 下午6:32

Sorry, 方法我找到了,底下這段就可以用了。

set serial_base "HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM"
set values [registry values $serial_base]
set res {}
foreach valueName $values {
set PortName [registry get $serial_base $valueName]
puts $PortName

揚楷 | 2013年1月11日 下午11:28

Hi Dai大:
謝謝妳熱心的教學
對於我這個初學者很有用,真的!
想請教一下,上面有一段程式我好難理解可否幫我解惑一下
if {$ch == "" && $val != ""} {
puts $destFd "$val "
}
if {$ch == ""} {set val "" ; continue}
append val $ch
}
我看不懂{$ch == "" && $val != ""}
還有{$ch == ""} {set val "" ; continue}
麻煩有空幫我解惑一下

感恩!!

Jimmy | 2013年2月27日 上午10:57

Dear Dai,

我想請問如何同時telnet 並將結果輸出在檔案上,
如下,我現在只能第一個執行後在執行第二個,答案也是第一個產出後,才會產出第二個,
是否有更好的寫法,謝謝!

proc test1 { } {
log_user 0
spawn telnet 192.168.1.1
expect -nocase "login:"
send "12345\r"
expect -nocase "#"
send "./test.sh"
set r1 [open "C:/tmp/test1.txt" "w+" ]
puts $r1 $expect_out(buffer)
flush $r1

}

proc test2 { } {
log_user 0
spawn telnet 192.168.1.2
expect -nocase "login:"
send "12345\r"
expect -nocase "#"
send "./test.sh"
set r2 [open "C:/tmp/test2.txt" "w+" ]
puts $r2 $expect_out(buffer)
flush $r2

}

test1; test2

dai | 2013年3月1日 下午1:10

如果想要並行處理,可以使用多緒的方法,把test1,test2交給不同的thread執行。

Jimmy | 2013年3月3日 下午11:28

Dear dai,
感謝你的建議,我剛剛試著用thread來處理,但可能是我第一次使用,所以寫起來沒有作用,是否可以再提供更詳細的建議,謝謝!

匿名 | 2013年4月18日 下午2:22

Dear dai大:
拜讀您的教學文件讓我學習TCL很有幫助,最近在綀習使用檔案讀寫的部份,試了很久,卡住了,想請您教我,如果我想修改檔案內容的某一行的參數,行數與項目都不變只修改參數,例如:第10行原為A=1,開啟檔案後找到此行把值改為A=2,A這個欄位不變動,我不想把已存在的檔案內容清空,只要把新的參數寫入檔案,請問還是用此篇的教學文件就可達成嗎??
麻煩您解答了,感恩~~

dai | 2013年4月18日 下午5:58

嗯 ~ 假設這個文件不會大到幾十MB,那可以先用讀取模式把檔案一次讀入並存在某個變數,ex. data ~
然後由data中找出A=1的部份改成A=2然後再寫回檔案就可以了。

Jarry_chang | 2013年4月19日 上午11:28

大大,我想問一個 Thread 的問題

我的環境 windows XP , Tcl TK 8.6 , 封裝為 exe 檔

我的架構

TK GUI 按下 Button 會開啟一個 Thread 運行 socket deamon 接收字串 , 接收到字串之後 會根據字串 運行相對應的 Thread (例如開啟一個 Ping 網段的 Thread)

問題是在 第二層的 Thread 在 Package require 的時候 會找不到 需要的 Package

我在 GUI 、第一層的 Thread 、第二層的 Thread 個別 puts 出 ::auto_path 、tcl::tm::path 都是一樣的

在有安裝 Tcl Tk 直譯器的環境運作沒有問題
在沒有安裝 Tcl Tk 直譯器的環境運作才會發生

dai | 2013年4月19日 下午4:54

嗯 ~ 可以試試2個方法

1. 建立新thread動作全都由main thread來完成,其它的slave如果要建立thread就用thread::send -async ... 的方法請main thread來建立

2. 換一個新的tclkit試試看

Jarry_Chang | 2013年4月22日 上午11:02

大大,我測試過兩種方式了,可是狀況還是一樣

dai | 2013年4月22日 下午1:02

@@ 那只能出絕招了!! 看星期幾 多那之 ~

Jarry_Chang | 2013年4月22日 下午3:18

大大,package 的 問題弄好了 ,

因為我之前的 環境是 8.6bx 的版本 所以我的 TclOO 是 0.7 、tdbc 是 1.0b17 、tdbc::mysql 1.0b15

可是我的 tclkit 換了 tclkit 8.6 ,tclKit 的 TclOO 是 1.0 所以會有問題

所以把 tdbc 與 tdbc::mysql 換成 1.0.0 就可以了


** 但是 在使用 tdbc::mysql::connection 無法連線
我在想是不是 找不到 libmysql.dll

dai | 2013年4月22日 下午4:36

嗯 ~ 很有這個可能性

我的做法會在每次執行程式時,先把libmysql.dll copy 一份到家目錄下

然後把放libmysql.dll的位置加入PATH環境變數

Jarry_Chang | 2013年4月24日 上午9:44

大大,請問

環境 Ubuntu 12.04 desktop
設定 libmysql.so Path 問題

1. 開啟 gnome-terminal

$ export LD_LIBRARY_PATH=/home/mysql_path/lib/

$ tclsh

% puts $::env(LD_LIBRARY_PATH)
/home/mysql_path/lib/

% package require tdbc::mysql
1.0.0


2. 開啟 gnome-terminal

$ tclsh

% append ::env(LD_LIBRARY_PATH) "/home/mysql_path/lib/"
/home/mysql_path/lib/

% puts $::env(LD_LIBRARY_PATH)
/home/mysql_path/lib/

% package require tdbc::mysql
couldn't load file "libmysql.so.15": libmysql.so.15: cannot open shared object file: No such file or directory


請問 第2個 使用 Tcl Shell 設定 lib path 少了什麼設定呢??

Jarry_Chang | 2013年4月24日 下午4:08

大大,我發現

在 bash shell 設定 lib path
的 env 的
LD_LIBRARY_PATH=/home/mysql_path/lib
位置在
_=/usr/local/bin/tclsh
之前



在 tcls shell 設定 lib path
的 env 的
LD_LIBRARY_PATH=/home/mysql_path/lib
位置在
_=/usr/local/bin/tclsh
之後

不知道是不是這個原因造成
couldn't load file "libmysql.so.15": libmysql.so.15: cannot open shared object file: No such file or directory

dai | 2013年4月24日 下午8:31

哇~我有一陣子沒有在用linux了...抱歉,幫不上忙了!!

Jarry_Chang | 2013年4月26日 上午10:06

大大,我在 Ubuntu 使用 Tcl Thread

TK GUI 按下 Button 會開啟一個 Thread 運行 socket deamon 接收字串 , 接收到五次 Ping 192.168.100.1 到 192.168.100.25, 運行 Thread (開啟五個 Ping 網段的 Thread)

在 Ping 完之後出現

Error in my_thread_global_end(): 1 threads didn't exit

請問如何找出這個沒有結束的 thread ,並且如何結束呢??

dai | 2013年4月26日 下午12:46

感覺問題不是出在thread沒有結束
感覺問題是出在thread執行命令或是執行外部命令時發生錯誤或有出錯的訊息
也許可以先查看看thread執行的內容有沒有錯

匿名 | 2013年7月11日 上午10:41

你好 DAI大大
以下是我程式碼
exec cmd /c start cmd /k "XXX.bat"
我用CMD去呼叫TCL 然後想讓程式碼去執行BAT檔 BAT檔執行完會自動關閉

但是奇怪的是 我單一執行BAT檔會自動關閉

但我用TCL去用 它變不會自動關閉 要如何改寫呢??

匿名 | 2013年9月17日 下午12:28

您好:
請問如何將數據轉換成 int32 binary ?
例如 1366874411 >>5178D92B

dai | 2013年9月17日 下午8:36

用binary format 即可,參考: http://www.tcl.tk/man/tcl8.6/TclCmd/binary.htm

JIN | 2013年9月17日 下午11:01

感謝,問題已經解決。請您幫我看一下我的code,這方式讀寫大型檔案(80mb)以上很慢。請問哪裡可以補強加速讀檔案嘛 ?

set srcFd [open Platform.txt r]
set destFd [open Heave.srh w]
fconfigure $destFd -translation binary -encoding binary

while {![eof $srcFd]} {
gets $srcFd buf
if {[string trim $buf] == ""} {continue}
lassign $buf YY MM DD hh mm ss heave
set ssint [expr int($ss)]
set heaveint [expr int(-$heave*100)]
set k1 "$YY/$MM/$DD $hh:$mm:$ssint"
#取尾數4位
set s2 [expr (round($ss*10000))%10000]
set s2 [string range 00$s2 end-4 end]
#轉string to number
set s2int [scan $s2 "%d"]
set s3 [ clock scan $k1 -format "%Y/%m/%d %H:%M:%S"]
#UTC +8 and 16sec (2013)
set utc [ expr $s3+28816]
set header [binary format H8 0D00AA52 ]
set status [binary format H6 000000 ]
set YMDHMS [binary format I1 $utc]
set SSSS [binary format S1 $s2int]
set pheave [binary format S1 $heaveint]
set total $header$YMDHMS$SSSS$pheave$status
puts -nonewline $destFd $total
}

close $destFd
close $srcFd

dai | 2013年9月18日 下午12:53

1.取尾數4位
puts [format "%04d" 37]

2.字串和數值很多時候在tcl裡是沒有區別的,必要的時候變數的型別會自動轉換
set v1 "123"
set v2 333
puts [expr $v1+$v2] => 456

考慮下面的兩行:
set ssint [expr int($ss)] <= ss是秒數? 會有小數點?
set s2int [scan $s2 "%d"] <= s2會有小數點?

3.若要強制某個變數讓tcl內部用值數的型態儲存可以這樣

set v3 [expr $v1]

4.若非常在意效率,可以用C來實作這一部份的處理,Tcl可以很容易呼叫用C實作的函數

匿名 | 2013年12月4日 下午3:39

Hi Dai大:
謝謝您熱心的教學,讓我在學習TCL有很大的用處, 感謝!
我想針對 11.5 字元編碼,請教您一個問題,
我需要將檔案強制轉換為ascii,
依據你的範例,我很順利的將UTF8轉成ascii,
但因為檔案裡有使用到ascii 161(超過亞洲國家的ascii 0~127的定義),
所以檔案轉成ascii後,超出ascii 127的部份,都變成?,
想請問有其他方法可解 超過ascii 127的部份嗎?
感謝您^^

dai | 2013年12月4日 下午10:47

抱歉!! 這個功能我也沒有用過~

匿名 | 2013年12月6日 上午10:36

您好,想請問一下,如果想提高TCL一次可開啟的上限,可於AOL Sever哪裡調整呢?

匿名 | 2013年12月6日 上午11:02

不好意思,沒寫清楚,我想提高的是TCL一次可開啟檔案的上限^^"

dai | 2013年12月6日 下午2:20

沒記錯的話,欠個限制應該是在作業系統上,或是c-runtime library裡。
Linux 可以用 ulimit -n [number] 取得/設定 目前的限制,windows下就沒用過了。

匿名 | 2013年12月10日 上午11:45

不好意思,想再請教一下
在Linux下直接執行程式,是可以依ulimit -n 2000 開啟2000個檔案,但使用網址方式呼叫程式,仍無法開啟2000個檔案
這會是需要調整哪部分的設定嗎?
目前我的主機OS為Linux,TCL是掛於AOL Server,可使用網頁(網址)呼叫TCL程式呈現。

dai | 2013年12月10日 下午1:52

抱歉,這個我沒用過,我也不知道~

kk | 2013年12月20日 上午8:38

Dear Dai,

我想請教如果我要在serial port下如:
expect "#"
send_expect $serial " set testfile "" "
因中間還有多了"",所以一直都無法執行,是否有更好的寫法?謝謝

dai | 2013年12月20日 下午4:32

改這樣:

send_expect $serial {set testfile ""}

徐瑞賢 | 2014年5月29日 下午3:59

hi Dai,

請問我在dos 下, 使用exp_spawn telnet 10.1.1.2 23, 操作過程訊息會顯示在dos , 可以關掉它嗎? 只留下我要印的東西 puts "message 1" ....

謝謝

Ryan ChangChien | 2014年8月19日 下午8:17

Hi Dai,

您好, 我有個問題想要請問.

我想要在tcl中call sed來幫我做事情, 所以我執行了 exec /bin/sed {s/aaa/bbb/g} ccc.txt 這樣看起來是可以work的

但是我想要更進一步的把aaa bbb換成variable, 可是這似乎會被{ } block住導致傳不進去 , 請問這樣有辦法避過嗎? Thanks

dai | 2014年8月19日 下午10:57

試試 exec /bin/sed "s/$a/$b/g" ccc.txt

cheng wang | 2014年8月22日 下午8:50

Hi Dai,
您好,誠摯向您請教一個問題:
我的一個tcl程序由於要調用別的程序在後台進行大量運算,所以導致tcl主程序頻繁被凍結。因此,我想到用Thread包來進行多線程運行,這樣主程序就不至於被凍結。但是在導入Thread后,tcl主程序下新建的thread里,只要使用puts命令,解釋器就會報錯:can not find channel named "stdout" 。我找了許多資料,可能的原因是,thread::create新建的線程所用的解釋器是“安全”解釋器,在這種“安全”解釋器里很多類似exec、open、puts等命令都被拒絕執行。但是這種解釋我也找不到解決的辦法。不知道您能給我一點建議嗎?謝謝。

問題實例:我的運行環境是tclkit-8.5.9-win32.upx.exe
package require Thread

set t [thread::create]
thread::send $t {puts OK}

==>Error:can not find channel named "stdout"

以下是外國網站上的實例,我運行后雖然沒有錯誤,但是thread里的puts輸出結果都不顯示。
package require Thread

puts "*** I'm thread [thread::id]"

# Create 3 threads

for {set thread 1} {$thread <= 3} {incr thread} {
set id [thread::create {

# Print a hello message 3 times, waiting
# a random amount of time between messages

for {set i 1} {$i <= 3} {incr i} {
after [expr { int(500*rand()) }]
puts "Thread [thread::id] says hello" #---->這裡有puts,但運行結果里沒有顯示。
}

}] ;# thread::create

puts "*** Started thread $id"
} ;# for

puts "*** Existing threads: [thread::names]"

# Wait until all other threads are finished

while {[llength [thread::names]] > 1} {
after 500
}

puts "*** That's all, folks!"


期待您的回音。再次感謝!

dai | 2014年8月23日 上午1:24

我比較常用的方法像這樣;


package require Thread

tsv::set share mainTid [thread::id]

set tid [thread::create -joinable {
set mainTid [tsv::get share mainTid]

thread::send -async $mainTid [list puts "Hello"]
}]

set ::bye 0
after 3000 [list set ::bye 1]
vwait ::bye

thread::join $tid

puts "bye~~~"

cheng wang | 2014年8月24日 下午8:02

Hi Dai,
在您的啟發下,我成功解決了遇到的問題。謝謝!
您的教學文件也給予了我學習tcl的極大幫助,再次感謝!祝部落格越辦越好,我會常來學習的。^_^

匿名 | 2014年10月20日 下午5:58

Hi Dai~~
請問一下open fileName access中的access如果不指定模式下,預設是以何種模式再進行呢??
如:
set fd [open ./test.rxt]

dai | 2014年10月20日 下午6:40

預設的話是 r 只能讀取檔案的內容

匿名 | 2014年10月21日 上午10:27

Hi Dai~~

我了解了~~
初學Tcl/Tk 還真的是會有不少疑問~~
非常感謝你~~

匿名 | 2014年10月31日 下午2:22

有一個情況不知道該怎麼處理比較好

假設我今天必須根據我讀入的檔案內容來決定我會寫出幾個檔案

我原本的想法如下

###定義寫出檔案
set foutFile "abc bbb kkk"
foreach run $foutFile {
set fout_$run [open group_${run}.rpt w]
}

###編輯寫出檔案內容
foreach run $foutFile {
puts $fout_${run} "test !!!!"
}

###關閉寫出檔案的channel
foreach run $foutFile {
close $fout_${run}
}

##############################
我會在第二部分時就會跳錯誤, 因為我的變數定義不清楚 tclsh找不到變數內容

匿名 | 2014年10月31日 下午2:32

Hi Dai,

不好意思想請教一下
有一個情況不知道該怎麼處理比較好
就是假設我今天必須根據我讀入的檔案內容來決定我會寫出幾個檔案


我原本的想法如下
###定義寫出檔案
set foutFile "abc bbb kkk"
foreach run $foutFile {
set fout_$run [open group_${run}.rpt w]
}

###編輯寫出檔案內容
foreach run $foutFile {
puts $fout_${run} "test !!!!" ---> can't read "fout_$run" : no such variable
}

###關閉寫出檔案的channel
foreach run $foutFile {
close $fout_${run}
}

##############################
我會在第二部分時就會跳錯誤, 因為我的變數定義不清楚 tclsh找不到變數內容
這種情況動態變數的定義我該怎麼定義才是正確的

再麻煩解惑~~先謝謝了!!!

dai | 2014年10月31日 下午11:38

用陣列即可
###定義寫出檔案
set foutFile "abc bbb kkk"
array set fout [list]

foreach run $foutFile {
set fout($run) [open group_${run}.rpt w]
}

###編輯寫出檔案內容
foreach run $foutFile {
puts $fout($run) "test !!!!"
}

###關閉寫出檔案的channel
foreach run $foutFile {
close $fout($run)
}

匿名 | 2014年11月1日 下午6:16

非常感謝!!!

謝宗軒 | 2014年11月4日 上午1:06

請教版主
TCL 有辦法執行檔案嗎
比如說 使用終端機 呼叫一個wish 視窗介面
按下介面中一按鈕 終端機會再次執行力另一個wish 視窗
甚至 按下一按鈕 終端機能去執行C語言的檔案
麻煩版主了 !!!!!

dai | 2014年11月4日 下午5:15

下面的程式是執行 ipconfig 的例子

1.把下面的程式存在 c:/a.tcl

console show
update

button .btn -text "ipconfig" -command {puts [exec ipconfig]}
pack .btn -expand 1 -fill both

2. 在命令提示字元下輸入

wish c:/a.tcl

謝宗軒 | 2014年11月4日 下午6:40

感謝版主 利用console
就能在視窗介面中 再開啟另一視窗介面
但對於呼叫其他檔案 還是不太清楚
用TCL 能否控制終端機呢

版主範例是按下按鈕 終端機會執行 ipconfig
然後在console上出現 我的理解是這樣
因為我剛剛執行 他跳出錯誤 "no option ipconfg "

我想請問的是 我能不能在一視窗介面中
按下一按鈕 command為終端機執行一C語言
並將結果傳到 console 這是可行的嗎 ??

dai | 2014年11月5日 下午10:18

看這個訊息 "no option ipconfg " ,有很大的機會是你程式寫錯,像ipconfg 就少了一個 i

exec 可以執行,大部份的視窗程式及終端機下的程式,不管是不是C語言寫的都可以執行 ~

匿名 | 2014年12月25日 上午10:30

Dai你好,我是TCL新手。
最近想透過TCL 控制RS232 console port 連到SWITCH做讀寫。
但是參考了很多網站都沒進展,看到你這有相關的經驗不知道可否執導一下。

謝謝!!

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

版主你好, 我參考了上面的範例可以看到 Switch console,但是無法互動。
請問有何需要修改的地方, r+不是可以讀寫嗎??
下面是我參考的code

#open serial port
set serial [open COM3: r+]
fconfigure $serial -blocking 0 -mode 9600,n,8,1 -translation binary -translation cr -buffering line

set fd [open b w+]

# make a text window for incoming communication
text .t -width 100 -height 20
pack .t -expand y -fill both
fileevent $serial readable {.t insert end [read $serial] ;.t see end}

puts $serial "show ip int"
gets $serial line
puts $serial $line
close $fd

dai | 2014年12月25日 下午6:39

改這樣試試
puts $serial "show ip int"
flush $serial
after 100
gets $serial line
puts $serial $line
flush $serial
close $fd

匿名 | 2014年12月26日 上午10:18

我最近也想用TCL呼叫Tera Term並執行Expect。
我遇到的問題是可以呼叫Tera Term但無法控制Tera Term Console,如send/expect ...........

package require Tcl 8
# package require telnet
package require Expect

set username admin
set password admin

exec {C:\Program Files (x86)\teraterm\ttermpro.exe} &
expect "Switch{send "enable\r"}
expect "Switch#" {send "configure terminal\r"}
expect "Switch(config)#" {send "show ip interface"\r}
expect eof
exit

匿名 | 2014年12月29日 上午11:08

版主好,

我發現Tera Term好像不行跟TCL互動,所以我用了plink。
plink在windows command promt下可以執行,但在TCL下"spawn plink -serial com4"就有問題。
plink我也在windows設為DEP除外但還是不行,可以執導一下嗎 謝謝。

dai | 2014年12月29日 下午1:06

不好意思,這些功能我也沒用過 ~ 要看有沒有熱心的網友幫忙了

匿名 | 2014年12月29日 下午4:55

版主好,

我發現加了/r就OK了,console可能一直等待我下一步。
謝謝你的時間!!

匿名 | 2015年2月10日 上午10:44

版主你好
我想開啟一個檔案,處理後再另存不同副檔名
比如 我開啟 abc.123 處理後直接存為 abc.456
開啟檔案是用 set a [tk_getOpenFile]
請問要如何處理 $a

Eiyu | 2015年3月10日 上午11:41

Hi Dai 你好

我最近工作需要寫個測試程式透過serial port 去android box boot mode下指令
所以在那box一開機就要趕快下ESC 讓他進入boot mode.
以往透過tcl 連serial port都非常順利. 但是android box 一開機就非常快跳到正常開機去
所以我用fileevent 去read channel 都得到error :
error reading "file18448e8": I/O error
error reading "file18448e8": I/O error
while executing
"gets $pipe line"
(procedure "Reader" line 4)
invoked from within
"Reader file18448e8"
麻煩Dai大幫忙看看如何解決. 快要被搞瘋了
謝謝
ps. fconfigure我有嘗試設-sysbuffer {32768 32768} 但沒用 一樣的error
設-lasterror 又跟我說我下錯command

我的code 這樣寫
set bootup 0
set fh [open COM1: RDWR]
fconfigure $fh -blocking 0 -mode 115200,n,8,1 -buffering none
fileevent $fh readable "Reader $fh"
vwait bootup

proc Reader {pipe} {
global bootup
if {[gets $pipe line] > 0} {
puts "LINE>>>>$line<<<"
# 一些開機看到的 隨便抓幾個關鍵字
if {[regexp {U-Boot} $line match] || [regexp {get_bootparam} $line match] } {
puts $pipe "\033"
flush $pipe
} elseif {[regexp "Boot>" $line]} {
set bootup 1
}
}
}

Eiyu | 2015年3月10日 下午2:08

Hi Dai

我剛剛終於看到-lasterror 回傳的ERROR CODE了 是FRAME
可是 我不知道要怎麼改?

謝謝你麻煩幫我看一下

Eiyu | 2015年3月10日 下午3:04

Hi Dai,
我用 read $channel 1 一個一個去讀
會出現下列 message
2015-03-10 14:53:20 0 error: error reading "file18448e8": I/O error
2015-03-10 14:53:20 0 received: ===> (後面出現一個符號亂碼 我copy 不下來)
2015-03-10 14:53:20 com-err: FRAME BREAK
2015-03-10 14:53:20 1 received:
2015-03-10 14:53:20 1 received: ?
2015-03-10 14:53:20 3 received: h
2015-03-10 14:53:20 4 received: b
2015-03-10 14:53:20 5 received:

2015-03-10 14:53:20 5 received:
....

陳宗聖 | 2015年3月12日 下午6:33

作者已經移除這則留言。

陳宗聖 | 2015年3月12日 下午6:35

作者已經移除這則留言。

Unknown | 2016年12月9日 上午2:04

請問家如我要讀取一個txt檔的數值
然後傳出給arduino這有辦法做到嗎

hark ku | 2017年7月13日 上午10:00

請問,我存檔時,檔名用中文,為何會變亂碼?
有解嗎?感謝

留下您的意見

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