Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Saving TCL arrays

4 views
Skip to first unread message

steve offutt

unread,
May 5, 1999, 3:00:00 AM5/5/99
to
Am trying to learn tcl. Have encountered difficulty trying to save a simple
array to a file. Do I need to use upvar to pass an array to a procedure? I
have only succeded in creating a data file that is blank, save for a newline.
I am using tcl/tk 8.0 on both Win 95/98 and Linux. Thanks in advance for the
help.

--
Steve Offutt Industrial Mold & Tool Co
"The only person that makes no mistakes
is the one who does nothing at all"
:-)

-----------== Posted via Deja News, The Discussion Network ==----------
http://www.dejanews.com/ Search, Read, Discuss, or Start Your Own

Jeff Gosnell

unread,
May 5, 1999, 3:00:00 AM5/5/99
to
Would it be possible to see some of your code? That might help us help you.
The way I pass arrays into a procedure is by using global.
You might try the following...

# the following is tk stuff and should be used with wish
button .btn1 -text "Set Array" -command {SetCommand}
button .btn2 -text "Save" -command {Save}
pack .btn1 .btn2

# the following proc does simple set stuff creating the array
proc SetCommand {} {
global arr1
set arr1(1) a
set arr1(2) b
set arr1(3) c
}

proc Save {} {
# global allows this procedure to see arr1
global arr1
# fill the variable that will eventually be saved to the file
# array names arr1 gathers all the index names into a list (useful ;-)
foreach index [array names arr1] {
append data $arr1($index)\n
}
# open the file for writing
set fileid [open foo.txt w]
# write to the file channel
puts $fileid $data
# close the file channel
close $fileid
}

I think that should do it.
Jeff Gosnell

steve offutt wrote in message <7go3kq$8ro$1...@nnrp1.dejanews.com>...

steve offutt

unread,
May 5, 1999, 3:00:00 AM5/5/99
to
In article <7gogv6$g97$1...@hermes.louisville.edu>,

"Jeff Gosnell" <jmgo...@athena.louisville.edu> wrote:
> Would it be possible to see some of your code? That might help us help you.
> The way I pass arrays into a procedure is by using global.
> You might try the following...
>
Jeff thanks for reply, I realized that I should have posted some of the script
shortly after I posted.

proc save_data { data } {
foreach item [array names array1] {puts $data "set array1($item)
$array1($item)"}
}

When I run the script, am getting an error similar to " no value given for
"data" - I think perhaps I have a problem with global declarations.

The array is being set from textvariable entry widgets.

Thanks to all who have helped. Special thanks to Laurent.

Bryan Oakley

unread,
May 5, 1999, 3:00:00 AM5/5/99
to
steve offutt <stev...@my-dejanews.com> wrote in message
news:7gpms7$kgi$1...@nnrp1.dejanews.com...

> In article <7gogv6$g97$1...@hermes.louisville.edu>,
> "Jeff Gosnell" <jmgo...@athena.louisville.edu> wrote:
> > Would it be possible to see some of your code? That might help us help
you.
> > The way I pass arrays into a procedure is by using global.
> > You might try the following...
> >
> Jeff thanks for reply, I realized that I should have posted some of the
script
> shortly after I posted.
>
> proc save_data { data } {
> foreach item [array names array1] {puts $data "set array1($item)
> $array1($item)"}
> }

Assuming you are calling this thusly:

entry .foo -textvariable myData(foo)
....
save_data myData

Then you need to use upvar in your save_data proc:

proc save_data {arrayName} {
upvar $arrayName array
foreach name [array names array] {
puts "set ${arrayName}($name) {$array($name)}"
}

Upvar is about the only way to "pass" arrays to a procedure. (I say "about",
because you could also use the global command, and you can also pass the
contents of an array using [array get]. But I digress).

This should (assuming I got the quoting right...) print out entries like:

...
set myData(foo) {some value}
....

If the array is global (and in your case it must, since you are using it as
a parameter to -textvariable), you can (and probably ought to) use global
instead of upvar. Using global makes it quite clear that the variable being
passed in is, in fact, global.

proc save_data{arrayName} {
global $arrayName
.... (code with lots of cumbersome quoting)
}

But with this method you have to do bunch of extra work since you have to
dereference the array name every where you want to use it. What a pain!

However, if you don't care about writing a general purpose routine, just
skip all the weird quoting and upvaring and hard-code it like this:

proc save_myData {} {
global myData
foreach name [array names myData] {
puts "set myData($name) {$myData($name)}"
}
}

Certainly no harm in that. Fewer lines of code, less quoting weirdness.
Though,if you want the same code to be able to save several different
arrays, a general purpose routine may be the Right Thing To Do. I often find
myself trying to write a general purpose routine "just in case I need it
later", when it would have been faster to just hard-code exactly what I
need. So, if you are saving only one array, go ahead and hard-code it. If
you want to save several different arrays using the same proc, you'll have
to use upvar.

Finally, be aware that you have to be very careful about protecting yourself
from generating invalid commands in your output. I've guarded against a
common case of having spaces in the values by placing curly braces around
the value that is printed out (or saved to a file or whatever), but that
will break with unbalanced curly braces in the data. Imagine if one of the
array elements had just a single closed curly brace. The output would look
like this:

set myData(foo) {}}

... which is invalid, of course. So, how do we guard against it? Well,
remember that a tcl command line is simply a list with a command and a
series of arguments. For defining values, the set command requires two
arguments -- the name and the value. So, ideally we need to create a well
formed list with set as the first element, the array element reference as
the second element, and the value as the third element.

So, what is the best way to create a list of exactly three elements? The
list command, of course. It is guaranteed to return a valid list with all
the quoting one could want (and often more...). So, our proc should probably
look something like this:

proc save_myData {} {
global myData
foreach name [array names myData] {
puts [list set myData($name) $myData($name)]
}
}

This will yield results that may not be particularly human readable (lots of
backslashes and such), but will survive reconstitution much better for a
wider range of possible values.


Jim Graham

unread,
May 5, 1999, 3:00:00 AM5/5/99
to
As promised via e-mail earlier today.... :-)

In article <7go3kq$8ro$1...@nnrp1.dejanews.com>,


steve offutt <stev...@my-dejanews.com> wrote:
>Have encountered difficulty trying to save a simple array to a file.
>Do I need to use upvar to pass an array to a procedure?

I wanted the same thing a while back, and created the following proc:

--------------------------- CUT HERE ---------------------------

# Take the name of an array as the arg and save all of its elements to
# the open (w) file specified by fileptr (in a format that can be sourced
# directly to restore the array's contents).

proc array_save {name fileptr} {
upvar $name array_local

puts $fileptr "array set $name {"
foreach idx [array names array_local] {
puts $fileptr " {$idx} {$array_local($idx)}"
}
puts $fileptr "}"
puts $fileptr ""
}

--------------------------- CUT HERE ---------------------------

Usage:

# create a sample array
for {set i 0} {$i < 20} {incr i} { set my_array($i) "$i" }

# open the file to save the array to
set f [open my_array.tcl w]

# save the array
array_save my_array $f

# close the file
close $f

Then, when you restart the program (or whatever will be using that
array), you just do the following: source my_array.tcl

It's that simple. :-)

Later,
--jim

--
73 DE N5IAL (/4) | |\ _,,,---,,_
j...@n5ial.gnt.net | ZZZzz /,`.-'`' -. ;-;;,_
ICBM / Hurricane: | |,4- ) )-,_. ,\ ( `'-'
30.39735N 86.60439W | '---''(_/--' `-'\_)


Bryan Oakley

unread,
May 5, 1999, 3:00:00 AM5/5/99
to
Jim Graham <j...@n5ial.gnt.net> wrote in message
news:7gqgsc$rgu$1...@tourist.gnt.net...

> As promised via e-mail earlier today.... :-)
>
> In article <7go3kq$8ro$1...@nnrp1.dejanews.com>,
> steve offutt <stev...@my-dejanews.com> wrote:
> >Have encountered difficulty trying to save a simple array to a file.
> >Do I need to use upvar to pass an array to a procedure?
>
> I wanted the same thing a while back, and created the following proc:
[snip]

This doesn't work for arrays where some values have unbalanced curly braces.
To wit:

% source jims_example.tcl
% set foo(ok) "this is ok"
this is ok
% set foo(bad) "this is not ok... }"
this is not ok... }
% set fid [open "/tmp/foo.tcl" w]
filed59a78
% array_save foo $fid
% close $fid
% source /tmp/foo.tcl
invalid command name "}"

The generated file looks like this:

array set foo {
{ok} {this is ok}
{bad} {this is not ok... }}
}

Here's a variation of your proc that works better:

proc array_save {name fileptr} {
upvar $name array_local
puts $fileptr "array set $name {"
foreach idx [array names array_local] {

puts $fileptr [list $idx $array_local($idx)]
}
puts $fileptr "}"
puts $fileptr ""
}

... which gives us this:

array set foo {
ok {this is ok}
bad this\ is\ not\ ok...\ \}
}

Not as pretty, but more robust. Note the use of [list ...] to insure that
each line of the array set command has exactly two elements.

You could do this all a bit more quickly using a single puts combined with
an [array get], though the performance gain would be minimal. Using the loop
makes it a tiny bit more obvious what is going on, and does allow you some
control on how the data appears in the file. For example, you could prepend
a tab before each line if you wish, and/or add a comment for each element.
Well, ok, you can't do that in combination with the array set command, but
if you set each element individually you could.

The program tkdiff does something like the latter, putting a human-readable
comment before each line, just in case someone wants to hand-edit the file.
Of course, now that I look at the source I see it has the same fault as the
original proc in this thread -- it could break with unbalanced braces in an
array value. Yikes!

Andreas Kupries

unread,
May 6, 1999, 3:00:00 AM5/6/99
to
"Bryan Oakley" <oak...@channelpoint.com> writes:

> steve offutt <stev...@my-dejanews.com> wrote in message
> news:7gpms7$kgi$1...@nnrp1.dejanews.com...
> > In article <7gogv6$g97$1...@hermes.louisville.edu>,
> > "Jeff Gosnell" <jmgo...@athena.louisville.edu> wrote:
> > > Would it be possible to see some of your code? That might help us help
> you.
> > > The way I pass arrays into a procedure is by using global.
> > > You might try the following...

> Assuming you are calling this thusly:
>
> entry .foo -textvariable myData(foo)
> ....
> save_data myData
>
> Then you need to use upvar in your save_data proc:
>
> proc save_data {arrayName} {
> upvar $arrayName array
> foreach name [array names array] {
> puts "set ${arrayName}($name) {$array($name)}"
> }

And in case you don't care about a nice and human-readable formatting
at all you can do:

proc save_data {arrayName} {
upvar $arrayName array

puts [list array set $arrayName [array get array]]
}

This will produce something like

...
array set myData {foo {some value} bar bza ...}
...

and could be a very long line, depending on your data.

--
Sincerely,
Andreas Kupries <a.ku...@westend.com>
<http://www.westend.com/~kupries/>
-------------------------------------------------------------------------------

WANGNICK Sebastian

unread,
May 6, 1999, 3:00:00 AM5/6/99
to
steve offutt wrote:
> proc save_data { data } {
> foreach item [array names array1] {puts $data "set array1($item)
> $array1($item)"}
> }

Uh, oh,

are you sure your users never enter blanks? I'd rather use list:

proc save_data { data } {
foreach item [array names ::array1] {

puts $data [list set array1($item) $::array1($item)]
}
}

Regards,
Sebastian
--
Dipl.-Inform. Sebastian <dot> Wangnick <at eurocontrol in be>
Office: Eurocontrol Maastricht UAC, Horsterweg 11, NL-6191RX Beek,
Tel: +31-433661370, Fax: ~300
Spam email is reported (charge $100) to providers and U...@FTC.GOV.

Donal K. Fellows

unread,
May 6, 1999, 3:00:00 AM5/6/99
to
In article <7go3kq$8ro$1...@nnrp1.dejanews.com>,
steve offutt <stev...@my-dejanews.com> wrote:
> Am trying to learn tcl. Have encountered difficulty trying to save

> a simple array to a file. Do I need to use upvar to pass an array
> to a procedure? I have only succeded in creating a data file that is

> blank, save for a newline. I am using tcl/tk 8.0 on both Win 95/98
> and Linux. Thanks in advance for the help.

Saving an array is dead easy. Just do:

proc saveArray {arrayName fileName} {
upvar $arrayName ary
set f [open $fileName w]
puts $f [list array set $arrayName [array get ary]]
close $f
}

Reloading it is even easier, since the save file format is that of a
tcl script you can just source back in, probably being careful to do
it in an equivalent context to where it was saved. (It takes a bit
more work to make the save format for long lists nicely readable by
people without good editors... :^)

The [list] quoting is the final part of the trick to all this that
I've not seen elsewhere...

Donal.
--
Donal K. Fellows http://www.cs.man.ac.uk/~fellowsd/ fell...@cs.man.ac.uk
-- The small advantage of not having California being part of my country would
be overweighed by having California as a heavily-armed rabid weasel on our
borders. -- David Parsons <o r c @ p e l l . p o r t l a n d . o r . u s>

steve offutt

unread,
May 6, 1999, 3:00:00 AM5/6/99
to
In article <7gs6jn$4r6$1...@m1.cs.man.ac.uk>,

fell...@cs.man.ac.uk (Donal K. Fellows) wrote:
>
> Saving an array is dead easy. Just do:
>
> proc saveArray {arrayName fileName} {
> upvar $arrayName ary
> set f [open $fileName w]
> puts $f [list array set $arrayName [array get ary]]
> close $f
> }
>
> Reloading it is even easier, since the save file format is that of a
> tcl script you can just source back in, probably being careful to do
> it in an equivalent context to where it was saved. (It takes a bit
> more work to make the save format for long lists nicely readable by
> people without good editors... :^)
>
> The [list] quoting is the final part of the trick to all this that
> I've not seen elsewhere...
>
> Donal.

Donal:

Thanks to you, (and everyone else who has replied). I appreciate and need
all the help I can get. The [list] quoting was also pointed out by Andreas
Kupries (msg 5) and others.

The following works well with the rest of my script, and saves my data files
in tcl for loading via source:

proc file_save { } {
global array1
foreach index [array names array1] {
append data "set array1($index) $array1($index)" \n
}
set filename [tk_getSaveFile]
set fileid [open $filename w]
puts $fileid $data
close $fileid
}

Thanks again to everyone, and now that I have my small test script working,
perhaps I wont have to ask for too much help to apply this to my larger
project.

0 new messages