Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I watch a running program's STDOUT from a perl program that called it?

We're trying to figure out how to run a java application from within a perl script but still be able to periodically read from the java app's STDOUT.

print "running dcmrcv.bat\n";

open my $fh_dcmrcv, "-|", 'z:\apps\dcm4che\dcm4che-2.0.26\bin\dcmrcv.bat \
  DCMRCV:11112 -dest z:\dcmrcv -journal z:\dcmrcv', 
  or die "could not execute dcmrcv: $!";

print "dcmrcv.bat started\n";

We wanted to be able to read from the filehandle, $fh_dmcrcv, every few minutes or perhaps by setting up an AnyEvent io trigger when there is activity on the filehandle.

However, when I try and read from the filehandle, it blocks if I use something like this:

foreach my $line (<$fh_dmcrcv>) {
  print $line;
}

We've tried several approaches, don't think we can use File::Tail, since it seems that module needs an actual file. The issue seems to be that $fh_dcmrcv is blocking us when we read from it, not really sure of the correct approach on how to achieve what we want.

EDIT #1

When we run our perl script we're seeing output like this:

Z:\projects\demo_2>process_files.pl
running dcmrcv.bat
dcmrcv.bat started
Start Server listening on port 11112
11:55:13,495 INFO   - Start listening on 0.0.0.0/0.0.0.0:11112

The script, process_files.pl is emitting the msgs.:

running dcmrcv.bat
dcmrcv.bat started

The msgs. from the java program are: Start Server listening on port 11112 11:55:13,495 INFO - Start listening on 0.0.0.0/0.0.0.0:11112

In this case we're echoing those out just for the sake of this question, really we want to periodically parse for certain msgs. and never echo any of them out.

Any insight is appreciated,

-Sam

like image 904
slm Avatar asked Feb 09 '12 16:02

slm


Video Answer


2 Answers

Most systems support a 4-argument select function (also packaged nicely in IO::Select) that can tell you whether there is input waiting on sockethandle or pipehandle. In Windows, select is only supported on sockets, which leads to this byzantine solution:

  1. Create a socket pair
  2. Fork and run the command in a child process
  3. In the child process, retrieve command output and write it to the socket
  4. In the parent, use select and read operations on the socket as desired

Example:

use Socket;
use IO::Select;
use Time::HiRes;
$cmd = $^X . ' -MMath::BigInt -e "$_=1; '
       . 'print qq/$_!=/,Math::BigInt->new($_)->bfac(),qq/\n\n\n/'
       . ' for 4000..4100"';
socketpair A,B,AF_UNIX,SOCK_STREAM,PF_UNSPEC;  # step 1
if (fork() == 0) {
    open my $fh, '-|', $cmd;                   # step 2
    while (<$fh>) {
        print B;                               # step 3
    }
    close $fh;
    exit $? >> 8;
}
$s = IO::Select->new();
$s->add(\*A);
for (;;) {
    if ($s->can_read(0.25)) {                  # step 4
        $line = <A>;
        print "Do something with command output: $line";
    } else {
        print "No output now. Could be doing something else ...\n";
        Time::HiRes::sleep(0.25);
    }
}

Are you sure you don't want to just write your command output to a temp file?

like image 186
mob Avatar answered Oct 23 '22 05:10

mob


Use IO::Select or four-argument select() to read when reading will not block. EDIT: Nevermind, you're on Windows, so this won't work. One option: have an intermediary process read from this pipe and keep an intermediary file filled with just whatever tail-length you need of it. Bleh.

That said, the best way to read "every few minutes" from a pipe is to read from it every few seconds.

like image 36
Julian Fondren Avatar answered Oct 23 '22 05:10

Julian Fondren