Naaz
Naaz

Reputation: 57

Parsing a file with Tcl

I have a file in here which has multiple set statements. However I want to extract the lines of my interest. Can the following code help

set in [open filename r]    
seek $in 0 start    
while{ [gets $in line ] != -1} {    
    regexp (line to be extracted)
}

Upvotes: 2

Views: 27582

Answers (6)

vaichidrewar
vaichidrewar

Reputation: 9621

Other solution:

Instead of using gets I prefer using read function to read the whole contents of the file and then process those line by line. So we are in complete control of operation on file by having it as list of lines

set fileName [lindex $argv 0]
catch {set fptr [open $fileName r]} ;
set contents [read -nonewline $fptr] ;#Read the file contents
close $fptr ;#Close the file since it has been read now
set splitCont [split $contents "\n"] ;#Split the files contents on new line
foreach ele $splitCont {
    if {[regexp {^set +(\S+) +(.*)} $ele -> name value]} {
        puts "The name \"$name\" maps to the value \"$value\""
    }
}

How to run this code:
say above code is saved in test.tcl
Then

tclsh test.tcl FileName

FileName is full path of file unless the file is in the same directory where the program is.

Upvotes: 9

Pac
Pac

Reputation: 23

set File [ open $fileName r ]

  while { [ gets $File line ] >= 0 } {

 regex {(set) ([a-zA-Z0-0]+) (.*)} $line str1 str2 str3 str4

   #str2 contains "set";
   #str3 contains variable to be set;
   #str4 contains the value to be set;

  close $File
 }

Upvotes: 0

Erik Johnson
Erik Johnson

Reputation: 1164

I've read your comments so far, and if I understand you correctly your input data file has 6 (or 9, depending which comment) data fields per line, separated by spaces. You want to use a regexp to parse them into 6 (or 9) arrays or lists, one per data field.

If so, I'd try something like this (using lists):

set f [open $filename]
while {[gets $f line] > -1} {
    # Process lines
    if {[regexp {(\S+) (\S+) (\S+) (\S+) (\S+) (\S+)} $line -> name source drain gate bulk inst]} {
        lappend nameL $name
        lappend sourceL $source
        lappend drainL $drain
        lappend gateL $gate
        lappend bulkL $bulk
        lappend instL $inst
    }
}
close $f

Now you should have a set of 6 lists, one per field, with one entry in the list for each item in your input file. To access the i-th name, for example, you grab $nameL[$i].

If (as I suspect) your main goal is to get the parameters of the device whose name is "foo", you'd use a structure like this:

set name "foo"
set i [lsearch $nameL $name]
if {$i != -1} {
    set source $sourceL[$i]
} else {
    puts "item $name not found."
    set source ''  
    # or set to 0, or whatever "not found" marker you like
}

Upvotes: 0

jk.
jk.

Reputation: 14004

Yet another solution:

as it looks like the source is a TCL script, create a new safe interpreter using interp which only has the set command exposed (and any others you need), hide all other commands and replace unknown to just skip anything unrecognised. source the input in this interpreter

Upvotes: 1

Hai Vu
Hai Vu

Reputation: 40773

Here is yet another solution: use the file scanning feature of Tclx. Please look up Tclx for more info. I like this solution for that you can have several scanmatch blocks.

package require Tclx

# Open a file, skip error checking for simplicity
set inputFile [open sample.tcl r]

# Scan the file 
set scanHandle [scancontext create]
scanmatch $scanHandle {^\s*set} {
    lassign $matchInfo(line) setCmd varName varValue; # parse the line
    puts "$varName = $varValue"
}
scanfile $scanHandle $inputFile
close $inputFile

Yet another solution: use the grep command from the fileutil package:

package require fileutil

puts [lindex $argv 0]
set matchedLines [fileutil::grep {^\s*set} [lindex $argv 0]]
foreach line $matchedLines {
    # Each line is in format: filename:line, for example
    # sample.tcl:set foo bar
    set varName [lindex $line 1]
    set varValue [lindex $line 2]
    puts "$varName = $varValue"
}

Upvotes: 0

Donal Fellows
Donal Fellows

Reputation: 137777

First, you don't need to seek to the beginning straight after opening a file for reading; that's where it starts.

Second, the pattern for reading a file is this:

set f [open $filename]
while {[gets $f line] > -1} {
    # Process lines
    if {[regexp {^set +(\S+) +(.*)} $line -> name value]} {
        puts "The name \"$name\" maps to the value \"$value\""
    }
}
close $f

OK, that's a very simple RE in the middle there (and for more complicated files you'll need several) but that's the general pattern. Note that, as usual for Tcl, the space after the while command word is important, as is the space between the while expression and the while body. For specific help with what RE to use for particular types of input data, ask further questions here on Stack Overflow.

Upvotes: 3

Related Questions