taking file content as file name itself in tcl - tcl

I am running one tcl script who is taking file as a input by "stdin".The problem is that its taking the file content as a filename and throwing error while running the script on command line processor.
tcl script is
#!/bin/sh
# SystemInfo_2.tcl \
exec tclsh "$0" ${1+"$#"}
set traps [read stdin];
#set traps "snmp trap test"
set timetrap [clock format [clock seconds]];
set trapout [open Database_traps_event.txt a+];
set javaout [open JavaTrapOutput.txt a+];
puts $trapout $timetrap;
puts $trapout $traps;
puts $trapout "Before executing java program";
set javaprogargs "open {|java -cp mysql-connector-java-5.1.10.jar;. EventAlarmHandling \"$traps\"} r";
puts $trapout $javaprogargs;
set javaprogram [eval $javaprogargs];
puts $trapout "Execution of java is over"
while { [gets $javaprogram line] != -1 } {
puts $javaout $line;
}
close $javaprogram;
puts $trapout "After excution of java program\r\n\r\n\r\n\r\n\r\n";
close $trapout;
close $javaout;
exit;
input file content is -
<UNKNOWN>
UDP: [192.168.1.19]:60572->[0.0.0.0]:0
.iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.sysUpTimeInstance 1:9:58:56.61
.iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTrap.snmpTrapOID.0 .iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTraps.linkDown
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifIndex.1 8
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifAdminStatus.8 up
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifOperStatus.8 down
From command line it ran like below
E:\eventAlarmHandling>tclsh TclTempFile.tcl < traps.txt
couldn't read file "UNKNOWN>
UDP: [192.168.1.19]:60572->[0.0.0.0]:0
.iso.org.dod.internet.mgmt.mib-2.system.sysUpTime.sysUpTimeInstance 1:9:58:56.61
.iso.org.dod.internet.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTrap.snmpTrapOID.0 .iso.org.dod.intern
et.snmpV2.snmpModules.snmpMIB.snmpMIBObjects.snmpTraps.linkDown
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifIndex.1 8
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifAdminStatus.8 up
.iso.org.dod.internet.mgmt.mib-2.interfaces.ifTable.ifEntry.ifOperStatus.8 down": No error
while executing
"open {|java -cp mysql-connector-java-5.1.10.jar;. EventAlarmHandling "<UNKNOWN>
UDP: [192.168.1.19]:60572->[0.0.0.0]:0
.iso.org.dod.internet.mgmt.mib..."
("eval" body line 1)
invoked from within
"eval $javaprogargs"
invoked from within
"set javaprogram [eval $javaprogargs]"
(file "TclTempFile.tcl" line 26)
So clearly in command line its showing that "couldn't read file UNKNOWN> ......"
So please explain it that whats happening here in command line.I am new to tcl.So hoping that someone help me out.
Thanks

You're having problems with one of the trickier bits of how pipelines work in Tcl. If we look at the documentation carefully, we see:
If the first character of fileName is “|” then the remaining characters of fileName are treated as a list of arguments that describe a command pipeline to invoke, in the same style as the arguments for exec.
That means you have to have the first character be | and the rest, after stripping that first character, be a proper list. In your case, you've not got that. Instead, you're doing:
set javaprogargs "open {|java -cp mysql-connector-java-5.1.10.jar;. EventAlarmHandling \"$traps\"} r";
That's pretty complicated anyway. Let's build this in the idiomatic fashion instead:
set CPsep ";"
set classpath [list mysql-connector-java-5.1.10.jar .]
set javaprogargs [list open |[list \
java -cp [join $classpath $CPsep] EventAlarmHandling $traps]]
It helps to split the classpath out; it's got a ; character in it (on Windows; you'll need to change that if you port to Linux or OSX) and it's nicer to use list in Tcl to build things and then join to convert into what Java expects.
We also no longer need any backslash-quoted substrings in there (except the one I put in to keep lines short and readable); the pattern of list commands there will add everything that is required. Note the |[list …] there: that's non-idiomatic everywhere in Tcl except when creating a pipeline when it is recommended practice as it is doing in reverse what open expects to parse.
The other thing you're running into is this:
If an arg (or pair of args) has one of the forms described below then it is used by exec to control the flow of input and output among the subprocess(es). Such arguments will not be passed to the subprocess(es).
[…]
< fileName
The file named by fileName is opened and used as the standard input for the first command in the pipeline.
Your argument from $traps starts with a < and so it triggers this rule.
Unfortunately, there's no simple workaround for this and this is a severe, known, and very annoying limitation of the pipeline creation code. The only known techniques for dealing with this are to move to transferring that data by either a file or via the subprocess's standard input, both of which require modifying the subprocess's implementation. If you can make that Java program read from System.in (a good idea anyway, so you don't hit Windows's command line length limitations!) then you can pass the value like this:
set CPsep ";"
set classpath [list mysql-connector-java-5.1.10.jar .]
set javaprogargs [list open |[list \
java -cp [join $classpath $CPsep] EventAlarmHandling << $traps]]
That is just by adding a << in there immediately before the value.

Related

Redirecting output of tcl proc to file and output (like tee) Part 2

I am using tee from https://wiki.tcl-lang.org/page/Tee to redirect file output from my procedures. I need to redirect both stdout and stderr to the file.
Using the input from Redirecting output of tcl proc to file and output (like tee) I arrived at doing the following:
set LogFile [open ${LogFileName} w]
tee channel stderr $LogFile
tee channel stdout $LogFile
set BuildErrorCode [catch {LocalBuild $BuildName $Path_Or_File} BuildErrMsg]
set BuildErrorInfo $::errorInfo
# Restore stdout and stderr
chan pop stdout
chan pop stderr
# Handle errors from Build ...
I am testing this on three different EDA tools and I have three different issues.
When I run from tclsh (on MSYS2 running on Windows 10) and run either the open source simulator GHDL, ModelSim, or QuestaSim, all the even characters are the NUL character.
If I run ModelSim or QuestaSim from the GUI, I miss the output of each command. Shouldn't that be going to either stdout or stderr?
In Riviera-PRO, I am getting extraneous characters that were previously printed. They are generally the second half of a word.
Am I doing something wrong? I tested out the above code using:
set LogFile [open test_tee.log w]
tee channel stderr $LogFile
tee channel stdout $LogFile
puts "Hello, World!"
puts stderr "Error Channel"
puts stdout "Output Channel"
chan pop stdout
chan pop stderr
And this works well.
I am hoping to find something that works in the general case for all tools rather than having to write a different handler for each tool.
============ Update =============
For #1 above, with #Shawn's suggestion, I tried the following and it did not work.
set LogFile [open ${LogFileName} w]
chan configure $LogFile -encoding ascii
. . .
I also tried the following and it did not work.
set LogFile [open ${LogFileName} w]
fconfigure $LogFile -encoding ascii
. . .
Then I tried updating the write in tee to the following and it did not work:
proc tee::write {fd handle buffer} {
puts -nonewline $fd [encoding convertto ascii $buffer]
return $buffer
}
Any other hints solutions appreciated
============ Update2 =============
I have successfully removed the nul characters by doing the following, except now I have an extra newline. Still not a solution.
proc tee::write {fd handle buffer} {
puts -nonewline $fd [regsub -all \x00 $buffer ""]
return $buffer
}
The extra NUL bytes are probably because the stdout ahd steer channels are being written in UTF-16 (the main use for that encoding is the console on Windows). The tee interceptors you are using come after the data being written is encoded. There's a few ways to fix it, but the easiest is to open the file with the right encoding when reading it.
The output of the commands is not necessarily written to those channels. Code written in C or C++ is entirely free to write directly, and Tcl code cannot see that; it's all happening behind our back. Command results can be intercepted using execution traces, but that cannot see anything that the commands internally print that aren't routed via the Tcl library somehow. (There are a few more options on Unix due to the different ways that the OS handles I/O.)
Don't know what's happening with the extra characters. I can tell you that you are getting what goes through the channel, but there are too many tricks (especially in interactive use!) for a useful guess on that front.

Reading cmd arguments in TCL file

I am trying to run a tcl script through .bat file. I want to read some cmd arguments in the tcl script. Below is my code:
Command to run:
D:\Cadence\Sigrity2021.1\tools\bin\PowerSI.exe -tcl abcd.tcl %new_var%.spd %new_file_name%
Below is how I am trying to read the variable in the tcl file:
sigrity::open document [lindex $argv 0] {!}
It open up the Cadence Sigrity, but I see the below error:
How do I read cmd argument in tcl?
If you have no other way to do it that you can find (and it sounds like that might be the case) then you can fake it by writing a helper file with content like this, filling in the real arguments in the appropriate places:
# Name of script to call
set ::argv0 "abcd.tcl"
# Arguments to pass
set ::argv {}
lappend ::argv "%new_var%.spd"
lappend ::argv "%new_file_name%"
# Number of arguments (rarely used)
set ::argc [llength $::argv]
# Do the call
source $::argv0
Then you can pass that file to PowerSI and it will set things up and chain to the real file. It's messy, but practical.
If you're writing this from Tcl, use the list command to do the quoting of the strings (instead of putting them in double quotes) as it will do exactly the right thing for you. If you're writing the file from another language, you'll want to make sure you put backslashes in before \, ", $ and [ characters. The fiddlyness of doing that depends on your language.

How to use Tcl script in unix pipeline?

Want to use some custom function (written in tcl) in Unix pipeline ie grep patt file.rpt | tclsh summary.tcl. How to make tcl script to take output from the pipeline, process and out on the commandline as if a normal unix command?
This is very easy! The script should read input from stdin (probably with gets) and write output to stdout (with puts; this is the default destination).
Here's a very simple by-line filter script:
set lineCount 0
while {[gets stdin line] >= 0} {
incr lineCount
puts stdout "$lineCount >> $line <<"
}
puts "Processed $lineCount lines in total"
You probably want to do something more sophisticated!
The gets command has two ways of working. The most useful one here is the one where it takes two arguments (channel name, variable name), writes the line it has read from the channel into the variable, and returns the number of characters read or -1 when it has an EOF (or would block in non-blocking mode, or has certain kinds of problems; you can ignore these cases). That works very well with the style of working described above in the sample script. (You can distinguish the non-success cases with eof stdin and fblocked stdin, but don't need to for this use case.)

Separating stdout and stderr in Tcl drops all but last line of stderr

I'm not a Tcl programmer, but I need to modify a Tcl script that invokes an external command and tries to separate stdout and stderr. The following is a minimal example of how the script currently does this.
#!/usr/bin/tclsh8.4
set pipe [open "|cmd" r]
while {[gets $pipe line] >= 0} {puts $line}
catch "close $pipe" errorMsg
puts "$errorMsg"
Here, cmd is a an external command, and for the sake of this example, I will replace it with the following shell script. (I'm working on a Linux machine, but you can modify this to write to stdout and stderr however is appropriate for your system.)
#!/bin/sh -f
echo "A" > /dev/stdout
echo "B" > /dev/stdout
echo "C" > /dev/stderr
echo "D" > /dev/stderr
When I execute cmd, I get the following four lines as expected:
% ./cmd
A
B
C
D
However, when I execute my Tcl script, I get:
% ./test.tcl
A
B
D
This is an example of a more general phenomenon, which is that catch seems to swallow all but the last line of stderr.
To me, the "obvious" way to approach this is to try to mimic what is happening with stdout, which obviously works and prints all lines of the output. However, the current implementation is based on getting a Tcl channel by using open "|cmd", which requires running an external command. I can't figure out how to create a channel without opening an external command, and even if I could figure that out, there are subsequent issues with this approach. (How do I get the output of close into the channel? And if I need to open a new channel to get the output of each channel I am closing, then wouldn't I need an infinite number of channels?)
If anyone has any idea why errorMsg drops the initial lines or another approach that does not suffer from this problem, please let me know.
I know that this will come up, so I will say in advance that switching to Tcl 8.5 is probably not an option for me in the short term, since I do not control the environment in which this script is run.

tcl open pipe seems to misshandle spaces in parameters

I have this open:
set r [catch {open "|[concat $config(cmd,sh) [list $cmd 2>#1]]" r} fid]
where $config(cmd,sh) is cmd /c and I am trying to pass a file name (and possibly a command such as echo) in $cmd. If there is no space in the file name, i.e. :
cmd is echo /filename
all is well. With a space, i.e.:
cmd is echo "/file name"
what appears to be passed is:
\"file name\".
When I try this on Linux, I get "file name" (no backslashes). I have tried replacing the spaces in the file name with "\ ", but then the target gets two file names, i.e. the space is used to break up the file name.
I am beginning to think I have found a bug in the Windows port of Tcl...
Ugh, that looks convoluted! To pass this sort of thing into the pipe creation code, you need to use exactly the right recipe:
set r [catch {open |[list {*}$config(cmd,sh) $cmd 2>#1] r} fid]
That is, always use the form with |[list ...] when building pipes as the documentation says that is what the pipe opener looks for. (This is the only command like that in Tcl.)
And of course, using the (8.5+) {*} syntax is much simpler in this case too, as it is more obviously doing the right thing.