我有一个这样的文件:
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
顺便说一句:最后一行包含内部{},它应该成为字符串变量。而且没有意图将其作为一个字典。
答案 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
(括号而不是括号)符合您的意图。
除非您在另一个范围内访问变量,否则您不会需要uplevel
。 namespace 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
包。它允许您创建一个完全空的解释器。
文档:apply,continue,eof,foreach,gets,if,incr,{{3 }},info包,interp,interp,list,namespace,proc,puts,{{3} },set,string,uplevel