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

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.

Related

Tcl hang in proc return

I write 2 script to do somting like this:
#script1, to dump info:
proc script1 {} {
puts $file "set a 123"
puts $file "set b 456"
.....
}
(The file size I dump is 8GB)
#And use script2 to source it and do data category:
while { [get $file_wrtie_out_by_script1 line] != -1 } {
eval $line
}
close $file_wrtie_out_by_script1
Do the job....
return
In this case, the script is hang in return, how to solve the issue... stuck 3+ days, thnaks
Update:
Thanks for Colin, now I use source instead of eval, but even remove the "Do the job...", just keep return, still hang
The gets command will return the number of characters in the line that it just read from the file channel.
When all the lines of the file have been read, then gets will return -1.
Your problem is that you have a while loop that is never ending. Your while loop will terminate when gets returns 1. You need to change the condition to -1 for the while loop to terminate.
I agree with the comment from Colin that you should just use source instead of eval for each line. Using eval line-by-line will fail if you have a multi-line command (but that might not be the case in your example).

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

taking file content as file name itself in 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.

How to run a TCL script to tell run in every 10 minutes?

My TCL script:
source reboot_patch.tcl
set a 1
while {$a < 10} {
exec reboot_patch.tcl
after 60000
incr a
}
I need to run "reboot_patch.tcl" script for every 1 min in my system. I wrote above script. But its running only once and its coming out.
Following is the "reboot_patch.tcl" script:
#!/usr/bin/tcl
package require Expect
spawn telnet 40.1.1.2
expect "*console."
send "\r"
expect "*ogin:"
send "test\r"
expect "*word:"
send "test\r"
expect "*>"
send "clear log\r"
expect "*#"
send "commit \r"
expect "*#"
Please suggest me a way to achieve this.
Thanks in advance.
Script to print numbers from 1 to 10 in windows 7:
#!c:\Tcl\bin\tclsh
set a 1
while { $a < 11} {
puts $a
incr a
}
I am unable to run the above script using "./" format in windows7.
In general, exec command will return the output of program execution. It is our responsibility to capture and print and manipulate it.
You have to print it manually like
puts [ exec ./reboot_patch.tcl ]
Or like,
set result [ exec ./reboot_patch.tcl ]
puts $result
Since you are using exec without printing it's result, you have not seen anything. Then how come it got executed for the first time ? Who else can do except the following ?
source reboot_patch.tcl
Well, Since you have sourced the file and it got executed which seemed to be the first time execution but which is not actually from exec command.
Note : If you are calling any of that sourced file's proc, then only it is required to source it. As far as I can see you are not having any proc there. So, source is not required at all.

How do you spawn application from tcl/tk such that it's independant of the parent app?

I've tried just about EVERYTHING. I've tried exec. I've tried open with a pipe. I've tried expect's spawn command. I've even tried creating scripts that launch the application. None of this works. It should be something so freaking simple but it isn't. Basically I want something that executes "./myapp &". I don't want the parent app to wait for any input. I don't want the parent app to kill the child when it's closed. Nor do I want the closure of the child to affect the parent.
I've spent 4 hours trying to do this with no dice.
EDIT 4: I have produced a small test case. Actually. I know think the bug is related to a shell script called from tcl not recognizing it's finished and wanting more input. But oddly, the script does run and the following tcl commands are still executed.
Create a file called "data.dat"
9.2877556131E+03 1.2512862771E-15 1.5337670739E-18
9.2910873127E+03 1.2439911921E-15 1.5339531650E-18
9.2944190123E+03 1.2253566905E-15 1.5354833054E-18
9.2977507119E+03 1.2115157273E-15 1.6142496743E-18
9.3010824115E+03 1.2080944425E-15 1.7533261348E-18
9.3044141111E+03 1.2076649858E-15 1.8405304542E-18
9.3077458106E+03 1.1904275879E-15 1.8336914953E-18
9.3110775102E+03 1.1790369061E-15 1.8270892064E-18
9.3144092098E+03 1.1620102216E-15 1.8244075829E-18
9.3177409094E+03 1.1178361307E-15 1.8581382655E-18
9.3210726090E+03 1.0969704027E-15 1.8237852087E-18
9.3244043086E+03 1.0721326432E-15 1.7016959482E-18
9.3277360082E+03 1.0699729320E-15 1.6841145916E-18
9.3310677078E+03 1.0266229217E-15 1.7253936877E-18
9.3343994074E+03 9.0875918248E-16 1.6065954292E-18
9.3377311069E+03 7.9157363851E-16 1.5220001484E-18
and then run this tcl file on it
#!/usr/bin/wish
frame .main
button .button -text "Make plot" -command { makeplot }
pack .button
proc makeplot {} {
set data {#!/bin/bash
cat << EOF > plot.gnu
splot "data.dat" using 1:2:3 with points
EOF
}
set fileId [open "plot.csh" "w"]
puts -nonewline $fileId $data
flush $fileId
close $fileId
exec chmod +x ./plot.csh
exec ./plot.csh
exec gnuplot -persist < plot.gnu &
}
The gnuplot plot will not appear until you "foreground" it. I have tried many variations on this (with/without the ampersand). Strangely, I DID get a version of this working under bash.
EDIT 5: I GIVE UP! This test case is now working. It was not just moments ago. I have NO idea what's going on here. This has been one of the hardest bugs to fix in 15 years of programming.
Try exec with & at the end:
exec ./myapp &
Have you tried piping to 'at now'?
./myapp | at now