TCL/TK - how does one capture stderr output from a package? - redirect

I have an application making use of some packages. Looking at their source code I see they're doing a simple puts stderr ... to dump out debug information. The problem is if you wrap the program with something like FreeWrap or the TDK, you lose access to the console; so we'd like to forward that stderr output to a file instead so we can see what's being printed.
I saw somewhere on StackOverflow that you can simply close the stderr channel, open a new one, and it should automatically replace the most recently closed channel like so:
close stderr
set out [open "outfile.txt" w]
puts stderr "hello world" # should output to the file
Unfortunatly this doesn't work. When I try it I get the error message: can not find channel named "stderr"

You can override puts so that printing to stderr can be intercepted:
set error_file [open "outfile.txt" w]
rename puts __tcl__puts
proc puts {args} {
if {[llength $args] == 2 && [lindex $args 0] eq "stderr"} {
set args [list $::error_file [lindex $args end]]
}
__tcl__puts {*}$args
}

This has been addressed a number of times: Use a channel interceptor (covered before, for capturing Tcl test suite output):
A channel interceptor is implemented as a channel transform; and has been covered here before.
Step 1: Define a Channel Interceptor
oo::class create ChannelSink {
variable fileHandle
method initialize {handle mode} {
set fileHandle [open "outfile.txt" w]
fconfigure $fileHandle -translation binary
return {finalize initialize write}
}
method finalize {handle} {
catch {close $fileHandle}
}
method write {handle bytes} {
puts -nonewline $fileHandle $bytes
return
}
}
The above snippet was derived from Donal's.
Step 2: Register the interceptor with stderr around your printing code
set cs [ChannelSink new]
chan push stderr $cs
puts stderr "hello world"
chan pop stderr

Related

Process all files without exiting

I am working on implementing something where i need to check whether value of a variable is defined or not and then proceed with exiting the code. I kept this logic in one script where it has to check for all files opened on my perforce client.
eval { $test = $temp->project($loc); };
unless ($test){
print "undefiled value.please check.\n\n";
exit(1);
}
There are other files which are opened on my perforce client which needs to be validated. Here my script gets exiting when it sees first issue.
Here i want to display all the issues by validating all opened files on my client.
Any suggestions?
I guess you'd want to change the code to something like this:
# Before your loop, set up a variable to store errors
my #errors;
# Where your code is
eval { $test = $temp->project($loc) };
unless ($test) {
# Don't exit, but store the error and move to the next iteration
push #errors, "Undefiled value <$loc>. Please check.\n\n";
next;
}
# After your loop, die id there are any errors
die join "\n", #errors if #errors;
Update: I like ikegami's suggestion in the comments.
# Before your loop, set up a variable to count errors
my $errors;
# Where your code is
eval { $test = $temp->project($loc) };
unless ($test) {
# Don't exit, but store the error and move to the next iteration
warn "Undefiled value <$loc>. Please check.\n\n";
++$errors;
next;
}
# After your loop, die id there are any errors
exit(1) if $errors;

Net::MQTT::Simple->new() hangs?

I'm attempting to use the Net::MQTT::Simple package from cpan. The following is a modified version of the 'Object oriented' example on the same cpan page:
use Net::MQTT::Simple;
my $mqtt = Net::MQTT::Simple->new("test.mosquitto.org");
#This doesn't actually execute
print "After new";
$mqtt->run(
"test" => sub {
my ($topic, $message) = #_;
die "The building's on fire" if $message > 150;
},
"#" => sub {
my ($topic, $message) = #_;
print "[$topic] $message\n";
},
);
My script never seems to connect or at least the print line never gets executed. No error is reported and the program just hangs. The sever name is correct as I can connect via the mosquitto_sub client.
I feel like I'm missing something obvious. Any ideas/pointers on why this would be hanging?
It actually does execute. You are not seeing the output of it because of buffering.
Either add a newline to print:
print "After new\n";
Or enable autoflush:
$| = 1;
print "After new";
(If you look at the code of Net::MQTT::Simple you'll see that there are no blocking operations in the constructor at all. It's waiting for the messages in $mqtt->run, I guess there are no messages matching the topics you are subscribing to, so it appears to be "stuck")

Sending output to STDOUT in perl while executing a command

I am writing a perl code to upload code from a repro to a directory(jsvn update . a shell comand in my case) . I wanted that while the check in is going on, the result should display in stdout ('jsvn update .' does show that but i have to keep on looking at the monitor in case of any error and incase of error i have to give a clean up and start the process again.) I wrote a program for that, but it doesnot displays output to screen. The cursor keeps blinking and i know the process is going on background, but i want to have the results also displayed to stdout. Please help me.
#!usr/bin/perl
use Capture::Tiny qw/tee/;
sub code(){
`jsvn cleanup .`;
($stdout, $stderr, #result) = tee { system( "jsvn update ." ) };
print "#result\n";
}
code();
if($stderr){
code();
}else{
print "The checkout has been done successfully \n";
exit;
}
If you wanna use IPC::System::Simple you could grab exit values through $EXITVAL doing something like this:
...
use IPC::System::Simple qw[capture $EXITVAL];
use feature qw[switch];
...
my #result = capture('jsvn update .');
given ($EXITVAL) {
when (0) {
print "Ok\n";
}
when (1) {
}
..
when (N) {
}
}
...

Looping through data provided by Net::SSH2

I am using the following lib Net::SSH2
I can connect to my device and get the output OK in most case. Below is the relevant code:
sub logIntoDevice
{
my $self = shift;
my ($ssh2) = #_;
if(! $ssh2->connect($self->deviceIP))
{
say "Failed to connect to device:",$self->deviceIP;
$ssh2->disconnect();
exit 0;
}
if(! $ssh2->auth_password($self->usr, $self->pass))
{
say "Authentication Fail to ",$self->deviceIP;
$ssh2->disconnect();
exit 0;
}
my $channel = $ssh2->channel();
$channel->blocking(0);
$channel->shell();
return $channel;
}
sub sendCmd
{
my $self = shift;
my ($cmd,$channel) = #_;
my #cmdOutput;
print $channel "$cmd\n";
while (<$channel>)
{
chomp $_;
push(#cmdOutput, $_);
}
return #cmdOutput;
}
So below are the cmd i sent to the sub's. They work fine and the output is write to file OK.
$self->writeToFile($fileName,$self->sendCmd("show clock",$channel));
$self->writeToFile($fileName,$self->sendCmd("\n",$channel));
$self->writeToFile($fileName,$self->sendCmd("dir",$channel));
with the exception of when i sent the following cmd:
$self->writeToFile($fileName,$self->sendCmd("sh run",$channel));
the output of the cmd on the device using putty is:
sh run
Building configuration...
Current configuration : 16575 bytes
!
!
!Last configuration change at 16:37:19 CET+1 Sat Mar 15 2014
.....
but in the log file all you see is
sh run
Building configuration...
so the issue is the blank lines after the Building configuration output make the while (<$channel>) think its the end of the output.
My issue is I cant figure a way to loop through the data without using a While loop.
UPDATE
Ok come up with this solution but seems very clunky. must be a better way if doing this
sub sendCmd
{
my $self = shift;
my ($cmd,$channel) = #_;
my #cmdOutput;
my $currPrompt;
#get prompt. i am sure there is a better way!!! just cant figure it out
print $channel "\n";
while (<$channel>)
{
$currPrompt = $_;
}
print $channel "$cmd\n";
while(42)
{
my $inerOutput;
while (<$channel>)
{
chomp $_;
$inerOutput = $_;
push(#cmdOutput, $_);
}
if($inerOutput ne $currPrompt)
{
sleep(7);
}
else
{
last;
}
}
return #cmdOutput;
}
I don't think your issue is blank lines. Most likely, the issue is that you are using non-blocking mode, and it takes time for the device to perform the command. So you are getting an empty line (or an undef) after "Building configuration..." is read because there is no extra output produced yet.
I would use Net::SSH2's poll method with a timeout, which will let you know when there is something to read. If "sh run" takes substantially longer than other commands you are issuing, your sendCmd method needs to be aware of this, and allow more time to pass before it decides no more output is coming its way.
Alternatively, you can (as is the custom when using, for example, Net::Telnet) wait for more output until you see the prompt, whatever the prompt is for the device in question,
and then you will know that the command has finished its execution.
Net::SSH2->poll is deprecated as result of libss2_poll deprecation

perl: handle die before the framework

I am working with a perl framework which monitor $SIG{DIE} itself, my code was executed by the framework, so my exception handle code cannot be executed because the framework is first one to detected the exception then terminate the script.
frame.pm
sub execute
{
$SIG{__DIE__} = \&_handleDie;
eval{ #execute myscript.pl sub main
$rv = &$pFunct(#args);}
if ($#){ processException($#)}
print "myscript.pl success executed"
}
myscript.pl
use frame;
frame->execute( \&main );
sub main
{
%codes that redirect STDOUT to a file%
#if below API cmd no exception, hide it's output,
#otherwise output the API cmd STDERR msg
%codes called API of another module%
try
{
die("sth wrong");
}catch{
%codes restore STDOUT to terminal%
print "error msg, but this line will not be executed, how to get it be execute?"
}
}
The script first redirect STDOUT to a file for dumy some no use output.
When I want to implement is if exception happen(die line), the script can restore STDOUT to terminal then print error to terminal. Now it was handled by frame and print to STDOUT but not STDERR, so I need to handle restore STDOUT before frame print it to STDOUT.
with ruakh's solution, myscript.pl has passed SIG of frame, now catched by frame line if ($#){ processException($#)}, that is when execute myscript->die(), the program come to frame->if ($#){ processException($#)}, but not myscript->catch
=====================
I finally found this works for me:
myscript.pl
frame->execute( \&main );
sub main
{
open my $stdOri, ">&STDOUT";
my $tmpFile = "/tmp/.output.txt.$$";
open STDOUT, ">$tmpFile";
#overwrite frame provided exception handling.
local $SIG{__DIE__}=sub{close STDOUT; open STDOUT, ">&", $stdOri;};
#cause a exception,
#this exception will be processed by 'local $SIG{__DIE__}' block which restore STDOUT
#then frame->eval catch this exception, and print it in the terminal.
my $c=5/0;
}
thanks for ruakh's inspire.
Assuming that you don't want to modify the framework, you can locally override the signal-handler:
use frame;
frame->execute( \&main );
sub main
{
try
{
local $SIG{__DIE__}; # remove signal-handler
die("sth wrong");
}catch{
print STDERR "error msg";
die $#; # pass control back to framework's signal handler
}
}
Disclaimer: tested with an eval-block, rather than with try/catch, since I don't have TryCatch installed. My understanding is that TryCatch depends on eval, and not on $SIG{__DIE__}, but I could be wrong about that.
The framework's $SIG{__DIE__} handler is wrong wrong wrong. It shouldn't be eating exceptions inside of an eval. It should do die #_ if $^S as suggested by perldoc -f die.