我正在尝试使用tcl根据某些关键字解析文件中的选择性数据,例如我有一个这样的文件
...
...
..
...
data_start
30 abc1 xyz
90 abc2 xyz
214 abc3 xyz
data_end
...
...
...
我如何只捕获" data_start"之间的30,90和214;和" data_end"?我到目前为止(tcl newbie),
proc get_data_value{ data_file } {
set lindex 0
set fp [open $data_file r]
set filecontent [read $fp]
while {[gets $filecontent line] >= 0} {
if { [string match "data_start" ]} {
#Capture only the first number?
#Use regex? or something else?
if { [string match "data_end" ] } {
break
} else {
##Do Nothing?
}
}
}
close $fp
}
答案 0 :(得分:2)
如果文件较小,则可以使用read
命令将整个数据压入变量,然后应用regexp
来提取所需信息。
<强> input.txt中强>
data_start
30 abc1 xyz
90 abc2 xyz
214 abc3 xyz
data_end
data_start
130 abc1 xyz
190 abc2 xyz
1214 abc3 xyz
data_end
<强> extractNumbers.tcl 强>
set fp [open input.txt r]
set data [read $fp]
close $fp
set result [regexp -inline -all {data_start.*?\n(\d+).*?\n(\d+).*?\n(\d+).*?data_end} $data]
foreach {whole_match number1 number2 number3} $result {
puts "$number1, $number2, $number3"
}
输出
30, 90, 214
130, 190, 1214
更新:
将较大的文件内容读入单个变量将导致程序崩溃,具体取决于PC的内存。当我尝试在Win7 8GB RAM笔记本电脑中使用read
命令读取大小为890MB的文件时,出现unable to realloc 531631112 bytes
错误消息并且tclsh
崩溃。经过一些基准测试后发现它能够读取大小为500,015,901字节的文件。但该程序将占用500MB内存,因为它必须保存数据。
此外,在通过regexp
提取信息时,拥有一个保存这么多数据的变量效率不高。因此,在这种情况下,最好继续逐行阅读内容。
详细了解此here。
答案 1 :(得分:1)
将文件中的所有数据加载到变量中。设置开始和结束标记并寻找那些位置。逐行处理项目。 Tcl使用由空格分隔的字符串列表,因此我们可以使用foreach {a b c} $ line {...}处理该行中的项目。
TCL:
set data {...
...
..
...
data_start
30 abc1 xyz
90 abc2 xyz
214 abc3 xyz
data_end
...
...
...}
set i 0
set start_str "data_start"
set start_len [string length $start_str]
set end_str "data_end"
set end_len [string length $end_str]
while {[set start [string first $start_str $data $i]] != -1} {
set start [expr $start + $start_len]
set end [string first $end_str $data $start]
set end [expr $end - 1]
set item [string range $data $start $end]
set lines [split $item "\n"]
foreach {line} $lines {
foreach {a b c} $line {
puts "a=$a, b=$b, c=$c"
}
}
set i [expr $end + $end_len]
}
输出:
a=30, b=abc1, c=xyz
a=90, b=abc2, c=xyz
a=214, b=abc3, c=xyz
答案 2 :(得分:1)
我把它写成
set fid [open $data_file]
set p 0
while {[gets $fid line] != -1} {
switch -regexp -- $line {
{^data_end} {set p 0}
{^data_start} {set p 1}
default {
if {$p && [regexp {^(\d+)\M} $line -> num]} {
lappend nums $num
}
}
}
}
close $fid
puts $nums
或者,甚至
set nums [exec sed -rn {/data_start/,/data_end/ {/^([[:digit:]]+).*/ s//\1/p}} $data_file]
puts $nums
答案 3 :(得分:0)
我最喜欢的方法是为每个可接受的标记声明proc
s并使用unknown
mechanism静静地忽略不可接受的标记。
proc 30 args {
... handle 30 $args
}
proc 90 args {
... process 90 $args
}
rename unknown original_unknown
proc unknown args {
# This space was deliberately left blank
}
source datafile.txt
rename original_unknown unknown
您将使用Tcl的内置解析,这应该要快得多。在我看来,它看起来也更好。
您也可以将线路处理逻辑完全放入unknown
- 程序中:
rename unknown original_unknown
proc unknown {first args} {
process $first $args
}
source input.txt
rename original_unknown unknown
无论哪种方式,诀窍是Tcl自己的解析器(在C中实现)将把输入行分解为令牌 - 所以你不必自己在Tcl中实现解析
这并不总是有效 - 例如,如果输入使用多行语法(没有{
和}
),或者令牌是用除空格之外的其他内容分隔的。但在你的情况下它应该很好。