文本文件处理示例
在文件开头输出内容
while {[getline line]} {
when {$FNR==1} {
puts "我在文件开头"
}
}
在文件末尾添加内容
while {[getline line]} {
when {$FNR==1} {
puts "我在文件开头"
}
}
puts "我在文件末尾"
上面这种方式把文件末尾的处理放到了循环体外面。和文件开头的判断形式上不对称。
可以通过引入“行号小于 0 时表示到了文件末尾”这个概念来写成下面这样。
while {[getline line]} {
when {$FNR==1} {
puts "我在文件开头"
}
when {$FNR<0} {
puts "我在文件末尾"
}
}
匹配提取文本行
# 准备一个盒子
set lines_buffer []
while {[getline line]} {
when [string match "header-to-sort: *" $line] {
# 收集到盒子里
lappend lines_buffer $line
} [regexp {^header-to-uniq: *} $line] {
# 收集到盒子里
lappend lines_buffer $line
} {string eq $line ""} {
# 输出之前收集的内容
puts $lines_buffer
}
}
string match
- 通配符匹配regexp
- 正则表达式匹配string equal
- 判断相等eq
- 用操作符判断
神奇盒子:排序
puts [lsort $lines_buffer]
神奇盒子:排序 + 去除重复
puts [lsort -uniq $lines_buffer]
神奇盒子:去除重复 + 维持原序
只保留重复行的第一行
proc uniq-keep-first {lines_list}
set lines_result []
set lines_lut {}
foreach line $lines_list {
if [dict exist $lines_lut $line] continue
dict set lines_lut $line
lappend lines_result $line
}
return $lines_result
}
puts [uniq-keep-first $lines_buffer]
只保留重复行的最后一行
proc uniq-keep-last {lines_list}
set lines_lut {}
foreach line $lines_list {
dict set lines_lut $line
}
return [dict keys $lines_lut]
}
puts [uniq-keep-first $lines_buffer]
匹配提取一个区间
# 准备一个盒子
set lines_buffer []
while {[getline line]} {
when {$line eq "LIST BEGIN"} {
# 区间开始,做个标记
set do_collect 1
}
when {$line eq "LIST END"} {
# 区间结束,取消标记
set do_collect 0
# 拿盒子里的东西干点什么
puts $lines_buffer
}
if {$do_collect} {
# 收集到盒子里
lappend lines_buffer $line
}
}
Tcl 程序语言可以把复杂的逻辑进行抽象和封装,从而让表达更简洁。
while {[getline line]} {
collect-text-block $line "LIST BEGIN" "LIST END" {
# 区间结束,完成收集,做些什么
puts $lines_buffer
}
}
其中 collect-text-block
的实现留待后续。
输出到外部文件
while {[getline line]} {
collect-text-block $line "LIST BEGIN" "LIST END" {
# 区间结束,完成收集,做些什么
wirte-file output.txt $lines_buffer
}
}
多个匹配区间的输出
set output-file-seq 0
while {[getline line]} {
collect-text-block $line "LIST BEGIN" "LIST END" {
set outfile [format "output-%d.txt" [incr output-file-seq]]
wirte-file $outfile $lines_buffer
}
}
匹配模式
Tcl 里常用的匹配模式有三种。这点可以参考 lsearch
命令的说明
-exact
: 完全一样-glob
: 通配符匹配-regexp
: 正则表达式匹配
因此,比如我们要提取一个由数字开头、以空行结尾的区间,比如先这样
0002 ... # 区块开始
...
...
;# 区块结束
0003 ... ;# 新的区块开始
...
...
;# 区块结束
只要稍微改动一下 collect-block-text
的调用如下即可。
while {[getline line]} {
collect-text-block $line -regexp {^00\d+} {^\s*$} {
# 区间结束,完成收集,做些什么
wirte-file output.txt $lines_buffer
}
}
修改区块内容
如果需要区块里的内容做一定修改,可以借助 list 相关的命令。
while {[getline line]} {
collect-text-block $line -regexp {^00\d+} {^\s*$} {
# 区间结束,完成收集,做些什么
set lines_buffer [linsert $lines_buffer 1 "new line"]
set lines_buffer [lreplace $lines_buffer end "new line"]
wirte-file output.txt $lines_buffer
}
}