How can I output a list of the values for all variables from the info globals command? - tcl

I am trying to write a small bit of code that will list all of the variables and the values they contain in the info globals command. I have tried several iterations of substitution but cant get Tcl to treat the variable name as a variable, and return its value. Below is what I started with:
set fileid [open "c:/tcl variables.txt" w+]
foreach {x} [lsort [info globals]] {
set y $x
puts $fileid "$x $y "
}
I can get
DEG2RAD DEG2RAD
PI PI
RAD2DEG RAD2DEG
.....
or
DEG2RAD $DEG2RAD
PI $PI
RAD2DEG $RAD2DEG
.....
but what I need is
DEG2RAD 0.017453292519943295
PI 3.1415926535897931
RAD2DEG 57.295779513082323
....

I think you are looking for the subst command:
set fileid [open "c:/tcl variables.txt" w+]
foreach {x} [lsort [info globals]] {
puts $fileid "$x [subst $$x] "
}
Alternatively, you can take advantage of the fact that set returns the value being set:
set fileid [open "c:/tcl variables.txt" w+]
foreach {x} [lsort [info globals]] {
puts $fileid "$x [set $x] "
}

The easiest method for doing this (because it avoids littering the output with your temporary variables) is to use a helper procedure and the upvar command:
proc listAllGlobals {filename} {
set fileid [open $filename w+]
foreach varname [lsort [info globals]] {
upvar "#0" $varname var
if {[array exists var]} continue; # Skip global arrays...
puts $fileid "$varname $var "
}
close $fileid
}
listAllGlobals "C:/tcl variables.txt"
If you've got Tcl 8.5, you can do this without creating a procedure:
apply {{} {
set fileid [open "C:/tcl variables.txt" w+]
foreach varname [lsort [info globals]] {
upvar "#0" $varname var
if {[array exists var]} continue; # Skip global arrays...
puts $fileid "$varname $var "
}
close $fileid
}}
This all works because what upvar does is link a local variable to a variable in another stack frame; #0 is the name of the global stack frame, $varname is the name of the variable in that context, and var is the local variable to bind to.

Arrays are variables too so just for reference, this outputs all variables (including arrays):
proc ListAllGlobals {{?pattern? *}} {
foreach {name} [lsort [info globals ${?pattern?}]] {
upvar {#0} $name var
if {[array exists var]} {
foreach {key val} [array get var] {
puts "${name}($key) [list $val]"
}
} else {
puts "$name [list $var]"
}
}
}
ListAllGlobals
ListAllGlobals tcl_platform
I'm using list to enhanced readability of the values.
A pattern can be specified for a subset of variables. It doesn't apply to array element names.
In keeping with previous examples the output can be written to an opened file with: puts $fileid [ListAllGlobals]

Related

TCL: Redirect output of proc to a file

I need to redirect output of a proc to a file. The "redirect" command isn't working for the Tcl interpreter that my tool uses. So I'm trying "exec echo [proc_name]" instead, which was suggested in one of the threads on this site. But this doesn't work, the file ${dump_dir}/signal_list.txt comes out empty,
proc dump_signals {{dump_dir "."}} {
upvar build build
puts "Dumping signals.."
set my_file [open ${dump_dir}/signal_list.txt w]
exec echo [get_signals] > $my_file
}
'get_signals' is a proc, which calls another proc,
proc puts_name_and_value {name} {
set value [value %h $name]
puts "$name $value"
}
proc get_signals {} {
# Get list of signals
set signal_list {test.myreg test.myreg2}
foreach signal $signal_list {
puts_name_and_value $signal
}
}
My workaround for now is this, writing to the file in the bottom level proc by upvar'ing the file variable. This works but isn't the most clean way of doing this. Please let me know how to cleanly redirect the output of a proc to a file.
proc puts_name_and_value {name} {
upvar my_file my_file
set value [value %h $name]
puts $my_file "$name $value"
#puts "$name $value"
}
proc get_signals {} {
upvar my_file my_file
# Get list of signals
set signal_list {test.myreg test.myreg2}
foreach signal $signal_list {
puts_name_and_value $signal
}
}
proc dump_signals {{dump_dir "."}} {
upvar build build
puts "Dumping signals.."
set my_file [open ${dump_dir}/signal_list.txt w]
get_signals
}
Your dump_signals proc writes to standard output, and doesn't return anything. So of course trying to use a shell to redirect its output to a file isn't going to result in anything in the file.
One solution is to use tcl's transchan API with chan push and chan pop to temporarily override what stdout goes to. Example:
#!/usr/bin/env tclsh
proc redirector {fd args} {
switch -- [lindex $args 0] {
initialize {
# Sanity check
if {[lindex $args 2] ne "write"} {
error "Can only redirect an output channel"
}
return {initialize write finalize}
}
write {
puts -nonewline $fd [lindex $args 2]
}
finalize {
close $fd
}
}
}
proc writer_demo {} {
puts "line one"
puts "line two"
}
proc main {} {
chan push stdout [list redirector [open output.txt w]]
writer_demo
chan pop stdout
}
main
Running this script will produce a file output.txt with the contents of writer_demo's puts calls instead of having them go to standard output like normal.
You could also just pass the file handle to write to as an argument to your functions (Instead of using upvar everywhere):
proc puts_name_and_value {out name} {
set value [value %h $name]
puts $out "$name $value"
}
proc get_signals {{out stdout}} {
# Get list of signals
set signal_list {test.myreg test.myreg2}
foreach signal $signal_list {
puts_name_and_value $out $signal
}
}
proc dump_signals {{dump_dir "."}} {
upvar build build
puts "Dumping signals.."
set my_file [open ${dump_dir}/signal_list.txt w]
get_signals $my_file
}

TCL script to extract the elements from a file in a directory and make a list

I am having one problem as stated below
Problem Description
I am having some "sv" extension files , I am using "glob" to extract the matching files , Now in these matching files , I need to split them and extract the elements and create different lists.
For example
set files [glob -type f modified_rtl_files/*/*{wrapper_hsm}*]
This command gives me these two files :
modified_rtl_files/mbist_wrapper/mbist_wpper_hsm_pkram.sv modified_rtl_files/mbi_wrapper/mbist_wrapper_hsm_sysram.sv
Now I am extracting the filename with the below command
foreach element $files {
set c [split [ file tail [ file rootname $element ] ] "_" ]
echo $c
}
This is giving me
pkram
sysram
But I need to set them to two independent list
$mem1
$mem2
$mem1 should be pkram
$mem2 should be sysram
Whenever I am trying to create something like this , both the elements are getting printed
foreach element $files {
set c [split [ file tail [ file rootname $element ] ] "_" ]
set d [ lindex $c 3]
set mem1 [ lindex $d 0 ]
puts $mem1
}
It is printing both
pkram
sysram
I want independent variables .
Try adding the values you want into an array instead of numbered variables:
set files {modified_rtl_files/mbist_wrapper/mbist_wpper_hsm_pkram.sv modified_rtl_files/mbi_wrapper/mbist_wrapper_hsm_sysram.sv}
set mem {}
foreach f $files {
lappend mem [file rootname [lindex [split $f _] end]]
}
puts [lindex $mem 0] ;# => pkram
puts [lindex $mem 1] ;# => sysram
Or use the lmap command to make it more concise:
set mem [lmap f $files {file rootname [lindex [split $f _] end]}]
You can do generated names with set:
foreach element [lsort $files] {
set mem[incr i] [lindex [split [file tail [file rootname $element]] "_"] 3]
puts [set mem$i]; # <<< How to *READ* a variable with a generated name
}
(Always sort the output of glob if you care about the order at all; it's not guaranteed to come in any order at all or with any consistency).
BUT DON'T DO THIS!
Generated variable names are a pain to work with precisely because they can't be used with the $ language syntax. You're much better using (associative) arrays because those do work with that:
foreach element [lsort $files] {
set mem([incr i]) [lindex [split [file tail [file rootname $element]] "_"] 3]
puts $mem($i)
}
The values will not be in mem1 and mem2, but rather mem(1) and mem(2).

Variable in curly bracket double quote tcl

I am working this script I want script to replace the second line of my session.mvw file so I am asking input "name" if I enter 2222 as input I expect the second line of my session.mvw file as {GRAPHIC_FILE_1 = "E:/ge work/hyperview scripting/222.rst"}
but instead its only giving
{GRAPHIC_FILE_1 = "E:/ge work/hyperview scripting/${name}.rst"}.
puts "Enter your name: "
#flush stdout set name [gets stdin]
set in [open session.mvw r]
# get the path to the parent directory
set hostDir [file dirname session.mvw]
set tempfile "$hostDir/temp2.txt"
# open/create a temp file
set out [open $tempfile w]
set count 0
while { [eof $in] != 1 } {
gets $in line
#set firstItem [lindex $line 0] incr count
# a match has been found...
if {$count == 2 } {
puts $out {GRAPHIC_FILE_1 = "E:/ge work/hyperview scripting/${name}.rst"}
} elseif {$count == 3} {
puts $out {GRAPHIC_FILE_1 = "E:/ge work/hyperview scripting/${name}.rst"}
} else {
puts $out $line
}
}
close $in
close $out
close $hostDir
# over-write the existing file
#file rename -force $tempfile session_file.mvw
Tcl's got a general rule that it doesn't do substitutions inside braces. That's usually exactly the right thing. However, in this case you need something a bit more. For these sorts of cases, there's the subst command which does do those substitutions:
puts $out [subst {GRAPHIC_FILE_1 = "E:/ge work/hyperview scripting/${name}.rst"}]
(Note: If you're generating Tcl code with subst, you're probably doing it wrong. Not that this is what you're doing in this case, but still it's a warning to all readers…)
If you want to have braces around the value, you could do this
puts $out [list "GRAPHIC_FILE_1 = \"E:/ge work/hyperview scripting/${name}.rst\""]
or this
puts $out "{GRAPHIC_FILE_1 = \"E:/ge work/hyperview scripting/${name}.rst\"}"
both of which print the string
{GRAPHIC_FILE_1 = "E:/ge work/hyperview scripting/222.rst"}

How to unset an arg in tcl

I am taking arguments from command line and passing all those arguments to another program (with expect spawn). I want to parse all options and omit some of them (or do something else). To do that I am doing this:
set arguments [lrange $argv 0 end]
#Check for -lp option. Set the program path
for {set var 0} {$var<$argc} {incr var} {
if {[lindex $arguments $var] == "-lp" || [lindex $arguments $var] == "--launcher-path"} {
if {[lindex $arguments [expr {$var+1}]] != ""} {
set program [lindex $arguments [expr {$var+1}]]
#unset [lindex $arguments $var]
} else {
puts "E: Argument missing for option: [lindex $arguments $var]"
exit 1
}
}
}
But I can't figure out how to unset those args that I used. For example, I need to unset [lindex $arguments [expr {$var+1}]] and [lindex $arguments $var].
This is how I am running the $program:
if {[catch {spawn $program --text {*}$arguments}]} {
puts "E: Launcher not found: $program"
exit 1
}
If your arguments are all key-value, then you can iterate over the arguments in pairs with foreach and build up a new list containing just the arguments you're interested in.
set newarguments [list]
foreach {arg value} $arguments {
switch -exact -- $arg {
"-lp" -
"--launcher-path" {
set program $value
}
default {
lappend newarguments $arg $value
}
}
}
If you have mixed flag and key-value options, then you will need to iterate using an index, similar to your code, but building up the new list of arguments will be roughly the same.
You could also check into the tcllib cmdline package, although that does not handle long options.
This is how I have done it:
set arguments [lreplace $arguments [expr {$var+1}] [expr {$var+1}]]
set arguments [lreplace $arguments $var $var]
As glenn-jackman pointed out, the above can be shortened to:
set arguments [lreplace $arguments $var [expr {$var+1}]]

Redirecting the "parray" output to a file in tcl

I have an array in tcl.
For example:
set a(1) "First element"
set a(2) "second element"
parray a
parray a displays output as
a(1) = "First element"
a(2) = "second element"
Is it possible to redirect the parray output to a file?
The parray command can't be redirected. It's a simple-minded procedure that is too stupid to be redirected. But it's source code isn't very long; in fact, it's short enough that I'll just paste it here (it's under the Tcl license):
proc parray {a {pattern *}} {
upvar 1 $a array
if {![array exists array]} {
return -code error "\"$a\" isn't an array"
}
set maxl 0
set names [lsort [array names array $pattern]]
foreach name $names {
if {[string length $name] > $maxl} {
set maxl [string length $name]
}
}
set maxl [expr {$maxl + [string length $a] + 2}]
foreach name $names {
set nameString [format %s(%s) $a $name]
puts stdout [format "%-*s = %s" $maxl $nameString $array($name)]
}
}
Redirecting it (hint: change the stdout for something obtained from open … a, and don't forget to close it afterwards) should be a simple exercise.
This builds on the answers by Dinesh and Donal Fellows: You could adapt the code of parray automatically, like this:
auto_load parray
proc printArray {a {pattern *} {channel stdout}} \
[string map {stdout $channel} [info body parray]]
This gives you a new proc printArray with an optional channel argument.