Copy filename (with wildcard) in tcl - tcl

I'm trying to copy a file using a wildcard and it isn't being interpreted correctly.
set projName [lindex $argv 0]
puts "$projName chosen"
set sysdefPath "$projName/$projName.runs/impl_1/*.sysdef"
file copy -force $sysdefPath ./src/generatedFiles/$projName.hdf
I've tried a couple of variations of this but none have worked {*}, (*), [*], {.*}. The result of this places the wildcard (*) in the search path instead of trying to pattern match it.
What is the correct way to perform this?
Output,
$ test.tcl -tclargs proj
# set projName [lindex $argv 0]
# puts "$projName chosen"
proj chosen
# set sysdefPath "$projName/$projName.runs/impl_1/*.sysdef"
# file copy -force $sysdefPath ./src/generatedFiles/$projName.hdf
error copying "proj/proj.runs/impl_1/*.sysdef": no such file or directory
while executing
"file copy -force $sysdefPath ./src/generatedFiles/$projName.hdf"
(file "./src/projTcls/build_bitstream.tcl" line 5)

Your shell will expand file patterns wherever it finds them. Tcl is not like that: you have to explicitly ask for the list of files matching a pattern using the glob command: untested
set pattern $projName/$projName.runs/impl_1/*.sysdef
set sysdefPaths [glob -nocomplain -- $pattern]
switch -exact [llength $sysdefPaths] {
0 {error "No files match $pattern"}
1 {file copy -force [lindex $sysdefPaths 0] ./src/generatedFiles/$projName.hdf}
default {error "Multiple files match $pattern: [list $sysdefPaths]"}
}

Related

Wildcard Search with tcl glob

I am trying to search for directories within sub-directories and return any directories that match the wildcard glob search.
The folder structure is as outlined below...
Rootdir
-dir01
-dir_match_01-OLD
-dir_match_01
-dir02
-dir_match_02-OLD
-dir_match_02
-dir03
-dir_match_03-OLD
-dir_match_03
-...
I am searching for directories that would reside in dir01, dir02, dir03 and so on.
I am using the following glob call to recursively search through the directories, which seems to be working correctly...
set rootdir "/home/rootdir/"
set searchstring "*-OLD"
foreach dir [glob -nocomplain -dir $rootdir -type d -- *] {
set result [glob -nocomplain -dir $dir -type d -- $searchstring]
puts $result
}
What I am finding is if I don't use a wildcard in the $searchstring and use an exact directory name that exists I receive the output successfully. But if I then use a wildcard to search for all directories ending in *-OLD It successfully finds them put puts them all out on the same line.
/home/rootdir/dir01/directory01-OLD /home/rootdir/dir01/directory02-OLD /home/rootdir/dir01/directory03-OLD
I have tried to separate the entries by using regsub to replace the whitespace with \n but all it does is remove the whitespace...
/home/rootdir/dir01/directory01-OLD/home/rootdir/dir01/directory02-OLD/home/rootdir/dir01/directory03-OLD
Any suggestions in what I am doing wrong would be much appreciated, thanks.
The most obvious part is that glob always returns a list of names. You'd therefore need to do the innermost loop like this:
foreach dir [glob -nocomplain -dir $rootdir -type d -- *] {
foreach result [glob -nocomplain -dir $dir -type d -- $searchstring] {
puts $result
}
}
However, for a fixed depth search, I think you can do it like this:
foreach dir [glob -nocomplain -dir $rootdir -type d -- */$searchstring] {
puts $dir
}
If you need recursive (full directory tree) search, there are utility commands in Tcllib's fileutil package:
package require fileutil
proc myMatcher {pattern filename} {
# Does the filename match the pattern, and is it a directory?
expr {[string match $pattern $filename] && [file isdir $filename]}
}
set rootdir "/home/rootdir/"
set searchstring "*-OLD"
# Note the use of [list] to create a partial command application
# This is a standard Tcl technique; it's one of the things that [list] is designed to do
foreach dir [fileutil::find $rootdir [list myMatcher $searchstring]] {
puts $dir
}

Is there is a way of sourcing csh file in tcl script and bringing variables to the main shell?

I'm trying to source a some.cshrc during TCL script execution.
I get no error but the variables set in some.cshrc are not passed back to the shell.
When writing it like:
source some.cshrc
I'm getting an error.
Then I tried:
exec /bin/csh -c "source some.cshrc"
Please advise
Since Csh and Tcl are two completely different languages, you cannot simply load a Csh file in a Tcl script via a source command.
You can think of a workaround instead. Let us assume you want to load all the variables set with a setenv command. Example contents of some.cshrc file could look something like this:
setenv EDITOR vim
setenv TIME_STYLE long-iso
setenv TZ /usr/share/zoneinfo/Europe/Warsaw
You can write a Tcl script which reads this file line by line and searches for setenv commands. You can then reinterpret the line as a list and set an appropriate variable in a global namespace via an upvar command.
#!/usr/bin/tclsh
proc load_cshrc {file_name} {
set f [open $file_name r]
while {[gets $f l] >= 0} {
if {[llength $l] == 3 && [lindex $l 0] eq "setenv"} {
upvar [lindex $l 1] [lindex $l 1]
set [lindex $l 1] [lindex $l 2]
}
}
close $f
}
load_cshrc "some.cshrc"
puts "EDITOR = $EDITOR"
puts "TIME_STYLE = $TIME_STYLE"
puts "TZ = $TZ"
Please note, that since we do not run a Csh subshell any variable substitutions in the Csh script would not occur.

Find all files in a directory with an extension, and allow user to choose one to be set to a variable in TCL

I have a TCL script running from a directory where a variable amount of .txt files can be. I know that the following TCL command returns all .txt files in the current directory -
glob *.txt
However, in the case there is more than one text file, I need the user to choose which file to set a variable name equal to that file name.
For example, suppose in a directory I have
info1.txt
info2.txt
info3.txt
I need there to be a way for the user to choose one of those files to set VAR equal to the filename.
I also know that in this case, glob *.txt output would be:
info1.txt info2.txt info3.txt
I am just stumped on how to achieve this because I am new to TCL. I was thinking maybe have the user press 1 for file1, 2 for file2, and so on since they are separated by a whitespace in the output of glob *.txt
Thanks!
Here's some example code for getting standard input and reading a file. The command you're looking for is "gets".
glob.tcl
#!/usr/bin/tclsh
set files [glob *.txt]
set len [llength $files]
if {$len == 0} {
puts "no files"
} elseif {$len == 1} {
puts "found 1 file. [lindex $files 0]"
} else {
for {set i 1} {$i <= $len} {incr i} {
puts "${i}. [lindex $files $i-1]"
}
puts "Enter file number to view or 'q' to quit."
while {[set n [gets stdin]] != "q"} {
if {[catch {set filename [lindex $files $n-1]} err] || $filename == ""} {
puts "Error selecting #${n}."
} else {
set fp [open $filename r]
set file_data [read $fp]
close $fp
puts "Contents of ${filename}."
puts $file_data
break
}
}
}
Output:
./glob.tcl
1. test.txt
2. test2.txt
3. test3.txt
4. test4.txt
Enter file number to view or 'q' to quit.
a
Error selecting #a.
q
% ./glob.tcl
1. test.txt
2. test2.txt
3. test3.txt
4. test4.txt
Enter file number to view or 'q' to quit.
1
Contents of test.txt.
hello world
hello world2

copy files in one location to another and modify the copied file by placing some data at particular location in tcl?

i have to perform following operation..
copy file from one location to another
search a word in the given file
and move the file pointer to beginning of that line
place the data in that location which are copied from other file...
3 files are as follows:
C:\program Files(X86)\Route\*.tcl
C:\Sanity_Automation\Route\*.tcl
C:\Script.tcl
First i need to copy files from Route folder in Program Files to
Sanity_Automation\Route*.tcl
Then i need to search "CloseAllOutputFile keyword in
C:/Sanity_Automation/Route/SystemTest.tcl
once found, move cursor to the beginning of that line where "CloseAllOutputFile " keyword found.
and place data found on script.tcl to that location.
Firstly, that first "file" is actually a pattern. We need to expand that to a list of real filenames. We do that with glob.
# In braces because there are backslashes
set pattern {C:\Program Files(X86)\Route\*.tcl}
# De-fang the backslashes
set pattern [file normalize $pattern]
# Expand
set sourceFilenames [glob $pattern]
Then we want to copy them. We could do this with:
set target {C:\Sanity_Automation\Route\}
file copy {*}$sourceFilenames [file normalize $target]
But really we also want to build up a list of moved files so that we can process them in the next step. So we do this:
set target {C:\Sanity_Automation\Route\}
foreach f $sourceFilenames {
set t [file join $target [file tail $f]]
file copy $f $t
lappend targetFilenames $t
}
OK, now we're going to do the insertion processing. Let's start by getting the data to insert:
set f [open {C:\Script.tcl}]
set insertData [read $f]
close $f
Now, we want to go over each of the files, read them in, find where to do the insertion, actually do the insertion if we find the place, and then write the files back out. (You do text edits by read/modify-in-memory/write rather than trying to modify the file directly. Always.)
# Iterating over the filenames
foreach t $targetFilenames {
# Read in
set f [open $t]
set contents [read $f]
close $f
# Do the search (this is the easiest way!)
if {[regexp -indices -line {^.*CloseAllOutputFile} $contents where]} {
# Found it, so do the insert
set idx [lindex $where 0]
set before [string range $contents 0 [expr {$idx-1}]]
set after [string range $contents $idx end]
set contents $before$insertData$after
# We did the insert, so write back out
set f [open $t "w"]
puts -nonewline $f $contents
close $f
}
}
Normally, I'd do the modify as part of the copy, but we'll do it your way here.
Try this:
set sourceDir [file join / Files(x86) Route]
set destinationDir [file join / Sanity_Automation Route]
# Read the script to be inserted
set insertFnm [file join / Script.tcl]
set fil [open $insertFnm]
set insertData [read $fil]
close $fil
# Loop around all the Tcl scripts in the source directory
foreach inFnm [glob [file join $sourceDir *.tcl]] {
# Determine the name of the output file
set scriptName [file tail $inFnm]
set outFnm [file join $destinationDir $scriptName]
# Open source and destination files, for input and output respectively
set inFil [open $inFnm]
set outFil [open $outFnm w]
while {![eof $inFil]} {
set line [gets $inFil]
if {[string match *CloseAllOutputFile* $line]} {
puts $outFil $insertData
puts $outFil ""; # Ensure there's a newline at the end
# of the insertion
}
puts $outFil $line
}
# Close input and output files
close $inFil
close $outFil
}
It seems to work for me.

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
}