#

強大的字串處理能力常常是直譯式程式語言的指標性特色,例如,Perl、Python、Ruby都是如此,當然Tcl也不例外。Tcl提供了兩種方法讓你處理字串,一種是普通的字串處理方法,另一種則是以正規表示式為基礎的字串處理。這一篇文章著重在前者的說明,至於後者的說明我打算讓它出現在進階篇的內容。

9.1 字串串接

字串串接是很常使用的功能,例如你有兩個字串然後想要把它們組合成一個更大的字串,這時就會用到串接的功能。append命令供了這樣的功能,它可以把多個字串逐一串接在指定的變數後面,append的語法如下:

append varName ?string1 string2 .... stringN?

append命令把參數string1~stringN串接在varName指定的變數尾巴,然後回傳串接後的結果。varName必需是變數的名字,如果該變數不存在append會自動建立它。 程式範例:



程式輸出:

Hello Tcl
Hello Tcl/Tk!

9.2 計算字串長度

Tcl的string命令包含了一整套的字串處理功能,例如:計算長度、字串搜尋、字串取代、取出字串片段....等。接下來就先以計算字串長度開始說明string命令的用法。

Tcl可以用兩種計算字串的長度,以下是這兩種方法的說明:

□ 計算位元組數

string bytelength string1

若string命令的第一個參數為字串bytelength,string命令會回傳string1所佔用的位元組數,string1即為要計算的目標字串。注意!!在這一章的內容string1會一直出現,如果沒有特別說明,這個參數都表示為要操作的目標字串。

□ 計算字元數

string length string1

若string命令的第一個參數為字串length,string命令會回傳string1的字元數。例如:



程式輸出:

6
9
6
3

為了方便說明,接下來的內容我會把string及它的第一個參數念做一起。例如,如果我說string length,實際上是指string命令後面的第一個參數是length。

§ 中文字的位元組數

在我的執行環境裡,系統預設的編碼是UTF-8,所以一個中文字佔用了3個byte。

§ 關於string命令

為了做到一個命令實現多種功能,string命令使用了第1個參數的值來決定真正該做的事,例如第一個參數是length就表示要算字串的長度、若是index就表示要取字串中的單字。像這種一個命令多種功能的做法被普遍的使用在Tcl裡面,其目的是希望讓同一性質的命令有統一的操作方法,像這樣的命令使用方法以後我們會常常看到,請大家慢慢習慣它。

9.3 截取字串內容

截取字串是撰寫解析器(Parser)常用的功能,例如,解析網頁的HTML找出所有的超連結標籤,或是解析網路協定封包取出特定欄位值,都會使用到這個功能。一般來說常用的字串截取需求有兩個,第一個是要可以一次取出一個字元,另一個是要可以一次取一個範圍的字串子集合,當然這兩個功能string命令都有提供。

□ 截取單一字元

string index string1 charIndex

string index回傳string1中索引位置為charIndex的字元。charIndex必需為零以上的整數或是end。end可以用來表示字串中最後一個字元的索引值。

□ 截取子字串

string range string1 first last

string range回傳string1中索引位置first到last間的子字串,而且包含first及last本身。其中first及last必需為零以上的整數或是end。end可以用來表示字串中最後一個字元的索引值。

string命令有許多功能都會使用到索引位置。接下來的內容如果沒有特別說明,就表示它們都是套用以下的規則:

  1. 索引位置即必需為零以上的整數。
  2. 索引位置可以使用end表示字串中最後一個字的索引位置。

以下是string index及string range的使用範例:



程式輸出:

1
家
bc12
Hi!

9.4 字串轉換

字串轉換包含英文字母大小寫轉換、剔除字元及字串取代等功能。這些功能時常會出現在搜尋文件或是格式化文字內容的程式裡。string當然也有提供這些功能,使用它們就可以輕鬆的應對各種字串轉換的狀況。

□ 轉換大小寫

string tolower string1 ?first? ?last?

string tolower把string1中索引位置first到last間的大寫英文字母轉換為小寫,然後回傳新的字串,而且包含first及last本身。若first及last沒有指定就把整個字串轉換為小寫字母。

string toupper string1 ?first? ?last?

toupper的使用方法如同tolower一樣,只是改成把string1中的小寫字母轉換為大寫。

string totitle string1 ?first? ?last?

totitle的使用方法如同tolower一樣,只是改成把string1中的第一個字母轉為大寫其它則轉為小寫。

轉換大小寫的範例如下:



程式輸出:

abcabc
ABCABC
Hello!! tcl/tk
英文字母HELLO夾中文字一樣沒問題

□ 剔除字串

如果你想要剔除字串前後某些不想要的字元,例如:空白。string命令也提供了數個功能,讓你可以處理這些工作,它們的語法如下:

string trim string1 ?chars?

string trim把string1左邊及右邊有包含在chars中的字元刪除,然後回傳新的字串。若chars不指定預設是刪除Unicode的空白字元 ex. \t \r \n \s。

string trimleft string1 ?chars?

如同trim只是改成刪除string1左邊含有chars的字元。

string trimright string1 ?chars?

如同trim只是改成刪除string1右邊含有chars的字元。

剔除前後字串的程式範例:



程式輸出:

abc
abc  
   abc
文字abc 123 中中中文文文字字字
字abc 123 中中中文文文字字字
abc 123 

雖然上方的第1及第2行輸出雖然看起來一樣,但事實上第2行輸出的後面的空白並沒有被刪除。

□ 字串取代

字串取代算是文字編輯器裡必備的功能之一,例如:Windows的記事本就有提供取代功能。一般來說取代時常配合搜尋來使用,常見的動作是:先透過搜尋找到想要被取代的位置,然後再用取代的功能來把原來的字串換掉。字串取代真的滿重要的,如果不會的話,像文字編輯器一類的程式就寫不出來了。以下是關於字串取代的語法。

string map ?-nocase? mapping string1

string map會把mapping當成是對照表去替換string1的內容,然後回傳替換後的新字串。若指定-nocase表示要把英文字母的大小寫視為一樣。mapping必需是一個清單而且項目的總數量一定要是偶數倍。如此一來string命令會把mapping中單數的項目當成是要被換掉的字串,雙數項目當成是要替換的結果。範示如下:



程式輸出:

Apple大家吃,好書大家看
蘋果大家吃,好書大家看

接下來是一個比較簡單的取代功能,它可以把字串裡特定範圍的內容換成新的內容,事實上我比較愛用string map。

string replace string1 first last ?newstring?

string replace把string1索引位置first到last間的子字串替換為newstring,然後回傳新的字串,而且包含first及last本身。若沒有給予newstring預設取代為空字串。

程式範例:



程式輸出:

前面8個字不見了!!

□ 字串重製

有時候我們會想要讓某些字串重複N次,在Tcl裡可以用下面的方法。

string repeat string1 count
string repeat命令把string1的值重覆count次,然後回傳新的字串,count必需為零以上的整數。

程式範例:



程式輸出:

tcltcltcltcltcl

□ 字串轉清單

最後要介紹一個很常使用的字串轉換功能,它可以利用字串裡的某些字元來把字串切割成清單的項目,簡單的說這是一種字串轉換為清單的功能。

split string1 ?splitChars?

split命令以splitChars裡的字元當項目分隔來切割string1,然後以清單的方式回傳切割後的結果。若不指定splitChars預設會以所以的Unicode空白字元來做分隔字元。

程式範例:



程式輸出:

www google com tw

9.5 字串搜尋

字串搜尋時常會伴隨著字串截取使用。常見的程式動作是:先透過搜尋由較大的字串找出符合的字串片段,然後再透過字串截取來得到真正想要的內容。string命令對字串搜尋主要提供了4個功能,除了常見的「找第一個」及「找最後一個」之外,也提供了「找單字開頭」及「找單字結尾」的功能。

□ 找第一個及最後一個

string first string1 string2 ?startIndex?

string first由索引位置startIndex的位置開始尋找string1出現在string2的位置,若找到的話回傳第一次找到的開始索引位置,否則回傳-1。若startIndex沒有指定,預設會從頭開始找。

string last string1 string2 ?lastIndex?

如同string first的用法,只是改成找最後一個符合的位置。string會由索引位置lastIndex開始往前找。若lastIndex沒有指定,預設會從最後一個字往前找。

程式範例:



程式輸出:

2
12
12
2

□ 尋找單字的開頭及結尾

這是其它程式語言比較少見的功能,很幸運的Tcl提供了。透過下面的命令,你可以很方便的找出字串裡某個英文單字的開頭或結尾:

string wordend string1 charIndex

string wordend會由索引位置charIndex開始尋找單字的結尾,若找到的話回傳第一次找到的索引位置,否則回傳0。

string wordstart string charIndex

如同string wordend的用法,只是改為找單字的開頭。

程式範例:



程式輸出:

-powerfully -
中文行得通?

程式的第4行特別在取出字串範圍的前後加上一根短線,以方便觀查。另外,在第1行的輸出裡,其實wordend找到的位置包含了空白字元,也就是單字結尾的下一個字元。

9.6 字串比對

字串比對常常發生在if或迴圈的條件式裡,在條件式裡非常直覺的會讓人想用 「==」及「!=」這兩個運算方法,事實上這兩個運算方法在某些情況下會有一些問題。接下來的字串比對方法可以避開這些問題,也適用於較複雜的比對情況。

□ 字串比對語法

string compare ?-nocase? ?-length len? string1 string2

string compare以unicode字碼的方式逐一比較string1及string2包含的字元,如果string1大於string2回傳1,如果回傳0表示兩個字串相同,如果傳回-1表示string1小於string2。 若指定-nocase表示比對時不分大小寫。若指定-length參數的話,len用來決定要比對的長度,預設的比對長度是string1及string2中較短的字串。

string equal ?-nocase? ?length len? string1 string2
如同string compare的字法,只是改為如果string1完全等於string2回傳1,否則回傳0。

程式範例:



程式輸出:

-1
0
0
1

if命令在下面樣的情況下,可能會輸出不是你期望的結果,原來你是想要比較0x0a及10這兩個字串,但因為0x0a同樣也是合法的16進制數值表示法,所以0X0a就被當成是16進制的10,結果判斷式成立了。



§ string compare及==的差異

注意哦!!string compare是以unicode字碼的方式逐一比較,但==的運算符號會解釋字面的意義,再比較。例如,對==運算符號來說6、06及0x06的意義都是數值的6,所以上一個例子會成立,但若以string compare逐字碼比較,上一個例子就不會成立了。再次提醒,別忘了if是用和expr同樣的方式求值它的運算式。



□ 判斷字串類別

有時候你會想知道某個字串,到底屬於那一種類型的資料,整數、字母或標點符號? 這時候你可以使用下面的命令來判別字串的類型:

string is class ?-strict? ?-failindex varname? string1

string is逐一字元判斷string1是否符合class指定的類別。如果不指定-strict參數string1為空字串時會無條件回傳1,如果指定的話會回傳0。如果指定-failindex參數,varname可以用來指定一個變數名稱,當判斷類型不成立時,這個變數用來儲存第一個判斷失敗的位置,位置從0開始。以下是可以用來判斷的類別:

alnum 任何Unicode的英文字母和數字
alpha 任何Unicode英文字母
ascii 任何ascii碼127以下的符號
boolean 可以是1、0、true、false (不分大小寫)
control 任何Unicode的控制字元
digit 包含Unicode數字[0-9]
double Tcl認可的double數值 (跟作業系統平台有關)
false 0、false (不分大小寫)
graph 任何Unicode可視字元,不包含空白字元
integer Tcl認可的integer數值 (跟作業系統平台有關)
lower 任何Unicode小寫的英文字母
print 任何Unicode可視字元,包含空白字元
punct 任何Unicode的標點符號
space 任何Unicode的空白字元
true 1、true (不分大小寫)
upper 任何Unicode大寫英文字母
wordchar 任何Unicode英文字母、數字、連接字元 ex.底線
xdigit 任何16進制的數字[0-9A-Fa-f]

程式範例:



程式輸出:

1
0

□ 進階比對功能

接下來的string match提供了比較強大的比對功能,它可以讓你使用Unix glob-style的方式來比對字串是否符合某個pattern,它的說明如下:

string match ?-nocase? pattern string1

string match使用Unix glob-style的方式比對pattern及string1,若string1符合pattern就回傳1,否則回傳0。如果指定-nocase,表示比對字串時不分大小寫。pattern簡單的說是一些關鍵字及特殊比對字元組成的字串,以下是pattern字可用的特殊比對字元:

* 符合任意長度的字元
? 符合任意一個字元
[chars] 符合chars集合內的任一字元
\x 用來跳脫特殊字元 *?[]\
control 任何Unicode的控制字元
digit 包含Unicode數字[0-9]
double Tcl認可的double數值 (跟作業系統平台有關)
false 0、false (不分大小寫)
graph 任何Unicode可視字元,不包含空白字元
integer Tcl認可的integer數值 (跟作業系統平台有關)
lower 任何Unicode小寫的英文字母

程式範例:



特別注意!! 第2行的中括號請用反斜線來取消代換或改成用大括號包夾,否則會被當成子命令並執行代換的動作。

程式輸出:

1
1

66 個意見

Anonymous | 2009年7月29日 上午10:21

你好,經由google走進你的部落格,覺得你寫的TCL教學非常的好,讓人很快的對TCL/TK入手。在此有個問題想請教:
為何
set s "中文字abc 123 中中中文文文字字字"
puts [string trim $s "中"]

其結果是
文字abc 123 中中中文文文字字字

而不是
文字abc 123 文文文字字字

string trim的功能不是把string1左邊及右邊有包含在chars中的字元刪除嗎?

在此致上最高敬意

Dai | 2009年7月29日 下午4:45

因為string trim在執行時,是由字串的「最右邊」及「最左邊」開始執行去除的動作,但只要遇到第一個不是要去掉的字,它就會停止目前這一邊的去除動作。

sam | 2009年11月2日 上午10:26

大大,請問一下:
set s "中文字abc 123 中中中文文文字字字"
puts [string trim $s "中文字"]

輸出:abc 123
abc 123的右邊為何會刪除全部"中中中文文文字定字"的字串,右邊並沒有出現"中文字"這樣子的字串丫,感謝。

sam | 2009年11月2日 下午6:22

我大概了解trim的用法了,不能看成是剔除"中文字",是剔除在字串中有"中"或"文"或"字",這三個字只要有其中之一出現就剔除該字,trim的用法不像我直觀所想的那樣,3Q,自問自答,Dai你的教學網很棒…

josh | 2009年12月10日 晚上7:36

D大您好,您寫的tcl 教學很棒,很淺顯易懂,

但我被一個字串處理的小問題搞了好久,請教你一下

我現在要處理以下資料,這些資料是存在一個檔案裡,我現在要打

開這個檔案並且刪除不要的內容再存檔

範例如下:

r300::1300:vicc,test,liou
r400::1400:test,jackkuo,geneliou
r450::1450:jackkuo,geneliou,test
r500::1500:vicc,,hack

我現在已完成的步驟的流程

1.開檔 ok
2.找到我要刪除的字串 ok (如"test")
3.從每一行中刪除"test" not ok

我試了好久還是弄不出來,請您給我點建議.

dai | 2009年12月10日 晚上8:10

嗯~方法滿多的一個簡單的方法像這樣:

*假設你連「,」也要去掉

set fd [open /etc/group r]
set data [string map {"test," "" ",test"} [read $fd]]
close $fd

set fd [open /etc/group w]
puts $fd $data
close $fd

翊翾 | 2010年1月5日 下午4:06

Dai 你好
想請問一下字串比較的部份
我現在有一關鍵字例如ppy

set t1 Happy
set t2 New
regexp {(ppy)} $t1 match
#結果為1,因為有符合
regexp {(ppy)} $t2 match
#結果為0,因為不符合

但我把ppy存入變數
set key ppy
在比較的話就會產生錯誤
regexp {($key)} $t1 match
會讀不到key的值
如果加上中括號
regexp {([$key])} $t2 match
則只會拿k去做比較

我不知道要怎麼解決這問題
我現在再做字串搜尋的功能
麻煩Dai幫我解惑一下

dai | 2010年1月5日 下午4:29

是這樣子的,你可以把大括號換成雙引號,這樣子的話變數裡的值才會代換出來

regexp "($key)" $t2 match

關於變數代換的觀念你可以參考文件的第4章-命令模型。

翊翾 | 2010年1月6日 上午9:14

Dai你好^^
現在成功的做出我想要的功能囉
一個小地方的提醒,讓我得到很大的成就呢~

dai | 2010年1月6日 上午10:36

成功啦!! 恭喜~恭喜~

Peter | 2010年3月23日 下午6:50

Dai 您好:
請問一下,在字串取代中以你的範例來看的話
set tbl [list apple "蘋果" book "書"]
puts [string map $tbl "Apple大家吃,好book大家看"]
如果在tbl清單中,有許多變數,需要用到for迴圈寫
應該怎麼寫法呢? 剛嘗試一下寫不太出來

例如我有三個不同變數: test1 test2 test3
要分別都建在tbl裡面,而test1分別裡面又包含10個子變數,
應該怎麼寫這個程式?

dai | 2010年3月23日 晚上7:31

Hi Peter,

不好意思~我有點看不太懂問題,你要不要寫一個實例說明test1~3的內容,然後你想要把它們變成tbl裡的哪些項目? 單數項目? 偶數項目? 都寫下來,然後我再用程式去解這個問題.

例如:

set test1 [list a 1 b 2 c 3 d 4]
set test2 [list e 5 f 6 g 7]
set test3 [list h 8 i 9]

set tbl [list {*}$test1 {*}$test2 {*}$test3]
puts [string map $tbl "abcdefghi"]

輸出會是 : 123456789

Peter | 2010年3月24日 上午8:55

Dai 您好:
昨天已解決這個問題 !

匿名 | 2010年7月8日 上午9:34

請問一下:如果我輸入是abcde,輸出是要edcba,請問如何寫出反向輸出??

dai | 2010年7月8日 下午3:12

用string reverse就可以了,例如:

set v1 "abc"
puts [string reverse $v1]

這樣會輸出

cba

匿名 | 2010年12月30日 下午5:47

錯字
在9.2
"string令令" 應為 "string命令"

匿名 | 2010年12月30日 下午6:23

程式與輸出內容不同
在 字串取代
程式碼為:
001 set tbl [list apple "蘋果" book "書"]
002 puts [string map $tbl "Apple大家吃,好book大家看"]
003 puts [string map -nocase $tbl "Apple大家吃,好book大家看"]

輸出:
Apple大家吃,好書本大家看
蘋果大家吃,好書本大家看

錯誤處:
多了"本",程式碼中只轉換成"書"

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

謝謝指定已修正。

匿名 | 2011年3月23日 晚上7:13

在你的文章
===================
9.6 字串比對
字串比對算是比較少用的功能 <=這句話有點不好
===================
原因: (reference from "TCL Tutorial 基本語法與指令")

字串的比較儘量使用 string compare 或 string equal,例如:

if {[string compare $s1 $s2] == 0} {
# s1 and s2 are equal
}

if {[string equal $s1 $s2]} {
# s1 and s2 are equal
}

避免使用 == 來比較字串,雖然下列程式碼中,比較的兩個字串有不一樣的內容,仍會輸出 ack 的訊息,這是因為 TCL 會試圖將字串轉換為數字再進行比較,於是16進位的0xa會等於十進位的10,這可不是我們所期望的結果,因此建議儘量使用 string compare 或 string equal 來取代這種寫法:

if { “0xa” == “10”} { puts “ack” }

dai | 2011年3月27日 上午9:27

hi 匿名的朋友,

我遺漏這種狀況的考慮,謝謝指教~

匿名 | 2011年3月29日 下午5:49

希望你能將字串比對和"=="的差異提一下
也可以舉上面所提到的例子
讓你的文章更加完美
也是因為比較網友會看下面的討論
所以會忽略一些使用上的小地方
謝謝 ^^

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

Hi 匿名的朋友,

哈 ~ 不好意思!!

我想當兵讓我懶散了,立刻補上...。

匿名 | 2011年5月31日 下午4:26

Hi dai,

string first 跟 string last 可以找出第一個跟最後一個字串,
但若我想找出log中的第二個或第三個,要怎麼寫才對??
比如:
我的文件內容是:
2011/02/11
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/12
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/13
1.apple : 54 元
2.book: 120元
3.coke:20 元

以下是我寫的部份(可以找出最後一筆):
set log [send_exp_getbuf $::serial]
set Info [string range $log 0 end]
set result [string first " " $Info [string last "book:" $Info]]
set result [string range $Info $result [string first "\n" $Info $result]]

dai | 2011年5月31日 晚上7:11

Hi,

請試試下面的程式:

set info {
2011/02/11
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/12
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/13
1.apple : 54 元
2.book: 120元
3.coke:20 元
}
set idx1 [string first "book:" $info 0]
while {$idx1 >= 0} {
set idx2 [string first " " $info $idx1]
puts [string range $info $idx1 [expr $idx2-1]]
set idx1 [string first "book:" $info $idx2]
}

匿名 | 2011年6月1日 上午10:29

Hi Dai,
謝謝,現在可以了,但我若只要第二筆資料需要改什麼?
因現在是整個資料全抓,please help me...

dai | 2011年6月1日 中午12:01

嗯~方法很多,其中一種像這樣:

set info {
2011/02/11
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/12
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/13
1.apple : 54 元
2.book: 120元
3.coke:20 元
}
set idx1 [string first "book:" $info 0]
set idx1 [string first "book:" $info [incr idx1]]
set idx2 [string first " " $info $idx1]
puts [string range $info $idx1 [expr $idx2-1]]

匿名 | 2011年6月1日 下午4:05

Hi dai,
因為我的set info{內容},內容會隨著每次抓取不一樣,
上面的方法可以用成陣列來寫嗎?
這樣可以讀取如第四筆或其他每一筆,而不侷限是第幾筆,
可以這樣做嗎?

dai | 2011年6月1日 晚上7:53

hi,

你想要的功能Tcl都做得到,而且不困難~~

有問題可以隨時留言,一起討論 呵呵

匿名 | 2011年6月2日 上午9:34

Hi dai,
因為我的資料會一直抓,如果用以下範例:
set idx1 [string first "book:" $info 0]
set idx1 [string first "book:" $info [incr idx1]]
set idx2 [string first " " $info $idx1]
puts [string range $info $idx1 [expr $idx2-1]]
確實會抓第二筆,但是一開始的第二筆,
若以下資料是會抓最後一筆:
set log [send_exp_getbuf $::serial]
set Info [string range $log 0 end]
set result [string first " " $Info [string last "book:" $Info]]
set result [string range $Info $result [string first "\n" $Info $result]]
若我想要抓最後第二筆或最後第三筆,請問要怎麼改才對?要改哪裡?

匿名 | 2011年6月2日 上午11:28

Hi Dai,
因為我抓的資料會一直產生,如果用以下的會永遠抓到資料的第二筆
set idx1 [string first "book:" $info 0]
while {$idx1 >= 0} {
set idx2 [string first " " $info $idx1]
puts [string range $info $idx1 [expr $idx2-1]]
set idx1 [string first "book:" $info $idx2]
}
但如果我用以下的會抓到新的資料的最後一筆
set log [send_exp_getbuf $::serial]
set Info [string range $log 0 end]
set result [string first " " $Info [string last "book:" $Info]]
set result [string range $Info $result [string first "\n" $Info $result]]

現在的問題是我要怎麼改才會抓到新的資料的最後第二筆或最後第三筆?

匿名 | 2011年6月3日 上午11:26

看起來你的資料格式是以空白做分隔,結尾是換行。
1111 2 333 44 555 6 777 888 9 0000\n
假設你的資料為上述格式。

大概像以下這樣:
set Info "1111 2 333 44 555 6 777 888 9 0000\n"
set result [lindex [split $Info " "] end-2]
puts $result

以上,最後第二筆就end-1,最後第三筆就end-2,如果數量不夠的話result會為空字串。
不過以上的做法限制為字串間的空白只能有一個,如果有多個的話會抓錯。頭尾不能出現多餘的空白。
如果頭尾會出現多餘的空白的話可以先使用string trim先做處理,如下:
set Info "1111 2 333 44 555 6 777 888 9 0000\n"
set result [lindex [string trim [split $Info " "] " "] end-2]
puts $result

以上

匿名 | 2011年6月3日 上午11:50

不好意思 沒看上下文 XD
應該是這樣:
set Info {2011/02/11
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/12
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/13
1.apple : 54 元
2.book: 120元
3.coke:20 元
}
set index 1
set interval 4
set order 2
set result [lindex [split $Info "\n"] [expr ($index*$interval)+$order]]
puts $result
以上

匿名 | 2011年6月3日 下午6:55

Dear ,這個方法會有點問題說,若我的info是每次都不一樣會加其他的字元,
每次的位置不一定,是不是應該要過濾如取book後值??
set Info {2011/02/11
1.apple : 50 元
####--------
2.book: 100元
3.coke:15 元
2011/02/12####--------
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/13####--------
1.apple : 54 元
####--------
2.book: 120元
3.coke:20 元
}

匿名 | 2011年6月3日 晚上7:34

Dear Dai,
資料會一直抓,每次抓要怎麼改才會抓到新的資料的最後第二筆或最後第三筆?
thanks.

匿名 | 2011年6月3日 晚上7:44

Dear Dai,
可能是我的表達方式不好,我想要每次抓的"book後面的值",因為我會拿這些值去做其他比對,而每次資料會一大串,然後我在去過濾資料.
ps:且我想抓起每次的最後第二筆或第三筆

dai | 2011年6月7日 下午6:32

我用另一種方式 你試試看,我把最後3筆book找出來

set Info {2011/02/11
1.apple : 50 元
####--------
2.book: 100元
3.coke:15 元
2011/02/12####--------
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/13####--------
1.apple : 54 元
####--------
2.book: 120元
3.coke:20 元
}

set lines [lreverse [split $Info "\n"]]

set cut 0

foreach l $lines {
set idx1 [string first "book:" $l]
if {$idx1 < 0} {continue}
lassign $l b m
set books($cut) $m
if {[incr cut] &gt= 3} {break}
}

parray books

匿名 | 2011年6月8日 上午8:51

Hi Dai ,
有點狀況,執行後會顯示;
nvalid bareword "gt"
in expression "[incr cut] &gt = 3";
should be "$gt" or "{gt}" or "gt(...)" or ...
(parsing expression "[incr cut] &gt = 3")
invoked from within
"if {[incr cut] &gt = 3} {break}"
("foreach" body line 6)
invoked from within
"foreach l $lines {
set idx1 [string first "book:" $l]
if {$idx1 < 0} {continue}
lassign $l b m
set books($cut) $m
if {[incr cut] &gt = 3} {break}
}"

dai | 2011年6月8日 上午9:28

阿 抱歉~ 請把 &gt 換成 大於 的符號

匿名 | 2011年6月8日 下午4:31

set Info {2011/02/11
1.apple : 50 元
####--------
2.book: 100元
3.coke:15 元
2011/02/12####--------
1.apple : 50 元
2.book: 100元
3.coke:15 元
2011/02/13####--------
1.apple : 54 元
####--------
2.book: 120元
3.coke:20 元
}

set rule "book: "
for {set data ""; set index 0; set len 0} {[set index [string first $rule $Info $index]] != -1} {incr len} {
set index [expr $index+[string length $rule]]
lappend data [string range $Info $index [expr [string firs "\n" $Info $index]-1]]
}
puts "$data"
puts "共$len 筆資料."
puts "倒數第一筆:[lindex $data [expr $len-1]]"
puts "倒數第二筆:[lindex $data [expr $len-2]]"
puts "倒數第三筆:[lindex $data [expr $len-3]]"

可以自行選擇你要的資料,更改rule可以找其他條件的資料

匿名 | 2011年8月17日 上午10:53

HI Dai
你的教學讓我學到很多東西
我想請問一下
有沒有方法可以去掉一個字串裡
某兩個字元範圍的內容嗎
像是 apple123bananaorange
刪掉apple到banana範圍內容
最後想要得到orange的內容
謝謝

dai | 2011年8月17日 下午1:19

這樣的狀況我會用下面的程式:

puts [string rnage "apple123bananaorange" end-5 end]

匿名 | 2011年8月18日 上午8:40

如果不確定中間會有哪些字
只確定開頭跟結尾的字元呢
比如說我只確定apple和banana是固定的
但中間123是不確定的
一定要把整個字串都輸入嗎?
謝謝

dai | 2011年8月18日 下午1:09

嗯~ 上一個例子,不需要在意字串的內容,因為它固定是取右邊6個字

如果你需要更強的字串處理功能,Tcl也內建了regexp及regsub的命令

它們應該足夠應付大部分的字串處理

匿名 | 2013年2月5日 晚上7:00

我有個問題想請教
set a "M28SPLL1024X40R2011VTES35D121BSIRLQF"
set b "M28SPLL1024X46R2011VTES35D121BSIRLQF"
set c "M28SPLL1024X5R3011VTS38D121BSIRLQF"
我想從 a 找出 1024 , 40 , 2011 , 121
我想從 b 找出 1024 , 46 , 2011 , 121
我想從 c 找出 1024 , 5 , 3011 , 121
怎樣用一同樣一段程式分別帶入a , b, c 就可以得到結果?
謝謝.

dai | 2013年2月10日 下午5:54

嗯 ~ 方法很多..例如:

set a "M28SPLL1024X40R2011VTES35D121BSIRLQF"
set result [regexp -all -inline {[0-9]+} $a]
puts [concat [lrange $result 1 end-2] [lindex $result end]]

匿名 | 2013年3月21日 下午2:53

謝謝您的詳細解說 讓我能快速入手
另外我有個不請之求
是否能舉例下 string match pattern ? 符合任意一個字元 的用法嗎?
因為我執行 puts [string match "?t" "t"]
結果是false 是否多說點這pattern的意思

匿名 | 2013年3月21日 下午5:34

不好意思 再問一個問題
因為我有看到 {*} 是用來split list
set af "a 2"
set {*}$af
puts $a
為什麼輸出會是2

PS.TCL版本為8.6

dai | 2013年3月22日 下午4:59

? : 表示符合任意單一字元
* : 表示符合任意字元(0~N個字元)

string match ? t ==> 1

{*}的意義是expand不是split

在執行 set {*}$af 的時候,直譯器會把命令先代換為 :

set {*}"a 2"

然後再展開變成:

set a 2

最後的結果就是a被設定為2

匿名 | 2013年10月29日 下午4:44

網頁改錯:
普偏→普遍

puts [string tolower "abcABC"]

程式輸出:

abcabc

Unknown | 2013年11月6日 下午5:02

因為需要計算字串出現的次數,上網找了其他人的解法提供給大家。
set message "Hi; Hi; Tcl is good. Hi"
set num [regsub -all Hi $message {} junk]
--
num = 3

dai | 2013年11月6日 晚上10:07

謝謝分享 ~

dai | 2013年11月6日 晚上10:09

修正了 普偏→普遍 謝謝~

JIN | 2013年12月10日 下午4:39

板主您好:
請問有什麼方式可以把不間斷資料排列輸出?
我的資料格式如下:
25.05159774 121.57423254 25.05160454 121.57382632 25.05161836 121.57351003
希望排列成下面格式輸出:
25.05159774 121.57423254
25.05160454 121.57382632
25.05161836 121.57351003
期待您的建議

匿名 | 2013年12月10日 晚上10:00

大概像這樣:
set data "25.05159774 121.57423254 25.05160454 121.57382632 25.05161836 121.57351003"
for {set i 0} {$i < [llength $data]} {incr i 2} {
puts [lrange $data $i [expr $i + 1]]
}

Unknown | 2014年3月10日 上午10:10

版主您好,小弟近日在學習tcl語言
感謝您的文章讓我獲益良多
在此提供幾個我在文章中發現到的小錯誤
以期文章能更完美地呈現

1.totitle的文字描述部分打成totile
2.puts [string tolower "abcABC"]的output打成bcabc

如有冒犯或造成不便還請見諒

匿名 | 2014年3月10日 上午11:35

已經修正了,謝謝 ~

PPTT | 2014年6月27日 下午5:04

請問大大
如何將「中文字」取代成「*」呢?
英文或數字我都可以轉換了
就剩中文字

匿名 | 2014年6月28日 晚上7:57

如果不用正規表示式,大概是這樣的做法~

set str "中文及abcd"
set len [string length $str]
array set map [list]
for {set i 0} {$i < $len} {incr i} {
set c [string index $str $i]
if {$c ni [list 這邊是26個字母及數字組成的清單]} {array set map [list $c "*"]}
}
puts [string map [array get map] $str]

匿名 | 2016年1月20日 下午3:54

Hi! Dai你好
假設我的資料格式如下
A.A
12321 21231
221378 32213
B.B
439850 409385
123 34024
32233 44445
2132 655
C.C
209384 20939
1223 495
11 11

我要抓下面這段資料要怎麼做
B.B
439850 409385
123 34024

匿名 | 2016年1月20日 下午3:56

Hi! Dai你好
假設我的資料格式如下
A.A
12321 21231
221378 32213
B.B
439850 409385
123 34024
32233 44445
2132 655
C.C
209384 20939
1223 495
11 11

我要抓下面這段資料要怎麼做
B.B
439850 409385
123 34024
32233 44445
2132 655

匿名 | 2016年1月20日 晚上9:48

set data {
A.A
12321 21231
221378 32213
B.B
439850 409385
123 34024
32233 44445
2132 655
C.C
209384 20939
1223 495
11 11
}

set idx1 [string first "B.B" $data]
set idx2 [string first "C.C" $data $idx1]

puts [string range $data $idx1 [expr $idx2-1]]

Huang | 2016年1月21日 上午10:17

Hi! Dai你好
謝謝你的回覆,其實我想要寫一個key B.B 就會跑出
B.B
439850 409385
123 34024
32233 44445
2132 655
但是後面接的C.C必須要打開要被處理的檔案才會知道是不是
接的是C.C,也有可能是其他 ?.? 這種格式,
再麻煩請你幫忙指導一下~ Thanks!

Huang | 2016年1月22日 下午5:22

Hi! Dai你好!
根據你提供的方法,我已經寫好了。
只是現在遇到另一個問題,就是如果要處理的檔案太大,
就會abort,不知道有甚麼方法可以解,謝謝!

Tcl_SetObjLength: negative length requested: -2147483646 (integer overflow?)
Abort

這是我寫的
#!/usr/bin/tclsh
puts {ex: Please input "test.db out.db keep_drc"}
gets stdin drc
set infile [open [lindex $drc 0] "r"]
set outfile [open [lindex $drc 1] "w"]

set i 2

while {![eof $infile]} {
set data [read $infile]
set drcall [regexp -all -inline {\w+\.\w+\.\w+\.*\w*} $data]
}

while {$i < [llength $drc]} {
set input [lindex $drc $i]
set end1 [lsearch $drcall $input]
set end2 [lindex $drcall [expr $end1+2]]
set idx1 [string first $input $data]
set idx2 [string first "$end2" $data $idx1]
puts $outfile [string range $data $idx1 [expr $idx2-1]]
incr i
}

close $infile

匿名 | 2016年1月23日 凌晨1:05

由程式片段無法看出你的意圖,若只就片面來看,假設你輸入了 "test.db out.db keep_drc"
1. 兩個while的邏輯應該有問題,因為它們都只會執行一次,即是說就算沒有寫while也不影響執行原來的功能
2. test.db的檔案內容無法推測,也有可能是因為檔案的內容產生問題,ex. test.db是一個超大的檔案,你必需要分段讀取,不可以用read一次讀入所有的檔案內容

匿名 | 2018年1月25日 下午4:18

請教一下~~~
可以在一大堆文件裡面就只挑選出特定字串 並且計算一共有幾個嗎?
舉例-->挑選出GDS這個字串

Unknown | 2018年9月17日 下午4:25

# count 為特定字串的數量
set content "OOXXGDSOXOXOXAXOXOXOXGDSOXOXOXA";
set searchitem "GDS";
set count [regsub -all $searchitem $content "" ""];

留下您的意見

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