Tcl: How to replace variable string? - tcl

Input file is a tcl script and it looks like:
set PATH /user/abc/path
set PATH2 /user/abc/path2
...
read_verilog ${PATH}/src/vlog/code_1.v
read_verilog $PATH/src/vlog/code_2.v
read_vhdl ${PATH2}/src/vhd/code_3.vh
read_vhdl $PATH2/src/vhd/code_4.vh
[other commands ...]
Need to check if the source file is exist and print out none-exist files.
If none of the file is exist, the output looks like:
read_verilog ${PATH}/src/vlog/code_1.v
read_verilog $PATH/src/vlog/code_2.v
read_vhdl ${PATH2}/src/vhd/code_3.vh
read_vhdl $PATH2/src/vhd/code_4.vh
And below is my script:
#!/usr/bin/tclsh
set input_file "input.tcl"
set input_fpt [open $input_file r]
set input_lines_all [read $input_fpt]
set input_lines [split $input_lines_all "\n"]
set PATH /user/abc/PATH
set PATH /user/dgc/PATH2
foreach line $input_lines {
if { [string match "read_verilog *" $line] || [string match "read_vhdl*" $line] } {
regexp {[read_verilog read_vhdl] (.*)} $line matched file
if { [string match {*[{P]AT[H}]*} $file] } {
set abs_file [string map {${PATH} /user/abc/PATH} $file]
} elseif { [string match "*PATH2*" $file] } {
set abs_file [string map {${PATH2} /user/abc/PATH2} $file]
} else {
set abs_file $file
}
if { ![file exists $abs_file] } {
puts $line
}
}
}
My script can't check $PATH and not sure if there is a more efficient way to do the job.

The simplest way of doing just the substitutions you want is with the string map command. Build up the map piece by piece first, then apply it to your string.
set map {}
lappend map {$PATH} $PATH
lappend map {${PATH}} $PATH
lappend map {$PATH2} $PATH2
lappend map {${PATH2}} $PATH2
set modified [string map $map $inputString]
You can apply the map as many times as you want once you have built it, and transform your data either a line at a time or all in one go. However, you might be better off just evaluating the file as a Tcl script. That can be an incredibly useful approach to some types of parsing (especially when used in conjunction with a safe interpreter) if the input is suitable, which yours appears to be.

Related

Tcl, if not not working

I'm trying to do a If not on a string match with Tcl. However, when I expect it not to match, it seems to be matching because when it shouldn't match it continues to "I don't want it to do this". Hope this makes sense. Inside the log.text file, it should contain, "This is a String."
set var1 "String"
set file [open "log.text" r]
while {[gets $file data] != -1} {
if {![string match *[string toupper $var1]* [string toupper $data]]} {
*I don't want it to do this
}
}
Your code appears to work fine:
$ cat log.text
This is a String
this line does not match
$ tclsh <<'END'
set var1 "String"
set file [open "log.text" r]
while {[gets $file data] != -1} {
if {![string match -nocase *$var1* $data]} {
puts "$data: does not match $var1"
}
}
END
outputs
this line does not match: does not match String
Ah, now you have clearly stated what you want: does the string exist in the file, yes or no. Here are some ways to accomplish that:
read the entire file, and string match against that.
set file [open log.text r]
set contents [read -nonewline $file]
close $file
set pattern_exists [string match -nocase *$var1* $contents]
if {$pattern_exists} {puts "$var1 found in file"}
read the file line-by-line until the pattern is found
set pattern_exists false
set file [open log.text r]
while {[gets $file line] != -1} {
if {[string match -nocase *$var1* $line]} {
set pattern_exists true
break
}
}
close $file
if {$pattern_exists} {puts "$var1 found in file"}
call out to grep to do the heavy lifting: grep exits with non-zero status when the pattern is not found, and exec thinks a non-zero exit status is an exception (see https://tcl.tk/man/tcl8.6/TclCmd/exec.htm#M27)
try {
exec grep -qi $var1 log.text
set pattern_exists true
} on error {e} {
set pattern_exists false
}
if {$pattern_exists} {puts "$var1 found in file"}
The code as you wrote it works… but I'm guessing it is a proxy for something else. If you are looking to see if an arbitrary string exists as a substring of a line, you are better off using string first instead of string match, since the latter has a few metacharacters (especially [ and ], which denote a set of characters) that can cause problems if you're not expecting them.
Try:
if {[string first [string toupper $var1] [string toupper $data]] >= 0} {
# The substring was there...
}
Alternatively, apply relevant backslash quoting when building your search pattern (possibly with string map) or use regexp, which has a useful find-a-literal mode:
if {[regexp -nocase ***=$var1 $data]} {
# The substring was there...
}
The ***= means “the rest of this pattern is a literal string to match” and we can pass -nocase as an option to allow us to not need to use string toupper.

Using string match to search a file

Want to search within a file using tcl to find a match.
Here is what I have.
set search "random string"
set file [open "file.txt" r]
while {![eof $file]} {
gets $file data
if {[ string match [string toupper $search] [string toupper $data] ] } {
//works
} else {
//doesnt work
}
}
File.txt
chicken.dinner:1439143130
random.strings:1439143130
more random strings:1439413390
random.strings.that.contain-special.characters:1439441566
Not able to match "random string" with what's in the file. Appreciate any help.
If you want to use only string match, then use the glob pattern * here.
set search "random string"
set file [open "file.txt" r]
while {[gets $file data] != -1} {
if {[string match *[string toupper $search]* [string toupper $data]] } {
puts "Found '$search' in the line '$data'"
} else {
# does not match case here
}
}
Output :
Found 'random string' in the line 'more random strings:1439413390'
Since we want to know whether the line contains the search string, we have added * at the beginning as well as in the end. It can match any number of sequence.
Reference : string match

TCL string match from file

I am trying to find a string in a file in TCL. Using the wish console, I get a successful match between two strings. When I read a string from a file and match it to its exact copy, it fails. I can see in Eclipse that the variables contain exactly the same string...that is unless there are invisible characters trailing. The following code never returns 1, even when the variables contain exactly the same strings.
set fileId [open $::InputFile "r"]
set file_data [read $fileId]
# Process data file
set data [split $file_data "\n"]
#search for string
foreach line $data {
set x $::StringToFind
set y $line
set z [string match x y]
puts $z
if [ string match $::StringToFind line ] {
return 1
}
}
You need to use the dollar sign on the line variable to get its value:
if [ string match $::StringToFind $line ] {
Also, it is a good practice to quote the condition of the if command:
if {[string match $::StringToFind $line]} {

TCL: Check file existance by SHELL environment variable (another one)

I have a file contain lines with path to the files. Sometimes a path contain SHELL environment variable and I want to check the file existence.
The following is my solution:
set fh [open "the_file_contain_path" "r"]
while {![eof $fh]} {
set line [gets $fh]
if {[regexp -- {\$\S+} $line]} {
catch {exec /usr/local/bin/tcsh -c "echo $line" } line
if {![file exists $line]} {
puts "ERROR: the file $line is not exists"
}
}
}
I sure there is more elegant solution without using
/usr/local/bin/tcsh -c
You can capture the variable name in the regexp command and do a lookup in Tcl's global env array. Also, your use of eof as the while condition means your loop will interate one time too many (see http://phaseit.net/claird/comp.lang.tcl/fmm.html#eof)
set fh [open "the_file_contain_path" "r"]
while {[gets $fh line] != -1} {
# this can handle "$FOO/bar/$BAZ"
if {[string first {$} $line] != -1} {
regsub -all {(\$)(\w+)} $line {\1::env(\2)} new
set line [subst -nocommand -nobackslashes $new]
}
if {![file exists $line]} {
puts "ERROR: the file $line does not exist"
}
}
First off, it's usually easier (for small files, say of no more than 1–2MB) to read in the whole file and split it into lines instead of using gets and eof in a while loop. (The split command is very fast.)
Secondly, to do the replacement you need the place in the string to replace, so you use regexp -indices. That does mean that you need to take a little more complex approach to doing the replacement, with string range and string replace to do some of the work. Assuming you're using Tcl 8.5…
set fh [open "the_file_contain_path" "r"]
foreach line [split [read $fh] "\n"] {
# Find a replacement while there are any to do
while {[regexp -indices {\$(\w+)} $line matchRange nameRange]} {
# Get what to replace with (without any errors, just like tcsh)
set replacement {}
catch {set replacement $::env([string range $line {*}$nameRange])}
# Do the replacement
set line [string replace $line {*}$matchRange $replacement]
}
# Your test on the result
if {![file exists $line]} {
puts "ERROR: the file $line is not exists"
}
}
TCL programs can read environment variables using the built-in global variable env. Read the line, look for $ followed by a name, look up $::env($name), and substitute it for the variable.
Using the shell for this is very bad if the file is supplied by untrusted users. What if they put ; rm * in the file? And if you're going to use a shell, you should at least use sh or bash, not tcsh.

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)
}
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.
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.
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
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"
}
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
}
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
}