Preserve file ownership while copying [tcl] [duplicate] - tcl

What is Tcl equivalent for UNIX "cp -pL" command?
I cannot find it in file command description

For a single file:
a) Get the real path to the file.
b) Copy it.
c) Set the attributes, modification time and access time.
Unfortunately, there does not appear to be any way to set the change time (creation time on Windows).
set fn sourcefn
set tofn targetfn
set nfn [file normalize [file readlink $fn]]
file copy -force $nfn $tofn
foreach {key} [list attributes mtime atime] {
set temp [file $key $nfn]
file $key $tofn {*}$temp
}
This is the pure Tcl solution that will work on unix, Mac OS X and Windows. Of course you could just do:
exec cp -pLf $from $to
References: file

Related

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

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

How to delete a specific text file (based on name) in TCL after searching all folders, not just the current directory?

How do I search for a particular file name in the entire directory and then delete it in TCL?
The easy way is using the fileutil::traverse package from tcllib, which safely searches a directory tree for matching files. You can then call file delete with those filenames.
Example:
#!/usr/bin/env tclsh
package require fileutil::traverse
# Look for all foo.txt files regardless of directory.
# Use this to get the OS-specific path delimiter instead of a hardcoded / or \\
set pattern [file join * foo.txt]
fileutil::traverse findFoo . -filter [list string match $pattern]
# I suggest a dry run first to make sure your filter is returning just the
# appropriate filename(s).
puts [findFoo files]
# When satisfied, delete for real.
# file delete [findFoo files]
Perhaps this is the easiest way, although it is not portable:
puts [exec find . -name $filenameToDelete -print]
If that find the right files, you can do this
exec find . -name $filenameToDelete -delete

I made several attempts to create Tcl variables from file content, always failed. What can it be?

I made an outline of a basic example. To simplify the scenario I am facing on time!
To have a reflection of what is happening to me, let's first follow the line of logical reasoning.
Let's go to the Walkthrough:
1 - I create the parent directory:
$ mkdir -p /tmp/tmp.AbiGaIl
2 - Then I create the subdirectories Children:
$ mkdir -p /tmp/tmp.AbiGaIl/A
$ mkdir -p /tmp/tmp.AbiGaIl/B
$ mkdir -p /tmp/tmp.AbiGaIl/C
3 - Now, I will populate the subdirectories with dummy files:
$ touch /tmp/tmp.AbiGaIl/A/1.png /tmp/tmp.AbiGaIl/A/2.png /tmp/tmp.AbiGaIl/A/3.png
$ touch /tmp/tmp.AbiGaIl/B/1.png /tmp/tmp.AbiGaIl/B/2.png /tmp/tmp.AbiGaIl/B/3.png
$ touch /tmp/tmp.AbiGaIl/C/1.png /tmp/tmp.AbiGaIl/C/2.png /tmp/tmp.AbiGaIl/C/3.png
4 - Anyway, we can check if everything is ok.
$ cd /tmp/tmp.AbiGaIl/
$ pwd
$ ls *
5 - Verified and confirmed, it's time to start working the script.
$ echo "A" > /tmp/.directory
$ DIR=$(cat /tmp/.directory)
$ cd $DIR
$ ls *.*
NOTE - "This last step is the main cause of my headache, not in Bash, but in Tcl." _
Ready! The bourn shell operation [sh] was a success.
Now, starting from this same line of reasoning. Let's start with the Tcl language [tclsh].
In this step, you can only follow the flow from the previous step (bash), so as not to create the file among other features. Just let's reuse something done before. Follow:
1º
set folder "/tmp/tmp.AbiGaIl"
#Return : /tmp/tmp.AbiGaIl
2º
% set entry [cd $folder]
3º
% pwd
#Return : tmp.meLBJzexGc
4º
% puts [glob -type d $entry *]
#Return : . C B A
5º
% set file [open "/tmp/.directory"]
#Return : file3
6º
% set text [read $file]
#Return : A
7º
% close $file
8º
% set view "$text"
#Return : A
9º
% puts [glob -nocomplain -type f -directory $view -tails *.png]
#Return : ?
You should expect this, but do not enter the directory. I did it on purpose for you to understand or what happens if I create.
You will see in the next lines below what happens. Continue..
So let's create a variable to get there.
% set view [cd $text]
Return : couldn't change working directory to "A": no such file or directory
Tai a big problem!
How can I go there and check the directory contents if it is giving me this unknown error!
% puts [glob -nocomplain -type f -directory $view -tails *.png]
If you create a direct insertion of the direct directory name in glob without going through this variable that is fed by a file containing the name of that directory. This works, see:
% puts [glob -nocomplain -type f -directory A -tails *.png]
#Return: 3.png 2.png 1.png
But that's not what I want. I really need this file with the subdirectory name (s).
This file is used every time a widget button is pressed, so an "exec echo "A" > /tmp/.directory" command is triggered according to the letter of the alphabet that corresponds to the name of each group and therefore can be accessed. .
If anyone can explain to me why this is giving error while accessing. Be sure to reply or even comment. Your help will be most welcome!
Firstly, I advise never using cd in scripts because it changes the interpretation of filenames. Instead, it is much simpler in the long run to use fully qualified filenames everywhere.
Secondly, cd never returns anything other than the empty string. Don't use the empty string as a filename, it confuses things (and not just Tcl!)
set folder "/tmp/tmp.AbiGaIl"
set f [open "/tmp/.directory"]
gets $f dir
close $f
set subfolder [file join $folder $dir]
set files [glob -nocomplain -dir $subfolder -type f *.png]
foreach filename $files {
puts "found [file tail $filename] in $subfolder :: $filename"
}
If you just want the last part of the name of a particular file, use file tail when you need it. But keep the full filename around for when you are talking about the file and not just its name: it is just so much more reliable to do so. (If you ever work with GUI apps, this is vital: you don't have anything like the control over the initial directory there.)
That advice also applies (with different syntax) to every other programming language you might use for this task.

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.

TCL- script to output a file which contains size of all the files in the directry and subdirectories

Please help me with the script which outputs the file that contains names of the files in subdirectories and its memory in bytes, the arguement to the program is the folder path .output file should be file name in 1st column and its memory in second column
Note:folder contains subfolders...inside subfolders there are files
.I tried this way
set fp [open files_memory.txt w]
set file_names [glob ../design_data/*/*]
foreach file $file_names {
puts $fp "$file [lindex [exec du -sh $file] 0]"
}
close $fp
Result sample:
../design_data/def/ip2.def.gz 170M
../design_data/lef/tsmc13_10_5d.lef 7.1M
But i want only file name to be printed that is ip2.def.gz , tsmc13_10_5d.lef ..etc(not the entirepath) and file memorry should be aligned
TCL
The fileutil package in Tcllib defines the command fileutil::find, which can recursively list the contents of a directory. You can then use foreach to iterate over the list and get the sizes of each of them with file size, before producing the output with puts, perhaps like this:
puts "$filename\t$size"
The $filename is the name of the file, and the $size is how large it is. You will have obtained these values earlier (i.e., in the line or two before!). The \t in the middle is turned into a TAB character. Replace with spaces or a comma or virtually anything else you like; your call.
To get just the last part of the filename, I'd do:
puts $fp "[file tail $file] [file size $file]"
This does stuff with the full information about the file size, not the abbreviated form, so if you really want 4k instead of 4096, keep using that (slow) incantation with exec du. (If the consumer is a program, or a programmer, writing out the size in full is probably better.)
In addition to Donal's suggestion, there are more tools for getting files recursively:
recursive_glob (from the Tclx package) and
for_recursive_glob (also from Tclx)
fileutil::findByPattern (from the fileutil package)
Here is an example of how to use for_recursive_glob:
package require Tclx
for_recursive_glob filename {../design_data} {*} {
puts $filename
}
This suggestion, in combination with Donal's should be enough for you to create a complete solution. Good luck.
Discussion
The for_recursive_glob command takes 4 arguments:
The name of the variable representing the complete path name
A list of directory to search for (e.g. {/dir1 /dir2 /dir3})
A list of patterns to search for (e.g. {*.txt *.c *.cpp})
Finally, the body of the for loop, where you want to do something with the filename.
Based on my experience, for_recursive_glob cannot handle directories that you don't have permission to (i.e. on Mac, Linux, and BSD platforms, I don't know about Windows). In which case, the script will crash unless you catch the exception.
The recursive_glob command is similar, but it returns a list of filenames instead of structuring in a for loop.