逐行执行tcl命令

时间:2015-07-08 18:33:11

标签: tcl

我有一个这样的文件:

set position        {0.50 0.50}
set visibility      false
set text {ID: {entity.id}\n Value: {entity.contour_val}}

我想做类似于source的事情,但我只想使用文件句柄。

我目前的尝试是这样的:

proc readArray {fileHandle arrayName} {
    upvar $arrayName arr

    set cl 0

    while {! [eof $fileHandle]} {
        set cl [expr "$cl + 1"]
        set line [gets $fileHandle]
        if [$line eq {}] continue

        puts $line
        namespace eval ::__esg_priv "

            uplevel 1 {*}$line
        "

        info vars ::__esg_priv::*

        foreach varPath [info vars ::__esg_priv::*] {

            set varName [string map { ::__esg_priv:: "" } $varPath]

            puts "Setting arr($varName) -> [set $varPath]"

            set arr($varName) [set $varPath]
        }
        namespace delete __esg_priv
    }       
    puts "$cl number of lines read"
}

代替高级版,我尝试了许多eval和引用的组合。 我的问题是,它要么在带有列表的行上失败,要么它不会设置变量。 如果执行的命令是任何有效的代码,那么正确的方法是什么。

另一个问题是如何正确应用错误检查,我还没有尝试过。

致电

readArray [open "myFile.tcl" r] arr

我希望

parray arr

问题如下:

arr(position)   = 0.50 0.50
arr(text)       = ID: {entity.id}\n Value: {entity.contour_val}
arr(visibility) = false

顺便说一句:最后一行包含内部{},它应该成为字符串变量。而且没有意图将其作为一个字典。

1 个答案:

答案 0 :(得分:0)

此代码有效,但仍存在一些问题:

proc readArray {fileHandle arrayName} {
    upvar $arrayName arr

    set cl 0

    while {! [eof $fileHandle]} {
        incr cl ;# !
        set line [gets $fileHandle]
        if {$line eq {}} continue ;# !

        puts $line
        namespace eval ::__esg_priv $line ;# !

        foreach varPath [info vars ::__esg_priv::*] {
            set varName [string map { ::__esg_priv:: "" } $varPath]

            puts "Setting arr($varName) -> [set $varPath]"

            set arr($varName) [set $varPath]
        }
        namespace delete __esg_priv
    }       
    puts "$cl number of lines read"
}

我已经拿出了一些看似不必要的线条,并且改变了一些线条。

您不需要set cl [expr "$cl + 1"]incr cl会这样做。

if [$line eq {}] continue将失败,因为[...]是命令替换。 if {$line eq {}} continue(括号而不是括号)符合您的意图。

除非您在另一个范围内访问变量,否则您不会需要uplevelnamespace eval ::__esg_priv $line将评估指定命名空间中的一行。

我没有改变以下内容,但也许你应该:

set varName [string map { ::__esg_priv:: "" } $varPath]按预期工作,但set varName [namespace tail $varPath]更清晰。

请注意,如果存在与文件中某个变量同名的全局变量,则不会创建任何名称空间变量;全局变量将被更新。

如果您打算将text变量中的值用作字典,则需要删除\n或大括号。

根据您的问题标题,您希望逐行评估文件。如果可以取消该要求,可以通过在一个操作中读取整个脚本然后使用单个namespace eval对其进行评估来简化代码。

<强> ETA

这个解决方案更加健壮,因为它在沙箱中读取脚本(在编写将执行任意外部代码的代码时总是一个好主意)并重新定义(在该沙箱中)要创建的set命令数组中的成员而不是常规变量。

proc readArray {fileHandle arrayName} {
    upvar 1 $arrayName arr
    set int [interp create -safe]
    $int alias set apply {{name value} {
        uplevel 1 [list set arr($name) $value]
    }}
    $int eval [read $fileHandle]
    interp delete $int
}

为了更加安全地防止与全局变量等的意外交互,请查看Tcllib中的interp包。它允许您创建一个完全空的解释器。

文档:applycontinueeofforeachgetsifincr,{{3 }},info包,interpinterplistnamespaceprocputs,{{3} },setstringuplevel