How to source a script file by passing arguments? - tcl

Say I have a tcl script and I want to pass some arguments to the second script file which is being sourced in the first tcl:
#first tcl file
source second.tcl
I want to control the flow of second.tcl from first.tcl and I read that tcl source does not accept arguments. I wonder how I can do then.

source does not accept any additional arguments. But you can use (global) variables to pass arguments, e.g.:
# first tcl file
set ::some_variable some_value
source second.tcl
The second TCL file can reference the variable, e.g.:
# second tcl file
puts $::some_variable
Remark:
Sourcing a file means that the content of the sourced script is executed in the current context. That means that the sourced script has access to all variables existing in that context. The above code is the same as:
# one joint tcl file
set ::some_variable some_value
puts $::some_variable

Regarding the "::" thing -- see the explanation here (sorry, I don't have enough rep. to leave comments yet).
I should also add that the original question discusses a problem which appears to be quite odd: it seems that it could be better to provide a specific procedure in your second source file that would set up a state pertaining to what is defined by that script.
Something like:
source file2.tcl
setup_state $foo $bar $baz
Making [source] behave differently based on some global variables looks too obscure to me. Of course you might have legitimate reasons to do this, but anyway...

Related

How does Tcl "file rename {*}[glob *tcl] dir/ " operate

I am trying to move a large number of files using Tcl and came across the expression :
file rename {*}[glob *tcl] dir/ which works perfectly.
Can anyone explain how this command works or what this feature is called?
It's a compound of two commands and some useful syntax.
glob returns a list of filenames that match the pattern, *tcl in your case, or an error if nothing matches. There's a bunch of options you could use to modify what it returns, but you're not using any of them; that's great for your use case.
file rename will rename files or move files around. In particular, when the final argument is an existing directory name, the other arguments are files (or directories) that will be moved into that directory. (That it moves things around is sensible if you're familiar with how POSIX system calls work.)
The final piece of the puzzle is {*}[…], i.e., command expansion, which runs a command (which is glob *tcl in your case) and uses the elements of the list it returns as a sequence of arguments to the command call within which it is used. Which is useful; we want a list of filenames at that point of the call to file rename. There's no real limit on the number of arguments that can be moved around that way, other than basic things like memory and so on.
The {*} prefix (it's only special at the start of a word) can be used with other well-formed ways of producing a Tcl word (e.g., a read from a variable with $ or a literal with {…}) or even with a compound word, though use with compound words is usually a sign that what you're doing is probably unwise.
If you have old Tcl code, written for Tcl 8.4 or before, you won't see {*}. Instead, you'd see something like this:
eval file rename [glob *tcl] dir/
# Or, more properly, one of these horrors:
eval {file rename} [glob *tcl] {dir/}
eval [list file rename] [glob *tcl] [list dir/]
eval [linsert [linsert [glob *tcl] 0 file rename] end dir/]
These were notoriously awkward to get right in tricky cases (causing many subtle bugs). The expansion syntax was added in Tcl 8.5 exactly to get rid of this whole class of trouble. eval still exists in modern Tcl, but it is now thankfully rarely used.

How to get value of variable from open file

I am working on TCL script that will open another tcl file and I want to get value of variable from second opened file and use it in the first file.
Two file abc.tcl and xyz.tcl
abc.tcl opens file xyz.tcl and reads value of variable and use it in abc.tcl.
If xyz.tcl sets a global variable, abc.tcl will be able to see it if it used source to load in xyz.tcl.
Here's a simple example. This is xyz.tcl:
set SomeVariable 12345
This is abc.tcl:
source xyz.tcl
puts "The password on my luggage is $SomeVariable"
The source command is really very simple internally. It just reads in the contents of the file (into a string), and then internally evals that string. Yes, this means that you probably shouldn't put source inside a procedure, at least not unless you're sure what the consequences of this are.

Difference tcl script tkconsole to load gro file in VMD

My problem is simple: I'm trying to write a tcl script to use $grofile instead writing every time I need this file name.
So, what I did in TkConsole was:
% set grofile "file.gro"
% mol load gro ${grofile}
and, indeed, I succeeded uploading the file.
In the script I have the same lines, but still have this error:
wrong # args: should be "set varName ?newValue?"
can't read "grofile": no such variable
I tried to solve my problem with
% set grofile [./file.gro]
and I have this error,
invalid command name "./file.gro"
can't read "grofile": no such variable
I tried also with
% set grofile [file ./file.gro r]
and I got the first error, again.
I haven't found any simple way to avoid using the explicit name of the file I want to upload. It seems like you only can use the most trivial, but tedious way:
mol load file.gro
mol addfile file.xtc
and so on and so on...
Can you help me with a brief explanation about why in the TkConsole I can upload the file and use it as a variable while I can not in the tcl script?
Also, if you have where is my mistake, I will appreciate it.
I apologize if it is basic, but I could not find any answer. Thanks.
I add the head of my script:
set grofile "sim.part0001_protein_lipid.gro"
set xtcfile "protein_lipid.xtc"
set intime "0-5ms"
set system "lower"
source view_change_render.tcl
source cg_bonds.tcl
mol load gro $grofile xtc ${system}_${intime}_${xtcfile}
It was solved, thanks for your help.
You may think you've typed the same thing, but you haven't. I'm guessing that your real filename has spaces in it, and that you've not put double-quotes around it. That will confuse set as Tcl's general parser will end up giving set more arguments than it expects. (Tcl's general parser does not know that set only takes one or two arguments, by very long standing policy of the language.)
So you should really do:
set grofile "file.gro"
Don't leave the double quotes out if you have a complicated name.
Also, this won't work:
set grofile [./file.gro]
because […] is used to indicate running something as a command and using the result of that. While ./file.gro is actually a legal command name in Tcl, it's… highly unlikely.
And this won't work:
set grofile [file ./file.gro r]
Because the file command requires a subcommand as a first argument. The word you give is not one of the standard file subcommands, and none of them accept those arguments anyway, which look suitable for open (though that returns a channel handle suitable for use with commands like gets and read).
The TkConsole is actually pretty reasonable as quick-and-dirty terminal emulations go (given that it omits a lot of the complicated cases). The real problem is that you're not being consistently accurate about what you're really typing; that matters hugely in most programming languages, not just Tcl. You need to learn to be really exacting; cut-n-paste when creating a question helps a lot.

How to copy multiple files with Tcl file command

From Tcl online manual I see that Tcl's file copy command can take multiple source files as argument:
file copy ?-force? ?--? source ?source ...? targetDir
However, I have the following code:
set flist [list a.txt b.txt]
file copy $flist [file join D:\\ test dest]
And get this error message:
error copying "a.txt b.txt": no such file or directory
How do I properly pass a file list as source argument to the file copy command?
The right way to do this is to use expansion:
file copy {*}$flist {D:\test\dest}
The {*} substitutes the words of the list given by what follows it as separate words; it's precisely right here.
I've also written the destination directory as a brace-quoted literal.
Still on Tcl 8.4 or before? Upgrade! Or use this:
eval file copy $flist [list {D:\test\dest}]
It's quite a lot harder to use eval right than {*}, so really upgrade.
Or even do:
foreach f $flist {
file copy $f {D:\test\dest}
}
Given that IO operations will dominate the performance, you shouldn't notice any speed difference for doing it this way.
The problem is the list is passed as a whole to the command instead of individual elements. Use {*} operator to break the list down to its individual elements.
The short answer is don't use a list the way you have done.
This works in your example:
set flist "a.txt b.txt"
file copy $flist [file join D:\\ test dest]
More correct would be to use the list expansion {*} syntax.

Converting Tcl to C++

I am trying to convert some tcl script into a C++ program. I don't have much experience with tcl and am hoping someone could explain what some of the following things are actually doing in the tcl script:
1) set rtn [true_test_sfm $run_dir]
2) cd [glob $run_dir]
3) set pwd [pwd]
Is the first one just checking if true_test_sfm directory exists in run_dir?
Also, I am programming on a windows machine. Would the system function be the equivalent to exec statements in tcl? And if so how would I print the result of the system function call to stdout?
In Tcl, square brackets indicate "evaluate the code between the square brackets". The result of that evaluation is substituted for the entire square-bracketed expression. So, the first line invokes the function true_test_sfm with a single argument $run_dir; the result of that function call is then assigned to the variable rtn. Unfortunately, true_test_sfm is not a built-in Tcl function, which means it's user-defined, which means there's no way we can tell you what the effect of that function call will be based on the information you've provided here.
glob is a built-in Tcl function which takes a file pattern as an argument and then lists files that match that pattern. For example, if a directory contains files "foo", "bar" and "baz", glob b* would return a list of two files, "bar" and "baz". Therefore the second line is looking for any files that match the pattern given by $run_dir, then using the cd command (another Tcl built-in) to change to the directory found by glob. Probably $run_dir is not actually a file pattern, but an explicit file name (ie, no globbing characters like * or ? in the string), otherwise this code may break unexpectedly. On Windows, some combination of FindFirstFile/FindNextFile in C++ could be used as a substitute for glob in Tcl, and SetCurrentDirectory could substitute for cd.
pwd is another built-in Tcl function which returns the process current working directory as an absolute path. So the last line is querying the current working directory and saving the result in a variable named pwd. Here you could use GetCurrentDirectory as a substitute for pwd.