parray

parray - print an array's keys and values

https://www.tcl-lang.org/man/tcl8.6/TclCmd/library.html

parray arrayName

Print on standard output the names and values of all the elements in the array arrayName. ArrayName must be an array accessible to the caller of parray. It may be either local or global.

In fact, it's more like this:

parray arrayName ?pattern?

Print on standard output the names and values of all the elements in the array arrayName that match pattern. If pattern is not specified, then all of the elements of the array are included in the result. If pattern is specified, then only those elements whose names match pattern (using the matching rules of string match) are included. ArrayName must be an array accessible to the caller of parray. It may be either local or global.

parray prints an array's keys and values, in key alphabetic order and formatted to the longest key name, to stdout. RS


Sometimes, a developer erroneously tries to do something like this:

 set str [parray env]

or some other array. parray only produces output to stdout. Just copy /installdir/tcl/lib/tcl8.4/parray.tcl and modify to accumulate a string rather than calling puts . Call the proc something else, though, or you may unintentionally impact some other tool.

LES on 10-18-2008: I just don't get it. A command/proc that returns data can be used in two ways: returning the data or printing it to stdout - because printing the output of a command that returns data is trivial. But a command/proc that will adamantly output to stdout only is half as useful (or even less than half). It can't simply return the data or feed it to a variable as in the set str [parray env] example above. What is the point of limiting parray like that? Imagine if all commands had that limitation, how much harder life would be.

DKF: The parray command prints an array, just as puts prints a string. The equivalent for scripted processing is array get.

Maria - 2010-06-11 08:33:15

yes, DFK, but puts, unlike parray, can take a channelId argument to instruct it to print to a specific channel.

LIV - 2010-02-01 17:19:35

puts $var can be redirected to a file, but parray can't. The format from array get ARRAYNAME does not look good when write them to a file.

LV: LIV makes a good argument. I recommend you submit a feature request at http://tcl.sf.net/ requesting that parray support an optional channel.

Lars H: You may also take a look at exhibit.


HE 2010-02-02: I use the following procedures to save/load an array in/from a file:

proc sarray {filename arrayname} {
        upvar $arrayname name
        set fid [open $filename w]
        foreach index [lsort [array names name]] {
                regsub -all -- {\n} [list $index $name($index)] {\n} tmp
                regsub -all -- {\r} $tmp                        {\r} tmp
                puts $fid $tmp
        }
        close $fid
        return
}
proc larray {filename arrayname} {
        upvar $arrayname name
        set fid [open $filename r]
        while {![eof $fid]} {
                gets $fid line
                if {$line eq {}} {
                        continue
                }
                regsub -all -- {\\r} $line "\r" tmp
                regsub -all -- {\\n} $tmp  "\n" tmp
                array set name $tmp
        }
        close $fid
        return
}

Casteele - 2016-06-24 23:22:39

Re: Saving/loading arrays to/from a file: I often see many people post these kind of code snippets, but they do not think them through. What happens if one of the array elements is actually file channel? A socket? A Tk window? An object? You will be saving the string representation of these things, rather than the thing itself, which can and often does change between invocations. You might unintentionally save a file handle reference that when you reload the array from the file, it may be pointing to another file entirely, possibly even a critical file. I've seen people write system programs which clobber /etc/passwd by doing this kind of thing. Especially when someone else modifies your code for their own needs, without realizing that you've put code in place that does this sort of thing.

As I try to put almost all my "global" data inside a single global array, thus avoiding having too many individual variable in global scope, saving an array with dynamic state information, such as open file handles, etc, would introduce many potentially nasty bugs. The only solution to this problem is to write code which knows how to handle each piece of data, and can safely (de)serialize it when needed. (Using object-orientated code helps a lot here!) It's a lot of extra programming, but shouldn't a programmer already be completely aware of the data they're operating on, and how to operate on it?

If you absolutely must use data-type agnostic code (generic programming), then look in to programming techniques which are better suited to it, such a using OO Classes, STL libraries, etc.

HE 2021-12-20. Could be long running discussion if we answer every 6 years ;-)

The risks you Casteele are mentioning are risks we always have to handle as a programmer. If you use code like sarray and larray you are in charge to do it on data with whom it works.

And looking closer the issues you mention are not raised by storing the array data it is raised by loading it which is not the original request.

But, this doesn't say this is generally harmful to store/load array data. It depends on the use case. For me it is mainly the following use cases:

  • store and load configuration
  • persistent data storage between two calls of the same script or between different scripts.
  • for debugging purpose

And I use sarray/larray for around 20 years now without running in any of your mentioned problems. And so will possibly most people who store/load array data.

HE 2023-10-28: I added a line handling '\r' (Carriage-return) to the above code of sarray and larray.

HE 2023-10-28: There are some other facts which should be mentioned.

1st: parray is not visible in tclsh or wish before its first call. This is described in the documentation but not in an obvious way. Therefore, you can't find it by using "info procs" or "info commands" if you not used it before.

2nd It should be mentioned that parray not handle '\r' (Carriage-return) properly. And, at least as index, '\n' (Newline) cause also some strange output. Other control codes are very likely also in question.

I mention this here because I struggled with it in real life by using parray. It is not only a theoretical case.

set tArray(a) 1
set tArray(b) "2\r3"
set tArray(c) 4
set tArray(d) "5\n6"
set tArray(e) 7
set tArray(f) "8\r9\r10"
set tArray(g) 11
set i1 "h\ni"
set tArray($i1) 12
set tArray(j) 13
set i2 "k\rl"
set tArray($i2) 14

parray tArray

will print:

tArray(a)   = 1
3Array(b)   = 2
tArray(c)   = 4
tArray(d)   = 5
6
tArray(e)   = 7
10rray(f)   = 8
tArray(g)   = 11
tArray(h
i) = 12
tArray(j)   = 13
l) = 14k

You can see where the output is weird. How Newline is printed today, in case it is inside the value, is very likely easier to read for most people. Particular in the cases with more data in the value. To replace 'Carriage-Returns inside index and inside value is on the other side mandatory because it removes/overwrites data if not. For equal handling it looks like the better choice to replace also Newline.

That is why my expectation of the output would be:

tArray(a)      = 1
tArray(b)      = 2\r3
tArray(c)      = 4
tArray(d)      = 5\n6
tArray(e)      = 7
tArray(f)      = 8\r9\10
tArray(g)      = 11
tArray(h\ni)   = 12
tArray(j)      = 13
tArray(k\rl)   = 14

And here a version of parray which provides that:

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 -dictionary [array names array $pattern]]
    foreach name $names {
        regsub -all -- {\n} $name  {\n} name1
        regsub -all -- {\r} $name1 {\r} name1
        if {[set tmpLen [string length $name1]] > $maxl} {
            set maxl $tmpLen
        }
    }
    set maxl [expr {$maxl + [string length $a] + 2}]
    foreach name $names {
        regsub -all -- {\n} $name  {\n} name1
        regsub -all -- {\r} $name1 {\r} name1
        set nameString [format %s(%s) $a $name1]
        regsub -all -- {\n} $array($name) {\n} tmp
        regsub -all -- {\r} $tmp          {\r} tmp
        puts stdout [format "%-*s = %s" $maxl $nameString $tmp]
    }
}