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

I've got 2 filehandles that run a process each. I need to work with both inputs in the same loop. What process returns output first is unknown but can be distinguished by $_ pattern matching. Sample code doesn't work but sortof demonstrates what I need to do. Proc1 might be silent for 10mins while proc2 is creating output which I need to work on in the loop, or proc1 is creating output that I need to read while proc2 is not producing anything. If both procs are producing output I'd need to make sure I can separate it.
$SG = open(Proc1,"Program1.exe|"); $FW = open(Proc2,"Program2.exe|"); while (<Proc1>) || <Proc2>) { print $_; }

Replies are listed 'Best First'.
Re: working with 2 inputs
by Corion (Patriarch) on Oct 01, 2008 at 10:54 UTC

    See IO::Select, select, threads, POE::Wheel::Follow or Coro. Likely, AnyEvent also provides an abstraction of a select loop.

    It really depends how much frameworkness you need. IO::Select nicely? abstracts away most of the uglyness of select, which is why I listed it before the select call itself. All other modules are frameworks that more or less tie you into their respective idiom of doing IO and other event handling. AnyEvent tries to abstract away the main event loop.

      OK, I have tried using IO::Select. This is what I got so far. After it goes into the while loop $rh_set is empty and the select doesn't wait for output. It's got 100%cpu and loops without going into ForEach loop to DoSomething. What am I doing wrong?
      $w1 = open(fw1,"pgm1|"); $w2 = open(fw2,"pgm2|"); $read_set = new IO::Select(); $read_set->add($w1); $read_set->add($w2); while (1) { ; my ($rh_set) = IO::Select->select($read_set, undef, undef, 2); print $rh_set; foreach $rh (@$rh_set) { ...DoSomething... } }

        As has already been mentioned, select doesn't work with pipes on Windows.  So this note is just for the record.

        $w1 = open(fw1,"pgm1|"); ... $read_set->add($w1);

        open() in this case (iff a pipe is involved) returns a PID, whereas IO::Select's add() method is expecting a file handle...  In other words, you'd rather do something like this (on Unix):

        my $pid1 = open my $fh1, "pgm1|"; my $pid2 = open my $fh2, "pgm2|"; my $read_set = new IO::Select(); $read_set->add($fh1); $read_set->add($fh2); ...

        BTW, as said, the return value of open() here is the PID of the subprocess, or undef if the fork failed. It says nothing about whether the command itself (i.e. pgm1 etc.) succeeded. For this reason, you'd normally also want to check the return value when closing the file handle:

        close $fh1 or warn "subprocess failed: status=$?\n";
Re: working with 2 inputs
by BrowserUk (Patriarch) on Oct 01, 2008 at 15:08 UTC

    The inevitable simple, threaded solution:

    #! perl -slw use strict; use threads; use Thread::Queue; my $Q = new Thread::Queue; my $cmd = 'perl -le"$|++;print $$ . q[:] . localtime() and sleep 1 for 1..rand( +10)" |'; async{ my $pid1 = open my $fh, $cmd or die $!; $Q->enqueue( $_ ) while <$fh>; close $fh; $Q->enqueue( undef ); }->detach; async{ my $pid2 = open my $fh, $cmd or die $!; $Q->enqueue( $_ ) while <$fh>; close $fh; $Q->enqueue( undef ); }->detach; for( 1.. 2 ) { while( defined( local $_= $Q->dequeue ) ) { chomp; print; } } __END__ c:\test>junk6 3140:Wed Oct 1 16:04:35 2008 216:Wed Oct 1 16:04:35 2008 3140:Wed Oct 1 16:04:36 2008 216:Wed Oct 1 16:04:36 2008 3140:Wed Oct 1 16:04:37 2008 216:Wed Oct 1 16:04:37 2008 3140:Wed Oct 1 16:04:38 2008 216:Wed Oct 1 16:04:38 2008 216:Wed Oct 1 16:04:39 2008 216:Wed Oct 1 16:04:40 2008 216:Wed Oct 1 16:04:41 2008 c:\test>junk6 3308:Wed Oct 1 16:04:46 2008 3804:Wed Oct 1 16:04:46 2008 3308:Wed Oct 1 16:04:47 2008 3308:Wed Oct 1 16:04:48 2008 3308:Wed Oct 1 16:04:49 2008 3308:Wed Oct 1 16:04:50 2008 3308:Wed Oct 1 16:04:51 2008 3308:Wed Oct 1 16:04:52 2008

    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
      Yes thanks for that. Unfortunately the servers this will run on are all missing threads.pm So that won't be a solution I can use because updating the servers, which I am not in charge of, is not really an option.
      print "read_set is now: $read_set \n"; $read_set->add(fw1); print "read_set is now: $read_set \n"; $read_set->add(fw2); print "read_set is now: $read_set \n";
      this results in: read_set is now: IO::Select=ARRAY(0x1acf0e4) read_set is now: IO::Select=ARRAY(0x1acf0e4) read_set is now: IO::Select=ARRAY(0x1acf0e4) Is this what's expected after $read_set->add(FH)?
        Unfortunately the servers this will run on are all missing threads.pm

        You build your own win32 Perl?

        this results in: read_set is now: IO::Select=ARRAY(0x1acf0e4)...

        As salva pointed out above, IO::Select doesn't work with pipes on Win32. Follow the link he provided for some alternatives.


        Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
        "Science is about questioning the status quo. Questioning authority".
        In the absence of evidence, opinion is indistinguishable from prejudice.
Re: working with 2 inputs
by zentara (Cardinal) on Oct 01, 2008 at 13:35 UTC
    In addition to what Corion said about using an event loop, Glib( base of Gtk2) has a neat way to watch filehandles, see Roll your own Event-loop. The "man Glib::MainLoop" manpage does not mention anything about not working on win32, I think I will ask on the Perl/Gtk2 maillist.

    UPDATE: Answers from the Perl/Gtk2 maillist indicate that the Window's port of Glib does include some "select" functionality, so Glib::IO->add_watch() works on Win32, allowing you to read the output from piped-opened filehandles in a callback. This is great news. :-)


    I'm not really a human, but I play one on earth Remember How Lucky You Are