Foreach output into a file - tcl

Here is the code I tried
foreach_in_collection t1 [ ga ...] {
set f1 [ga $t1 type_name]
set f2 [ga $t1 bb]
puts $f1
puts $f2
puts $file1 $f1 $f2
}
I want to write two output $f1 and $f2 to $file1, please let me know
above puts $file1 [list $f1 $f2] also doesn't works..

Try this:
set fd [open out.txt w]
foreach {x y} [list a b c d e] {
puts x=$x
puts y=$y
puts $fd $x
puts $fd $y
}
close $fd
Output file (before):
$ cat out.txt
cat: out.txt: No such file or directory
$
Execution output:
$ tclsh foo.tcl
x=a
y=b
x=c
y=d
x=e
y=
$
Output file (after):
$ cat out.txt
a
b
c
d
e
$

Sharad's solution is perfect. There is one more way if you want to try.
SBORDOLO-M-V1VG:EXPERIMENT sbordolo$ cat a11
exec rm -rf out1.txt ;# No need to worry about file is there or not.
exec touch out1.txt
exec chmod 777 out1.txt ;#You can give a write permission here. I have given 777 just like that
foreach {x y} [list a b c d e f] {
puts x=$x
puts y=$y
exec echo $x >> out1.txt
exec echo $y >> out1.txt
}
When you run the script:
SBORDOLO-M-V1VG:EXPERIMENT sbordolo$ tclsh a11
x=a
y=b
x=c
y=d
x=e
y=f
SBORDOLO-M-V1VG:EXPERIMENT sbordolo$ cat out1.txt
a
b
c
d
e
f

Related

How to recall a variable within the "string map" option in tcl

I want to sequentially update a series of file, from a tmp file distTmp.RST to sequentially dist1.RST, dist2.RST, etc..
For me, the fileutil package in vmd text interface is not working as follows:
My tcl code (add.tcl):
package require fileutil
set F 20.5
set Ff ""
for {set f 0} {$f < 70} {incr f} {
set F [expr {$F+1}]
lappend Ff $F
}
puts $Ff
for {set f 0} {$f < 70} {incr f} {
set M [lindex $Ff $f]
set N [expr {$f+1}]
package require fileutil
::fileutil::updateInPlace distTmp.RST {string map {WWW $M}}
::fileutil::cat dist$N.RST
}
========
The error occurring is
vmd > source add.tcl
can't find package fileutil
vmd >
========
Moreover, when I do not use "fileutil" package, my script is as follows:
set F 20.5
set Ff ""
for {set f 0} {$f < 70} {incr f} {
set F [expr {$F+1}]
lappend Ff $F
}
puts $Ff
for {set f 0} {$f < 70} {incr f} {
set M [lindex $Ff $f]
set N [expr {$f+1}]
set dat [open "distTmp.RST" r]
set out [open "dist$N.RST" w]
while {[gets $dat line] >= 0} {
set newline [string map {WWW $$M} $line]
puts $out $newline
}
}
======
But, there is a problem in recalling the variable $M within a string, and my required output files are as follows:
(base) [Prathit#master]~/APP/OnlyAPP/AlphaFold2/770_res/Charmm-Gui_Dimer-units/E2-E2_3222212666/charmm-gui-3222212666/amber/RSTfiles_Equil>head -n +4 dist1.RST dist2.RST
=> dist1.RST <==
&rst iat = -1, -1, r2 = $$M, r2a = $$M,
==> dist2.RST <==
&rst iat = -1, -1, r2 = $$M, r2a = $$M,
==========
In the above, $$M should be sequentially 21.5, 22.5, and so on....
Kindly let me know of a possible solution.
The fileutil package is part of tcllib. Check that your variable auto_path includes a path where Tcl can find tcllib and fileutil.
Your list for string map is in curly braces, so it's using a literal dollar sign for $M instead of the value of the variable named M.
Change curlies to double quotes or use the list command, as answered in a comment already.
$$M is usually not okay in Tcl. Are you trying to do double interpolation? If so, I recommend using set with one argument, to retrieve a value instead of to set a value. You can use $$ in a subst command, but that's not my preference.
set name John
set var_name name
puts $$var_name --> $name
puts [set $var_name] --> John
puts [set [set var_name]] --> John
puts [subst $$var_name] --> John

Reading and re-writing specific lines on TCL

Dears, I have the following information (x y z) in a "data.dat" file:
24.441 53.481 41.474
23.920 53.389 42.572
24.470 52.228 42.012
24.875 51.313 42.524
23.663 51.323 42.701
I require to re-write the information as following:
{24.441 53.481 41.474} {23.920 53.389 42.572}
{23.920 53.389 42.572} {24.470 52.228 42.012}
{24.470 52.228 42.012} {24.875 51.313 42.524}
{24.875 51.313 42.524} {23.663 51.323 42.701}
This is for a large data file. How could I do that in TCL. Thanks in advance for the help.
set infile "data.dat"
set outfile [file tempfile]
set in [open $infile r]
set out [open $outfile w]
gets $in prev_line
while {[gets $in line] != -1} {
puts $out [format "{%s} {%s}" $prev_line $line]
set prev_line $line
}
close $in
close $out
# remove next line if you don't need to keep a backup of the initial file
file link -hard "${infile}.bak" $infile
# and overwrite the original file with the new contents
file rename -force $outfile $infile
or, call out to GNU awk to do it
exec gawk -i inplace {
NR == 1 {prev = $0; next}
{printf "{%s} {%s}\n", prev, $0; prev = $0}
} data.dat

Command glob : type

Here my command :
foreach fic [glob -nocomplain -dir $dir -types {f d r} *] {
set infofile [list [file tail $fic] [file mtime $fic] [file atime $fic]]
# ...
}
Only I have an error : couldn't read directory "/Users/..." permission denied...
My solution is to add this command : file readable
foreach fic [glob -nocomplain -dir $dir -types {f d} *] {
if {![file readable $fic]} continue
set infofile [list [file tail $fic] [file mtime $fic] [file atime $fic]]
# ...
}
I thought when I added the r -type this kind of error did not appear.It’s a misunderstanding of the documentation ?
Permissions on Windows are complex, to the point where you can only be really sure that you've got permission to read a file immediately after you've successfully opened it for reading. The indications from glob and file readable are not definitive. This is the case on other operating systems, and in any case there's a race condition: the user could change the permissions between checking with file readable and calling some other operation. Because of that, while you can use glob -type r, you should not rely on it. It simply can't be guaranteed to be correct.
The fix for this? Handle errors from calls properly.
foreach fic [glob -nocomplain -dir $dir -types {f d r} *] {
try {
# More efficient than calling [file mtime] and [file atime] separately
file stat $fic data
} on error {} {
# Couldn't actually handle the file. Ignore
continue
}
set infofile [list [file tail $fic] $data(mtime) $data(atime)]
# ...
}

TCL regsub replace last / in file name

To replace the last /in a file name with : I tried following code.
set x a/b/c
regsub {.*(\/)\S+$} $x {:}
The undesired result is :.
The desired result is a/b:c
Examples:
a -> a
a/b -> a:b
aa/b -> aa:b
aa/bb/.../dd/e -> aa/bb/.../dd:e
Feedback is appreciated.
With a regular expression, you can say
set new [regsub {/([^/]+)$} $x {:\1}] ;# ==> a/b:c
foreach d {a a/b aa/b aa/bb/.../dd/e} {puts "$d => [regsub {/([^/]+)$} $d {:\1}]"}
# a => a
# a/b => a:b
# aa/b => aa:b
# aa/bb/.../dd/e => aa/bb/.../dd:e
Or, use the file command
set new [file dirname $x]:[file tail $x] ;# ==> a/b:c
The problem with the 2nd option is if the string does not contain a slash, you get a => .:a so you need to do something like this:
foreach d {a a/b aa/b aa/bb/.../dd/e} {
set new [expr {[string first / $d] == -1 ? $d : "[file dirname $d]:[file tail $d]"}]
puts "$d -> $new"
}

Compare columns between 2 files using TCL

I have 2 files having only one column. Say file1.txt and file2.txt.
Below are the contents inside the file
Inside file1.txt
Tom
Harry
Snowy
Edward
Inside file2.txt
Harry
Tom
Edward
2) I want to write a code that will check each item in the column and print something as below.
"Tom, Harry, Edward" are present in both the files
Snowy is there in file1.txt but not in file2.txt
3) Basic code
set a [open file1.txt r]
set b [open file2.txt r]
while {[gets $a line1] >= 0 && [gets $b line2] >= 0} {
foreach a_line $line1 {
foreach b_line $line2 {
if {$a_line == $b_line } {
puts "$a_line in file test1 is present in $b_line in file test2\n"
} else {
puts "$a_line is not there\n"
}
}
}
}
close $a
close $b
Issue is that it is not checking each name in the column.
Any suggestions.
Thanks in advance.
Neel
What you want to do is read each file separately and not have nested loops:
# read the contents of file1 into an associative array
# store the user as an array **key** for fast lookoup
set fh [open "file1.txt" r]
while {[gets $fh user] != -1} {
set f1tmp($user) ""
}
close $fh
# read file2 and compare against file1
array set users {both {} file1 {} file2 {}}
set fh [open "file2.txt" r]
while {[gets $fh user] != -1} {
if {[info exists f1tmp($user)]} {
lappend users(both) $user
unset f1tmp($user)
} else {
lappend users(file2) $user
}
}
close $fh
set users(file1) [array names f1tmp]
parray users
users(both) = Harry Tom Edward
users(file1) = Snowy
users(file2) =
Or as Donal suggests, use tcllib
package require struct::set
set fh [open file1.txt r]
set f1users [split [read -nonewline $fh] \n]
close $fh
set fh [open file2.txt r]
set f2users [split [read -nonewline $fh] \n]
close $fh
set results [struct::set intersect3 $f1users $f2users]
puts "in both: [join [lindex $results 0] ,]"
puts "f1 only: [join [lindex $results 1] ,]"
puts "f2 only: [join [lindex $results 2] ,]"
in both: Harry,Tom,Edward
f1 only: Snowy
f2 only: