Re: STDERR going to string
by Tanktalus (Canon) on Oct 03, 2011 at 18:16 UTC
|
Your basic problem is that T::BT uses XS code which in turn uses stdio instead of perl IO to print to stderr. Capturing this will become much more problematic, though not necessarily impossible.
There may be modules that can handle this, I'm not sure. However, one for-sure way to do this is to call T::BT in a subprocess, and capture its stderr. Inside a single process, you may be able to set up threads (even Coro threads) or events with a pipe - set up your stderr to a pipe (which is handled by the C library just fine) and have an event listener/thread read it in and append it to your string manually.
I don't think perl can do this by itself with the reference-to-a-scalar trick.
Long term, ask the module author to use perl IO instead of stdio for their output in XS code.
| [reply] [Watch: Dir/Any] |
|
Ah, I see, thank you. I assumed it was something to do with how the library/module handled STDERR ... I'll see what I can do.
| [reply] [Watch: Dir/Any] |
|
I can get around this by redirecting to a file and reading it later. I suspect I could use Config::Tiny or File::Tee too but it seems it's a harder problem on Windows because re-opening STDERR decouples perl's STDERR from the called C library's STDERR which seems impossible to capture. If anyone has any ideas about this ...
Since the issue is a library called from XS and not the XS extension itself and the fact that this library is also used apart from perl, it can't be modified to use PerlIO ...
| [reply] [Watch: Dir/Any] |
|
| [reply] [Watch: Dir/Any] |
|
|
|
|
open my $errsave, '>&', STDERR
or do { print "couldn't dup STDERR: $!"; exit 2; };
open STDERR ...
...
open STDERR, '>&', $errsave
or do { print "couldn't restore/dup STDERR: $!"; exit 2; };
| [reply] [Watch: Dir/Any] [d/l] |
Re: STDERR going to string
by kennethk (Abbot) on Oct 03, 2011 at 14:59 UTC
|
Your described behavior sounds odd, and thus I'd really like the opportunity to replicate your behavior. If you could post a self-contained code snippet that replicates your issue, I might be able to do just that. (wrap it in <code> tags instead of <pre> tags while you are at it). The code you have posted above correctly performs the redirects for me. See I know what I mean. Why don't you?. | [reply] [Watch: Dir/Any] [d/l] [select] |
|
I've updated the original post with a minimal example displaying the problem. It seems to work when I write to a file instead of a string, which is strange. Even stranger is that when writing to a file, the order is wrong, I see the Text::BibTeX error at the top of the file and "manual error" after this. Buffering issue? Tried this on two different machines, perl 5.14.1 on both, one cygwin, one OSX.
| [reply] [Watch: Dir/Any] |
|
Tanktalus has diagnosed your issue below, and I unfortunately have nothing useful to add.
| [reply] [Watch: Dir/Any] |
Re: STDERR going to string
by OlegG (Monk) on Oct 03, 2011 at 15:32 UTC
|
You can try to use $SIG{__WARN__} handler instead. | [reply] [Watch: Dir/Any] |
Re: STDERR going to string
by salva (Canon) on Oct 11, 2011 at 09:44 UTC
|
There is only one truly portable and robust way to do that: redirect STDERR to a temporal file before calling into XS and read it afterwards.
# Untested. Note also that this code will not handle special
# cases as STDERR being closed, tied or attached to a file
# descriptor different than 2.
use autodie;
use File::Temp qw(tempfile);
open my $olderr, '>&STDERR'; # save STDERR
my $fh = tempfile();
open STDERR, '>&', $fh;
my $entry = Text::BibTeX::Entry->new($bib_entry);
open STDERR, '>&', $olderr; # flush and reset STDERR
seek($fh, 0, 0);
my $err = do { local $/; <$fh> };
close $fh;
| [reply] [Watch: Dir/Any] [d/l] |
|
I had something like this too but it doesn't work on Windows. Here is a stripped down example using Inline::C. On non-Windows (cygwin included), it outputs correctly:
|ERROR|
On Windows, it gives:
||
ERROR
which shows you that the STDERR isn't being captured.
use File::Temp qw(tempfile);
use Inline C;
open my $olderr, '>&STDERR'; # save STDERR
my ($fh, $fn) = tempfile();
open STDERR, '>&', $fh;
test_err();
open STDERR, '>&', $olderr; # reset STDERR
close $fh;
open my $e, '<', $fn;
my $err = <$e>;
print "|$err|\n";
__END__
__C__
void test_err()
{
(void)fprintf (stderr, "ERROR");
}
| [reply] [Watch: Dir/Any] [d/l] |
|
use File::Temp qw(tempfile);
use Inline 'C';
open my $olderr, '>&STDERR'; # save STDERR
my ($fh, $fn) = tempfile();
open STDERR, '>&', $fh;
printf "fileno STDERR: %d\n", fileno(STDERR);
print STDERR "foo!";
test_err();
open STDERR, '>&', $olderr; # reset STDERR
close $fh;
open my $e, '<', $fn;
my $err = <$e>;
print "|$err|\n";
__END__
__C__
void test_err() {
FILE *err = fdopen(2, "a+");
(void)fprintf (err, "ERROR");
}
# here it says:
# fileno STDERR: 2
# |foo!|
# ERROR
Besides that, don't trust what you get from Inline::C, the perlio.h header is included before your C code and most stdio functions are replaced by macros calling into perl own implementations (that not-too-unsurprisingly, do not work as expected either):
update: the funny thing is that when a subprocess is launched, perl file descriptors are the ones inherited:
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
|
|
Re: STDERR going to string
by Anonymous Monk on Oct 12, 2011 at 09:27 UTC
|
You want to use Capture::Tiny, it works on win32, it does it through fork+exec and presents a beautiful interface
use Capture::Tiny 'capture';
my ($out, $err) = capture {
print "ya";
Text::BibTeX ...
system ...
};
print "captured out((($out))) err((($err)))";
| [reply] [Watch: Dir/Any] [d/l] |
|
Here is a simple example showing that even Capture::Tiny doesn't work in this case on Windows.
#!/usr/bin/perl
use Capture::Tiny 'capture';
use File::Temp qw(tempfile);
use Inline C;
my ($stdout, $stderr) = capture { test_err() };
print "|$stderr|\n";
__END__
__C__
void test_err()
{
(void)fprintf (stderr, "ERROR");
}
| [reply] [Watch: Dir/Any] [d/l] |
|
I'm embarrassed. Someone pointed out to me that Windows doesn't flush STDERR as often as other OSES. With one extra line of C, it works on Windows and I think I can modify the external library this much ...
#!/usr/bin/perl
use Capture::Tiny 'capture';
use File::Temp qw(tempfile);
use Inline C;
my ($stdout, $stderr) = capture { test_err() };
print "|$stderr|\n";
__END__
__C__
void test_err()
{
(void)fprintf (stderr, "ERROR");
(void)fflush(stderr);
}
| [reply] [Watch: Dir/Any] [d/l] |
|
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
I tried it - even that doesn't work in this case on windows ...
| [reply] [Watch: Dir/Any] |
Re: STDERR going to string
by pvaldes (Chaplain) on Oct 15, 2011 at 14:05 UTC
|
Ok, this is the error that you want to catch, two "," at the end of the line. This is not a perl error, is a bibtex error of course, perl should know nothing about this (but Text::BibTeX should).
TITLE = {Arranging Arguments},,
and if I comment all the fuss about STDERR and simply modify this line I got the bibtex error correctly
my $entry = new Text::BibTeX::Entry $bib_entry or die $!;
Update: Is obvious after reading the other answers that I had not understood the question at all...
| [reply] [Watch: Dir/Any] [d/l] [select] |
|
Update: Is obvious after reading the other answers that I had not understood the question at all...
It is ok, at least you realized it on your own :)
| [reply] [Watch: Dir/Any] |