rovf has asked for the wisdom of the Perl Monks concerning the following question:

It looks simple: I have a compiled third-party command line program (i.e. no source code available), where I want to catch its output (stdout and stderr). The program is available on Windoze and Unix/Linux, so I'm looking for a portable solution. Should be easy, isn't it?
$command="bsub $parameters" # 3rd party program ... $output=qx($command 2>&1); # Works also under Win
Now consider the case when $parameters happens to be a parameter which bsub doesn't like. For example, the call (on the Win/Unix) command line
bsub -k
happens to be illegal because of the -k switch. Not surprisingly, bsub complains:
bsub: option requires an argument -- k Usage: bsub ....
Now to the strange part: If I do the same under Perl, i.e.
$parameters='-k' ... $command="bsub $parameters" # 3rd party program ... $output=qx($command 2>&1); # Works also under Win
my $output is empty - this happens under Unix and Windows the same. It seems that bsub somehow finds out that its standard error is not connected to a terminal, and then refuses to print the message.

Is my guess likely correct, or are there other possibilities which could explain this behaviour?

And: Is it possible to fool a program (such as bsub) into thinking that it is connected to a terminal, although it is called via backticks from Perl? I guess there is indeed no workaround possible, but just wanted to make sure that I didn't miss something...

Ronald

Ronald Fischer <ynnor@mm.st>

Replies are listed 'Best First'.
Re: How to trick this program (weird catch-the-output problem)?
by mscharrer (Hermit) on Apr 30, 2008 at 13:49 UTC
    Please check if $output is really empty or undef (i.e. use defined). If it's really empty then you program really doesn't print anything.

    Otherwise: The back ticks return undef in scalar context when the called program fails, i.e. returns a non-null value. This is normally the case when wrong arguments are provided to the program. So your program probably prints the warning but it is not returned by the back ticks because the program call failed.

    You could try to use open in order to read the output:

    open (my $call, '-|', "$command 2>&1"); $output = join //, <$call>; # or use { local $/; $output = <$call>; } close ($call);
    Please see the perldoc -f open for the exact behavior of open in the case of failure.

    Update: It looks like the the back ticks return only undef when the given program couldn't be started, i.e. wrong name or permissions, but not when it fails. But checking if the output is really defined is still a good idea. Are you running the perl program under an other user, e.g. over CGI?

Re: How to trick this program (weird catch-the-output problem)?
by pc88mxer (Vicar) on Apr 30, 2008 at 14:22 UTC
    It seems that bsub somehow finds out that its standard error is not connected to a terminal, and then refuses to print the message.
    Can you check this theory by running bsub -k > some-file 2>&1 from the command line? If the error message shows up in some-file then that would be a work-around.

    Another option is to check $? to see if bsub exited with a 0 status. Commands usually exit with a non-zero status when there is an error condition. This won't get you the output, but at least you can tell if there was a problem:

    my $output = qx("$command 2>&1"); if ($?) { # something wrong }
      Good point. I tried
      bsub -k >a 2>&1
      and, indeed, a turns out to be empty. This means that bsub really behaves differently, when it sees that it is not connected to a terminal.
      Ronald Fischer <ynnor@mm.st>
Re: How to trick this program (weird catch-the-output problem)?
by alexm (Chaplain) on Apr 30, 2008 at 15:09 UTC
    If you find that bsub actually tests whether it's running from a tty, I guess that Expect (or Expect-Simple, if you prefer) should do the trick.
Re: How to trick this program (weird catch-the-output problem)?
by almut (Canon) on Apr 30, 2008 at 16:31 UTC

    As an alternative to Expect, you could also use IO::Pty directly, as you're not really interacting with your bsub program. IO::Pty is the pseudo terminal that Expect is using under the hood.  In its most simple case this could look something like this

    use IO::Pty; sub run_in_pty { my @cmd = @_; my $pty = IO::Pty->new(); my $pid = fork; die "Couldn' fork: $!" unless defined $pid; if ($pid) { # parent $pty->close_slave(); } else { # child my $slv = $pty->slave() or die "Couldn't get slave: $!"; close $pty; open STDOUT, ">&".$slv->fileno() or die "Couldn't reopen STDOU +T: $!"; open STDERR, ">&".$slv->fileno() or die "Couldn't reopen STDER +R: $!"; exec @cmd; die "Couldn't exec(@cmd): $!"; } return join '', (<$pty>); } my $cmd = "./bsub"; my $out = run_in_pty($cmd); print "$cmd: '$out'\n";

    Now, with this bsub-replacement (for demo purposes):

    #!/usr/bin/perl if (-t STDERR) { # connected to terminal? print STDERR "msg-to-stderr\n"; } print "msg-to-stdout\n";

    you should get both messages with the above run_in_pty routine, while if you'd do on the commandline:

    $ ./bsub >output 2>&1

    you'd only have "msg-to-stdout" in the output file.

    I'm afraid this doesn't work on native Windows, though... (Cygwin should be ok),  so hopefully, someone else knows a solution for this platform.

      Actually, IO::Pty is not only missing on Windows (in the ActiveState implementation), but is also not available under Cygwin....
      But thank you anyway for pointing this out.
      -- 
      Ronald Fischer <ynnor@mm.st>
Re: How to trick this program (weird catch-the-output problem)?
by chrism01 (Friar) on May 01, 2008 at 00:45 UTC