Tcl: Copy all files in directory structure to a single location outside - tcl

I need to recursively find all files that end with .vhd inside a directory and then copy all of them to the directory where the tcl script has been called from. This is the same directory that the user is in when the TCL script is invoked.
So what I need to so to find all .vhd files recursively and regardless of where they are, copy all of them into a single folder. I tried to use glob search but it seems perhaps that it cannot do recursive search. How can this be achieved using TCL?

You can use fileutil::findByPattern from tcllib to get the file names and copy them in a loop:
#!/usr/bin/env tclsh
package require fileutil
proc copy_tree {from_dir pat to_dir} {
foreach f [::fileutil::findByPattern $from_dir -glob -- $pat] {
file copy -force -- $f $to_dir
}
}
copy_tree some/dir *.vhd destination/

Unless you can provide for a tcllib installation for your environment, you will have to implement the directory traversal yourself. Below is but one of the many options (adapted from ftw_4 at Tclers' Wiki; this assumes Tcl 8.5+):
proc copy_tree {from_dirs pat to_dir} {
set files [list]
while {[llength $from_dirs]} {
set from_dirs [lassign $from_dirs name]
set from_dirs [list {*}[glob -nocomplain -directory $name -type d *] {*}$from_dirs]
lappend files {*}[glob -nocomplain -directory $name -type f $pat]
}
if {[llength $files]} {
file copy -force -- {*}$files $to_dir
}
}
Some remarks: This implements an iterative, depth-first directory traversal, and
glob is used twice, once to collect sub-directories, once to collect the source files.
file copy is executed just once (for the entire collection of source files), not for every source file.
The -nocomplain flag on glob is convenient, but maybe not useful in your scenario (whatever that might be).

Related

TCL script to go through all folders in a directory and perform a function

File structure
File1
test.pdb
xyz.txt
File2
test.pdb
xyz.txt
File3
test.pdb
xyz.txt
I want to loop in through all folders in the directory and run the following code which is in textfile autopsf.tcl on Tk console :
package require autopsf
mol new test.pdb
autopsf -mol 0
package require solvate
solvate test_autopsf.psf test_autopsf.pdb -t 5 -o solvate
package require autoionize
autoionize -psf solvate.psf -pdb solvate.pdb -neutralize
I am running the following code at the moment :
for d in ./*/ ; do
source autopsf.tcl
done
If you don't care about the order, you can do:
foreach dir [glob -type d *] {
# safety check
if {![file exists $dir/test.pdb]} continue
# code to do the work here; note that we have directories
}
You can probably factor out the package require calls. Well-designed packages can live together right, and putting them at the top is a useful way to make dependencies evident.
If you want the directories sorted, apply lsort to the output of glob. The default order from glob is whatever the OS gives us the directory entries in, and can depend on all sorts of things (including file ages and so on) so it should not be relied upon in code where a definite of processing matters.
This code worked for me:
foreach file [glob -nocomplain "./*/"] {
cd $file
source autopsf.tcl
cd ..
}

How to `rm -rf *` in TCL

I want to delete all files in a directory using TCL. (I'm using Xilinx Vivado's TCL console under Win 10.) I found that in TCL documentation that
file delete ?-force? ?- -? pathname ?pathname ... ?
should work.
But
file delete -force -- [glob *]
does nothing.
What's the wrong with that?
Make that
file delete -force -- {*}[glob *]
... so that the path names returned by [glob] are turned into multiple arguments to [file delete] (using the expansion operator {*}), rather than one argument representing the one list of a path names (read by [file delete] as one, well complex file path).
Alternatively, on Tcl older than 8.5, use an explicit loop:
foreach path [glob *] {
file delete -force -- $path
}
Additional things for you do consider:
do you need to be concerned about deleting files, not directories? Consider the -type option for the glob command.
if you need to work recursively, don't reinvent the wheel and use tcllib. The fileutil::traverse and fileutil packages are relevant.

recursive search for file with .log extension in all sub directories

What is the command in TCL to do recursive search for file with .log extension in current directory and sub directories.
I have tried glob -type f *.log but it only looks in current directory.
The fileutil package in Tcllib is ideal for this sort of thing. In particular, fileutil::find does exactly what you want.
package require fileutil
# We need a helper procedure here. This is a trivial wrapper round [string match]
proc is_log {name} {
return [string match *.log $name]
}
set log_files [fileutil::find [pwd] is_log]

How to search a a directory tree in TCL for files that end in .so or .a and build a list of those directories

I'm trying to write a set of TCL scripts that helps setup a user's environment for a set of libraries that are not in their standard LD_LIBRARY_PATH as part of a support solution.
Since the system is rather sparse in terms of what I can install, I don't really have access to any TCL extensions and am trying to do this in base TCL as much as possible. I'm also relatively new to TCL as a language.
What I'd like to do is search through a directory structure and locate the directories that have .so and .a files in them, build a list of those, and, eventually add them to the user's $LD_LIBRARY_PATH variable.
If I were doing this in a shell, I'd just use find with something like this:
find /dir -type f \( -name "*.so" -o -name "*.a" \) | awk -F/ 'sub(FS $NF,x)' | sort -u
I could hard-code the paths, but we want a single set of scripts that can manage several different applications.
Any ideas would be very much appreciated.
Tcllib has a fileutil module that does a recursive find:
package require fileutil
set filenames [::fileutil::findByPattern /dir -glob {*.so *.a}]
foreach filename $filenames {
if {[file isfile $filename]} {
set dirs([file dirname $filename]) 1
}
}
puts [array names dirs]
If you want to use this, but can't install something, you can just take the procedures and add them to your code (with the appropriate attribution) -- http://core.tcl.tk/tcllib/dir?ci=63d99a74f4600441&name=modules/fileutil
Otherwise, just call the system's find command and parse the output (assume that your filenames do not contain newlines).
set filenames [split [exec find /dir -type f ( -name *.so -o -name *.a )] \n]
And the loop to extract the unique directories is similar. As a side benefit, the find invocation is actually easier to read because you don't have to escape all the chars that are special to the shell.

How to run tcl script inside other tcl script?

I have two tcl scripts. I want to run the second script when the first finished. How can I do it?
Depends on what do you really mean.
One way is to write a third ("master") script which would do
source /the/path/to/the/first.tcl
source /the/path/to/the/second.tcl
Another way is to just add the second call to source from the above example to the bottom of the first script.
Amendment to the first approach: if the scripts to be executed are located in the same directory as the master script, an idiomatic way to source them is
set where [file dirname [info script]]
source [file join $where first.tcl]
source [file join $where second.tcl]
This way sourcing will work no matter what the current process's directory is and where the project directory is located.
While this is generally a correct answer, because the question was not precisely formulated there are tons of ways to achieve the goal of running Tcl code from within Tcl.
I want to get into this in detail because understanding the execution of code is one major point in understanding Tcl itself.
There is source
The source command should not be confound with executing scripts in a classical way, what I think the thread starter has asked.
The source command is like the "include" command in c/perl/php.
Languages like java or python on the other hand only have "import" mechanisms.
The difference is that those languages create a internal database of available packages, who are linked to the corresponding source/binary/bytecode files. By writing a import statement, linked source or bytecode or binary files are loaded. This allows more in-depth dependency management without writing additional code.
In Tcl this can be achieved with namespaces and the package require command.
Example:
Suppose you have this source.tcl:
proc foo {bar} {puts "baz"}
set BAM "BOO"
Now, you have your "master" script like you call it. I call it "main". It has the content:
set BAM {my important data}
source source.tcl
#also the function foo can now be used because the source reads the whole script
foo {wuz}
set BAM
#will output "BOO"
The exec command
If you can live with additional overhead of starting a whole new interpreter instance you could also do:
set BAM {my important data}
exec tclsh source.tcl
#The variable BAM will not be modified. You can not use the function foo.
The eval command
The command eval can evaluate a string or a list (in Tcl everything is a string) like it would be programmed code.
You would have to load the complete source file to a string. And then use eval, to evaluate the code within a separate scope, to not overwrite stuff in your main source file.
set fp [open "somefile" r]
set code_string [read $fp]
close $fp
eval $code_string
You just need to use source to run the 2nd script.
source "/tmp/whatever.tcl"
Simplest possible working example I could find:
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ tclsh main.tcl
hello world
7
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat main.tcl
lappend auto_path /home/thufir/NetBeansProjects/spawnTelnet/telnet/api
package require weather 1.0
tutstack::hello
set A 3
set B 4
puts [tutstack::sum $A $B]
#puts [tutstack::hello "fred"]
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat api/weather.tcl
package provide weather 1.0
package require Tcl 8.5
namespace eval ::tutstack {
}
proc ::tutstack::hello {} {
puts "hello world"
}
proc ::tutstack::sum {arg1 arg2} {
set x [expr {$arg1 + $arg2}];
return $x
}
proc ::tutstack::helloWorld {arg1} {
return "hello plus arg"
}
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$ cat api/pkgIndex.tcl
# Tcl package index file, version 1.1
# This file is generated by the "pkg_mkIndex" command
# and sourced either when an application starts up or
# by a "package unknown" script. It invokes the
# "package ifneeded" command to set up package-related
# information so that packages will be loaded automatically
# in response to "package require" commands. When this
# script is sourced, the variable $dir must contain the
# full path name of this file's directory.
package ifneeded weather 1.0 [list source [file join $dir weather.tcl]]
thufir#dur:~/NetBeansProjects/spawnTelnet/telnet$