逐行执行 tcl 命令

execute tcl commands line by line

我有这样一个文件:

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

顺便说一句:最后一行包含内部 {},应该将其放入字符串变量中。并且无意将其设为字典。

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

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.

对其进行评估来简化您的代码

预计到达时间

这个解决方案更加健壮,因为它在沙箱中读取脚本(在编写将执行任意外部代码的代码时总是一个好主意)并重新定义(在该沙箱内)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 包。它可以让你创建一个完全空的解释器。

文档:apply, continue, eof, foreach, gets, if, incr, info, interp package, interp, list, namespace, proc, puts, set, string, uplevel, upvar, while