how do i make a variable unique - tcl

How do I make a variable unique in TCL?
Example:
exec echo $msgBody - /tmp/Alert_Notify_Work.$$
exec cat /home/hci/Alert.txt -- /tmp/Alert_Notify_Work.$$
This does not work; I am trying to make the variable Alert_Notify_Work unique.

It's best to use a pre-existing library for this. Tcllib has a fileutil package that implements tempfiles:
set filename [fileutil::tempfile Alert_Notify_Work.]

$$ is not valid Tcl syntax, and Tcl will parse that line before the shell sees it. But there is a Tcl command to retrieve the pid: pid. I usually rely on the current time and the pid for uniqueness.
I assume msgBody is a Tcl variable, and the - and -- in your commands should be > and >> respectively.
option 1
set filename /tmp/Alert_Notify_Work.[clock seconds].[pid]
exec echo $msgBody > $filename
exec cat /home/hci/Alert.txt >> $filename
or, Tcl only with just a few more lines:
set f_out [open /tmp/Alert_Notify_Work.[clock seconds].[pid] w]
puts $f_out $msgBody
set f_in [open /home/hci/Alert.txt r]
fcopy $f_in $f_out
close $f_in
close $f_out

Related

Read all files in folder tcl/tk

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]]

TCL-Getting Log of Exec'd Process

Currently I am firing following command
set pid [exec make &]
set term_id [wait pid]
First command will execute makefile inside TCL and Second Command will wait for first command's makefile operation to complete. First command displays all logs of makefile on stdout. Is it possible to store all logs in variable or file when "&" is given in the last argument of exec using redirection or any other method?
If "&" is not given then we can take the output using,
set log [exec make]
But if "&" is given then command will return process id,
set pid [exec make &]
So is it possible stop the stdout logs and put them in variable?
If you are using Tcl 8.6, you can capture the output using:
lassign [chan pipe] reader writer
set pid [exec make >#$writer &]
close $writer
Don't forget to read from the $reader or the subprocess will stall. Be aware that when used in this way, the output will be delivered fully-buffered, though this is more important when doing interactive work. If you want the output echoed to standard out as well, you will need to make your script do that. Here's a simple reader handler.
while {[gets $reader line] >= 0} {
lappend log $line
puts $line
}
close $reader
Before Tcl 8.6, your best bet would be to create a subprocess command pipeline:
set reader [open |make]
If you need the PID, this can become a bit more complicated:
set reader [open |[list /bin/sh -c {echo $$; exec make}]]
set pid [gets $reader]
Yes, that's pretty ugly…
[EDIT]: You're using Tk, in Tcl 8.5 (so you need the open |… pipeline form from above), and so you want to keep the event loop going. That's fine. That's exactly what fileevent is for, but you have to think asynchronously.
# This code assumes that you've opened the pipeline already
fileevent $reader readable [list ReadALine $reader]
proc ReadALine {channel} {
if {[gets $channel line] >= 0} {
HandleLine $line
} else {
# No line could be read; must be at the end
close $channel
}
}
proc HandleLine {line} {
global log
lappend log $line; # Or insert it into the GUI or whatever
puts $line
}
This example does not use non-blocking I/O. That might cause an issue, but probably won't. If it does cause a problem, use this:
fconfigure $reader -blocking 0
fileevent $reader readable [list ReadALine $reader]
proc ReadALine {channel} {
if {[gets $channel line] >= 0} {
HandleLine $line
} elseif {[eof $channel]} {
close $channel
}
}
proc HandleLine {line} {
global log
lappend log $line
puts $line
}
More complex and versatile versions are possible, but they're only really necessary once you're dealing with untrusted channels (e.g., public server sockets).
If you'd been using 8.6, you could have used coroutines to make this code look more similar to the straight-line code I used earlier, but they're a feature that is strictly 8.6 (and later, once we do later versions) only as they depend on the stack-free execution engine.

Running an external program from a Tcl script

I would like to export a variable depending on result of a binary command. My TCL script is this:
set A ""
exec sh -c "export A=\"`/usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery -noprompt | grep ^Device | wc -l`\""
puts $A
if { $A == "1" } {
set CUDA_VISIBLES_DEVICES 0
} else {
set CUDA_VISIBLES_DEVICES 1
}
With this script, when I execute puts $A I don't get anything in terminal... so in if command I don't know what I evaluating...
My "export" must return ONLY 1 or 0...
Sorry about my poor TCL level.
Thanks.
I guess what you want is something like this:
set a [exec /usr/local/cuda/samples/1_Utilities/deviceQuery/deviceQuery -noprompt | grep ^Device | wc -l]
You set variable a in TCL context and assign the command's return value (i.e. the output text) to it.
The problem is that your exec'd command runs in its own process, so when you set a variable A, that A only exists as a shell variable for the life of that process. When the process exits, A goes away.
The exec command returns the stdout of the command that was exec'd. If you want the result of the command to be in a Tcl variable, you need to set the variable to be the result of the exec:
set A [exec ...]
For more information on the exec command, see the exec man page.

Get line number using grep

I would like to get the line number using grep command, but I am getting the error message when search pattern is not a single word:
couldn't read file "Pattern": no such file or directory
How should be the proper usage of the grep? The code is here:
set status [catch {eval exec grep -n '$textToGrep' $fileName} lineNumber]
if { $status != 0 } {
#error
} else {
puts "lineNumber = $lineNumber"
}
Also if the search pattern is not matched at all, the returned value is : "child process exited abnormally"
Here is the simple test case:
set textToGrep "<BBB name=\"BBBRM\""
file contents:
<?xml version="1.0"?>
<!DOCTYPE AAA>
<AAA>
<BBB name="BBBRM" />
</AAA>
Well, I also get problems with your code and a single word pattern!
First of all, I don't think you need the eval command, because catch itself does an evaluation of its first argument.
Then, the problem is that you put the $textToGrep variable in exec inside single quotes ', which have no meaning to Tcl.
Therefore, if the content of textToGrep is foo, you are asking grep to search for the string 'foo'. If that string, including the single quotes, is not found in the file, you get the error.
Try to rewrite your first line with
set status [catch {exec grep -n $textToGrep $fileName} lineNumber]
and see if it works. Also, read the exec man page, which explains well these problems.
If your system has tcllib install, you can use the fileutil::grep command from the fileutil package:
package require fileutil
set fileName data.xml
set textToGrep {<BBB +name="BBBRM"}; # Update: Add + for multi-space match
set grepResult [::fileutil::grep $textToGrep $fileName]
foreach result $grepResult {
# Example result:
# data.xml:4: <BBB name="BBBRM" />
set lineNumber [lindex [split $result ":"] 1]
puts $lineNumber
# Update: Get the line, squeeze the spaces before name=
set line [lindex [split $result ":"] 2]
regsub { +name=} $line " name=" line
puts $line
}
Discussion
When assigning value to textToGrep, I used the curly braces, thus allowing double quote inside without having to escape them.
the result of the ::fileutil::grep command is a lits of strings. Each string contains the file name, line number, and the line itself; separated by colon.
One way to extract the line number is to first split the string (result) into pieces, using the colon as a separator. Next, I use lindex to grab the second item (index=1, since list is zero-base).
I have updated the code to account for case where there are multiple spaces before name=
There are two problems here:
Pattern matching does not work.
grep exits with error child process
exited abnormally when pattern is not found
The first problem is because you are not enclosing the textToGrep within double quotes(instead of single quotes). So your code should be:
[catch {exec grep -n "$textToGrep" $fileName} lineNumber]
Second problem is because of the exit status of grep command. grep exits with error when the pattern is not found. Here is the try on a shell:
# cat file
pattern
pattern with multiple spaces
# grep pattern file
pattern
pattern with multiple spaces
# echo $?
0
# grep nopattern file
# echo $?
1
EDIT:
In your case you have special characters such as < and > (which have special meaning on a shell).
set textToGrep "<BBB name=\"BBBRM\""
regsub -all -- {<} "$textToGrep" "\\\<" textToGrep
regsub -all -- {>} "$textToGrep" "\\\>" textToGrep
set textToGrep {\<BBB name="BBBRM"}
catch {exec grep -n $textToGrep $fileName} status
if {![regexp "child process" $status]} {
puts $status
} else {
puts "no word found"
}
I think you should do regular expression with child process. Just check above code if it works. In if statement you can process the status command as you like.
With the given example (in your post) the above code works only you need to use backslash for the "<" in the textToGrep variable

how can i use input.properties files like concept in TCL script

in ANt script we access properties file as below
<property file="input.properties"/>
in perl script we access properties file as below
do "config.cfg";
same way how can i access properties file in TCL script.
Can anyone help me out pls?
thanks in advance...
Okay, if you want it as dumb as in Perl, just source the file in Tcl.
Configuration file sample (named config.tcl):
# Set "foo" variable:
set foo bar
To load this configuration file:
source config.tcl
After source-ing, you can access your variable foo in your script.
As with perl, a malicious user might put something like
exec rm -rf ~
in your "config file" and wish you all the good luck.
The equivalent of perls
$var = "test";
is in Tcl
set var "test"
So if you want it as easy as in Perl, I suggest kostix answer.
But you could also try to use dicts as config file:
This will look like
var {hello world}
other_var {Some data}
foo {bar baz}
I personally love using this, it allows even nesting:
nestedvar {
subvar {value1}
subvar2 {value2}
}
And comments: Kind of a hack, in fact has the key #
# {This is a comment}
Parsing:
set fd [open config.file]
set config [read $fd]
close $fd
dict unset config #; # Remove comments.
Access:
puts [dict get $config var]
puts [dict get $config nestedvar subvar]
But if you want really something like $var = "foo"; (which is valid Perl code but not Tcl), then you have to parse this file yourself.
An example:
proc parseConfig {file} {
set fd [open $file]
while {[gets $fd line] != -1} {
if {[regexp {^\s*\$([^\s\=]+)\s*\=\s*(.*);?$} $line -> var value]} {
# The expr parses funny stuff like 1 + 2, \001 inside strings etc.
# But this is NOT perl, so "foo" . "bar" will fail.
set ::$var [expr $value]
}
}
}
Downside: does not allow multi-line settings, will throw an error if there is an invalid value, and allows command injection (but you Perl solution does that too).
The simplest mechanism is to either make it a script or to make it the contents of an array. Here's how to do the latter while still supporting comments:
proc loadProperties {arrayName fileName} {
# Put array in context
upvar 1 $arrayName ary
# Load the file contents
set f [open $fileName]
set data [read $f]
close $f
# Magic RE substitution to remove comment lines
regsub -all -line {^\s*#.*$} $data {} data
# Flesh out the array from the (now clean) file contents
array set ary $data
}
Then you'd use it like this:
loadProperties myProps ~/myapp.props
if {[info exists myProps(debug)] && $myProps(debug)} {
parray myProps
}
With a file in your home directory (called myapp.props) like this:
# Turn on debug mode
debug true
# Set the foos and the bars
foo "abc"
bar "Harry's place downtown"
You can do a lot more complicated than that, but it gives you an easy format to get going with.
If you prefer to use an executable configuration, just do:
# Define an abstraction that we want users to use
proc setProperty {key value} {
# Store in a global associative array, but could be anything you want
set ::props($key) $value
}
source ~/myapp_config.tcl
If you want to restrict the operations to ones that won't cause (much) trouble, you need a slightly more complex approach:
interp create -safe parser
proc SetProp {key value} {
set ::props($key) $value
}
# Make a callback in the safe context to our main context property setter
interp alias parser setProperty {} SetProp
# Do the loading of the file. Note that this can't be invoked directly from
# within the safe context.
interp invokehidden parser source [file normalize ~/myapp_config.tcl]
# Get rid of the safe context; it's now surplus to requirements and contaminated
interp delete parser
Safety has pretty low overhead.