How to convert a file to a tcl list?

13,868

Solution 1

Something like this ought to do the trick:

proc listFromFile {filename} {
    set f [open $filename r]
    set data [split [string trim [read $f]]]
    close $f
    return $data
}
set times [listFromFile time.dat]
set accs  [listFromFile acc.dat]

The [split] command is doing the "heavy lifting" for you here.

EDIT

If you have a single file with both columns and you want to return that data set from a function, you have a couple choices. Both involve returning a "list of lists", and then it's just a question of whether you want two lists of N elements, or N lists of two elements. For example, to get N lists of two elements:

proc readData {filename} {
    set result {}
    set f [open $filename r]
    foreach line [split [read $f] \n] {
        lappend result $line
    }
    return $result
}

Or, to get two lists of N elements:

proc readData {filename} {
    set times {}
    set accs  {}
    set f [open $filename r]
    foreach line [split [read $f] \n] {
        lappend times [lindex $line 0]
        lappend accs  [lindex $line 1]
    }
    return [list $times $accs]
}

Technically you can even just return the data as a list of 2N elements:

proc readData {filename} {
    set result {}
    set f [open $filename r]
    foreach line [split [read $f] \n] {
        lappend result [lindex $line 0] [lindex $line 1]
    }
    return $result
}

It all depends on how you plan to use the data.

Solution 2

If the file isn't big and you don't mind leaving the file handle open until the script exits (when it will be closed automatically), you can do it all in one line with

proc listFromFile {file} { return [split [read [open $file r]] "\n"] }

Though splitting on \n means if the last line has \n (as most do), you'll end up with an extra empty element in the list. You can remove that with

proc listFromFile {file} { return [lreplace [split [read [open $file r]] "\n"] end end] }

And lastly if you'd like your list file to have comments, i.e., ignore files starting with #, you can do that with

proc listFromFile {file} { return [lsearch -regexp -inline -all [lreplace [split [read [open $file r]] "\n"] end end] {^[^#]}] }

Solution 3

Using fileutil this becomes trivial:

package require fileutil

fileutil::write testfile.dat {a b c}
set myList [fileutil::cat testfile.dat]
lindex $a 1

This actually writes a list to a file first, and then reads this data back in. As tcl represents lists as strings, we just have to assign the file contents to a variable.

In case your list uses newlines as element seperators, or something other than plain whitespace (tab, space), you will have to use split, i.e. split [fileutil::cat testfile.dat] \n as discussed in above posts.

NOTE: internally lists can also use a list representation besides their string representation. Calling sting and list functions onto a list alternativly triggers conversions between the representation and invalidates the other representation. For that reason it is bad to switch between the too, but that is a different topic, for those interested check the tcl wiki on shimmering.

Share:
13,868
Admin
Author by

Admin

Updated on June 26, 2022

Comments

  • Admin
    Admin almost 2 years

    I have a file named time.dat and acc.dat both of which contains a single column containing numerical values. I want to create lists from these files which contain the values in the files. Anyone knows how to do it?

    proc ReadRecord {inFilename outFilenameT outFilenameS} {
    
       if [catch {open $inFilename r} inFileID] {
          puts stderr "Cannot open $inFilename for reading"
       } else {
          set outFileIDS [open $outFilenameS w]
          set outFileIDT [open $outFilenameT w]
          foreach line [split [read $inFileID] \n] {
    
             if {[llength $line] == 0} {
                continue
             } else {
                puts $outFileIDT [lindex $line 0]
                puts $outFileIDS [lindex $line 1]
             } 
          }
          close $outFileIDT
          close $outFileIDS
          close $inFileID
       }
    };
    
  • Admin
    Admin almost 11 years
    Thanks Eric, it worked. I have one more question. I wrote a proc to read a file which has two columns. The first column has time values and second column as acceleration values. Because I didn't know if I can return two lists from a proc, I was creating two different files of single columns of time and acceleration values, respectively. And then asked here how to concert those to lists. I was wondering if I can return two lists directly from a procedure? My procedure is written in next post:
  • Admin
    Admin almost 11 years
    Since I am a new user and I can not post my code as an answer before 8 hrs, please look in the original post where I have added my procedure. Sorry for the mess up.
  • Admin
    Admin almost 11 years
    Two lists of N elements is what I need. So when I access them in two lists, would this work (three code lines separated by comma): set List2N [readData $filename], set timeList [lrange $List2N 0 [expr [llength $List2N]/2-1]]], set accList [lrange $List2N [expr [llength $List2N]/2]] [llength $List2N]]
  • Eric Melski
    Eric Melski almost 11 years
    @user2680224 no, the result of [readData] is a list itself. Use [lindex] to extract the sublists, as in set timeList [lindex $List2N 0].
  • Ralf
    Ralf over 3 years
    If the file consists of elements separated by line-breaks, and the last line also has a line-break, I found it easier to use [split [exec cat testfile.dat]] instead of [split [fileutil::cat testfile.dat]] since the latter produces an empty last element. Disadvantage of the former: needs to start the cat binary.