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

How to get selective data from a file in TCL?

我正在尝试使用 tcl 根据某些关键字从文件中解析选择性数据,例如我有一个这样的文件

...
...
..
... 
data_start
30 abc1 xyz 
90 abc2 xyz 
214 abc3 xyz
data_end
...
...
...

如何只捕获 "data_start" 和 "data_end" 之间的 30、90 和 214?到目前为止我所拥有的(tcl 新手),

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
}

将文件中的所有数据加载到一个变量中。设置开始和结束标记并寻找这些位置。逐行处理项目。 Tcl 使用由白色 space 分隔的字符串列表,因此我们可以使用 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

如果您的文件较小,那么您可以使用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

我会写成

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///p}} $data_file]
puts $nums

我最喜欢的方法是为每个 可接受的 标记声明 proc 并利用 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 中实现解析。

这并不总是有效——例如,如果输入使用多行语法(没有 {}),或者如果标记用白色以外的东西分隔space。但在你的情况下它应该做得很好。