2015-07-08 2 views
0

У меня есть файл, как это:выполнить TCL команд построчно

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

И я хочу сделать что-то похожее на источник, но я хочу использовать дескриптор файла только.

Моя текущая попытка выглядит следующим образом:

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" 
} 

На месте uplevel я перепробовал много комбинаций 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

Этот код работает, но есть еще некоторые проблемы с ним:

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 в качестве словаря, вам необходимо удалить либо , либо фигурные скобки.

Согласно вашему названию вопроса, вы хотите оценить файл по строкам. Если это требование можно снять, ваш код можно упростить, прочитав весь скрипт за одну операцию, а затем оценив его одним 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 
} 

Чтобы сделать его еще более безопасным от неожиданного взаимодействия с глобальными переменными и т.д., посмотрите на interp пакет в Tcllib. Это позволяет создать полностью пустой интерпретатор.

Документация: apply, continue, eof, foreach, gets, if, incr, info, interp пакет, interp, list, namespace, proc, puts, set, string, uplevel, upvar, while

+0

Спасибо за все ценные комментарии и улучшения. Хотя, похоже, он все еще не делает то, что я хочу. Я попытаюсь отредактировать вопрос, чтобы сделать его более ясным. – Ingo

+0

@Ingo: результат, который вы ожидаете, это именно то, что я получаю, когда запускаю свой код и печатаю массив. Что вы получаете? –

+0

Интересно. Массив для меня не определен. Я дважды проверю. – Ingo

Смежные вопросы