如何从TCL中的文件中获取选择性数据?

时间:2015-06-19 22:18:03

标签: tcl tk

我正在尝试使用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
}

4 个答案:

答案 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中实现解析

这并不总是有效 - 例如,如果输入使用多行语法(没有{}),或者令牌是用除空格之外的其他内容分隔的。但在你的情况下它应该很好。