in reply to IPC::Open3 and blocking filehandles

You do need to use select, or at least I can't think of any other way to do this.

Your use of sysread is fine; the 4096 is just a maximum. You can see how much data was actually read by looking at length($answer), and if there's more than 4096 bytes available you'd have to call this in a loop.

The reason for your problem is because you're not using IO::Select quite right. You're passing a timeout of 0 to can_read, which asks it to return immediately if there is no data ready yet. What I think you want to do is create one IO::Select object with both handles, then ask it to wait until one or both have something ready to read. Something like this:

#!/usr/bin/perl use warnings; use strict; use IPC::Open3; use IO::Select; #interface to "bc" calculator my $pid = open3(\*WRITE, \*READ,\*ERROR,"bc"); my $sel = new IO::Select(); $sel->add(\*READ); $sel->add(\*ERROR); my($error,$answer)=('',''); while(1){ print "Enter expression for bc, i.e. 2 + 2\n"; chomp(my $query = <STDIN>); #send query to bc print WRITE "$query\n"; foreach my $h ($sel->can_read) { my $buf = ''; if ($h eq \*ERROR) { sysread(ERROR,$buf,4096); if($buf){print "ERROR-> $buf\n"} } else { sysread(READ,$buf,4096); if($buf){print "$query = $buf\n"} } } }

This technique won't work if bc might output more than one line. One way to work around that is to fork a process to handle the reading from bc, and use the parent process to handle writing. There's an example of this in Re: Perl Web Zombie problem.

Replies are listed 'Best First'.
Re: Re: IPC::Open3 and blocking filehandles
by zentara (Cardinal) on Oct 18, 2003 at 00:12 UTC
    Hi, well I did some digging and came up with a method from "perldoc -f filehandle". It is from "how to read a byte from a filehandle". I did come up with an error at first telling me that my cdefs.ph file had a bunch syntax errors between ") (". I wonder if this is a bug in 5.8? The lines in question all needed a comma between the ") (" .

    A typical line needing the comma is

    if(defined (defined(&__cplusplus) ? &__cplusplus : 0) && (defined(&__GNUC_PREREQ) ? &__GNUC_PREREQ : 0), (2,8)) {

    Notice the comma I inserted between 0) and (2,8)).

    Anyways, after fixing that, the following code does what I was looking for. :-)

    #!/usr/bin/perl use warnings; use strict; use IPC::Open3; require 'sys/ioctl.ph'; #interface to "bc" calculator my $pid = open3(\*WRITE, \*READ,\*ERROR,"bc"); #if \*ERROR is false, STDERR is sent to STDOUT my($error,$answer)=('',''); while(1){ print "Enter expression for bc, i.e. 2 + 2\n"; chomp(my $query = <STDIN>); #send query to bc print WRITE "$query\n"; #timing delay needed tp let bc output select(undef,undef,undef,.01); #see which filehandles have output from perldoc -q filehandle my $esize = pack("L", 0); ioctl(\*ERROR, FIONREAD(), $esize) or die "Couldn't call ioctl: $!\n"; $esize = unpack("L", $esize); print "esize-> $esize\n"; my $rsize = pack("L", 0); ioctl(\*READ, FIONREAD(), $rsize) or die "Couldn't call ioctl: $!\n"; $rsize = unpack("L", $rsize); print "rsize-> $rsize\n"; #get the output from bc if($esize > 0){sysread(ERROR,$error,$esize); print "Error-> $error\n"} if($rsize > 0){sysread(READ,$answer,$rsize); print "Output->$query = $ +answer\n"} ($error,$answer,$rsize,$esize)=('','',0,0); }
      This is still dependant on bc having its output ready within .01 seconds. If the system were very busy or otherwise bogged down and could't meet this deadline, you'd miss a line of output, and then would be off by a line until the program restarted.
        Thanks for pointing out those flaws, sgifford. I did modify my method, so it didn't need to use the delay with select. It waits in a loop until output is available. This will give multiline output from READ, but now I'm stumped by another problem. It seems the output buffer which bc writes to with IPC::Open3 is limited to 4060 bytes. I've tried alot of things, but no matter what I do, bc stops output at 4060 bytes, but will give full output if run by itself from the command line. Try 123^12345 ; which if run from the commandline will output about 16k, but when run from IPC::Open3 will clip at 4060 bytes.
        #!/usr/bin/perl use warnings; use strict; use IPC::Open3; require 'sys/ioctl.ph'; #interface to "bc" calculator my $pid = open3(\*WRITE, \*READ,\*ERROR,"bc"); #if \*ERROR is false, STDERR is sent to STDOUT while(1){ my($error,$answer,$rsize,$esize)=('','',0,0); print "Enter expression for bc, i.e. 2 + 2\n"; chomp(my $query = <STDIN>); #send query to bc print WRITE "$query\n"; #timing delay needed tp let bc output #select(undef,undef,undef,.01); #this while loop waits and eliminates need for above delay #but is limited to 4060 bytes from bc do { #see which filehandles have output from perldoc -q filehandle $esize = pack("L", 0); ioctl(\*ERROR, FIONREAD(), $esize) or die "Couldn't call ioctl: +$!\n"; $esize = unpack("L", $esize); print "esize-> $esize\n" unless ($esize < 1); $rsize = pack("L", 0); ioctl(\*READ, FIONREAD(), $rsize) or die "Couldn't call ioctl: $ +!\n"; $rsize = unpack("L", $rsize); print "rsize-> $rsize\n" unless ($rsize <1); #get the output from bc if($esize > 0){sysread(ERROR,$error,$esize); print "\e[1;31m ERRO +R-> $error \e[0m \n"} if($rsize > 0){sysread(READ,$answer,$rsize); print "Output->$quer +y = $answer\n"} } until(($esize > 0)or($rsize > 0)); }