Read all files in folder tcl/tk - tcl

I want to read all files containing .sdc
The folder includes
alpha.sdc
beta.sdc
gamma.rpt
I try cmd
set a [open "proj/plrs/*.sdc" r]
but it not working

#Andreas has the right ideas.
set files [glob proj/plrs/*.sdc]
set combined ""
foreach file $files {
set fh [open $file r]
append combined [read $fh]
close $fh
}
To use the glob characters with cat, you'll need a shell to interpret them:
set combined [exec sh -c {cat proj/plrs/*.sdc}]
or expand the results of glob
set combined [exec cat {*}[glob proj/plrs/*.sdc]]
You could use tcllib
package require fileutil
set combined [fileutil::cat {*}[glob proj/plrs/*.sdc]]
Note that glob doesn't sort the files like the shell does, so you may want
set files [lsort [glob $pattern]]

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
}

Copy filename (with wildcard) in 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]"}
}

TCL script to replace C file line

I have C file at
C:\SVN\Code\fileio.c
This reads 2 audio files as
tuningFile = fopen("../../simulation/micdata.bin", "rb");
mic1File = fopen("../../simulation/mic1.pcm", "rb");
I need to write TCL script code that will read the C file, and replace these 2 occurrences to
tuningFile = fopen("C:/SVN/simulation/micdata.bin", "rb");
mic1File = fopen("C:/SVN/simulation/mic1.pcm", "rb");
Can anyone give a compact example for something like below:
read file line wise
search for something like tuningFile = fopen(
extracting path from it and change it to absolute path
combine it with *tuningFile = fopen(
replace original line with modified line at same location
Thanks
sedy
The key is that you actually want to replace:
fopen("../../simulation/
with
fopen("C:/SVN/simulation/
That's easily done with string map. The rest of your problem is then just a matter of doing the file I/O, and pretty much any C source file that can be compiled by an ordinary compiler is best processed by loading it all into memory at once:
set filename {C:\SVN\Code\fileio.c}
set mapping [list {fopen("../../simulation/} {fopen("C:/SVN/simulation/}]
# Slurp the file in
set f [open $filename]
set data [read $f]
close $f
# Apply the mapping
set data [string map $mapping $data]
# Make the original a backup
file rename $filename $filename.bak
# Write back with a separate open
set f [open $filename w]
puts -nonewline $f $data
close $f
If you prefer, you can get the filename as an argument using, say, [lindex $argv 0]. The rest of the code doesn't care.
Here's a version that extracts the filename and uses file normalize on it:
set f [open $filename r]
set code [read $f]
close $f
set code [subst -novar -noback [regsub -all {((?:tuningFile|mic1File) = fopen\(")([^"]+)} $code {\1[file normalize "\2"]}]]
Breaking that up,
this command
regsub -all {((?:tuningFile|mic1File) = fopen\(")([^"]+)} $code {\1[file normalize "\2"]}
will find the string tuningFile = fopen("../relative/file (or "mic1file = ...") and replace it with the text
tuningFile = fopen("[file normalize "../relative/file"]
Then we feed that to subst so that embedded commands can be substituted, executing that file normalize command, resulting in the text
tuningFile = fopen("/full/path/to/file
Take 2: handle brackets in C code
$ pwd
/home/jackman/tmp/base/SVN/Code
$ tree ../..
../..
├── SVN
│   └── Code
│   ├── fileio.c
│   └── normalize.tcl
└── simulation
├── mic1.pcm
└── micdata.bin
3 directories, 4 files
$ cat fileio.c
int tuningFix[MAXTUNING];
tuningFile = fopen("../../simulation/micdata.bin", "rb");
mic1File = fopen("../../simulation/mic1.pcm", "rb");
$ cat normalize.tcl
#! tclsh
package require fileutil
set code [fileutil::cat [lindex $argv 0]]
# protect existing brackets
set bracketmap [list \[ \x01 \] \x02]
set code [string map $bracketmap $code]
# normalize filenames
set code [
subst -novar -noback [
regsub -all {((?:tuningFile|mic1File) = fopen\(")([^"]+)} $code {\1[file normalize "\2"]}
]
]
# restore brackets
set code [string map [lreverse $bracketmap] $code]
puts $code
$ tclsh normalize.tcl fileio.c
int tuningFix[MAXTUNING];
tuningFile = fopen("/home/jackman/tmp/base/simulation/micdata.bin", "rb");
mic1File = fopen("/home/jackman/tmp/base/simulation/mic1.pcm", "rb");
package require fileutil
set filename C:/SVN/Code/fileio.c
set mapping [list {fopen("../../simulation/} {fopen("C:/SVN/simulation/}]
proc replace {mapping data} {
string map $mapping $data
}
::fileutil::updateInPlace $filename [list replace $mapping]
Should work too. (Definition of mapping nicked from Donal.) updateInPlace calls the command prefix in its second argument, passes the contents of the file to that command, and updates the file with the result from the command.
This is very nearly the same procedure as in Donal's answer, expressed in higher-level code. If you want a backup copy, do this before calling updateInPlace:
file copy $filename [file rootname $filename].bak
Documentation: fileutil package, list, proc, set, string
based on great help from all users who commented, I was able to do the task as
proc replaceFileTemp {} {
global pth_fileio_orig
# create backup for copy back
set pth_backup [file rootname $pth_fileio_orig].bak
file copy $pth_fileio_orig $pth_backup
#get current file path
set thisFilePth [ dict get [ info frame [ info frame ] ] file ]
# get folder for current file
set thisFileFolderPth [file dirname $thisFilePth]
# set the replacement string/path
set replacementPth [file dirname $thisFileFolderPth]
# obtain original string to be replaced
set origPth "../../simulation/toplevel"
# download package for file manipulation
package require fileutil
set mapping [list $origPth $replacementPth]
proc replace {mapping data} {
string map $mapping $data
}
# replace original string with replacement string for all occurrences in file
::fileutil::updateInPlace $pth_fileio_orig [list replace $mapping]
}
# set the path to toplevel C file
set pth_fileio_orig [file normalize "../../../fileio.c"]
replaceFileTemp

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.

TCL - find a regular pattern in a file and return the occurrence and number of occurrences

I am writing a code to grep a regular expression pattern from a file, and output that regular expression and the number of times it has occured.
Here is the code: I am trying to find the pattern "grep" in my file hello.txt:
set file1 [open "hello.txt" r]
set file2 [read $file1]
regexp {grep} $file2 matched
puts $matched
while {[eof $file2] != 1} {
set number 0
if {[regexp {grep} $file2 matched] >= 0} {
incr number
}
puts $number
}
Output that I got:
grep
--------
can not find channel named "qwerty
iiiiiii
wxseddtt
lsakdfhaiowehf'
jbsdcfiweg
kajsbndimm s
grep
afnQWFH
ACV;SKDJNCV;
qw qde
kI UQWG
grep
grep"
while executing
"eof $file2"
It's usually a mistake to check for eof in a while loop -- check the return code from gets instead:
set filename "hello.txt"
set pattern {grep}
set count 0
set fid [open $filename r]
while {[gets $fid line] != -1} {
incr count [regexp -all -- $pattern $line]
}
close $fid
puts "$count occurrances of $pattern in $filename"
Another thought: if you're just counting pattern matches, assuming your file is not too large:
set fid [open $filename r]
set count [regexp -all -- $pattern [read $fid [file size $filename]]]
close $fid
The error message is caused by the command eof $file2. The reason is that $file2 is not a file handle (resp. channel) but contains the content of the file hello.txt itself. You read this file content with set file2 [read $file1].
If you want to do it like that I would suggest to rename $file2 into something like $filecontent and loop over every contained line:
foreach line [split $filecontent "\n"] {
... do something ...
}
Glenn is spot on. Here is another solution: Tcl comes with the fileutil package, which has the grep command:
package require fileutil
set pattern {grep}
set filename hello.txt
puts "[llength [fileutil::grep $pattern $filename]] occurrences found"
If you care about performance, go with Glenn's solution.