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

Hi, this question arises from a question on the perl.beginners maillist. Someone asked for a IPC script and I showed him the following:
#!/usr/bin/perl use warnings; use strict; use IPC::Open3; #interface to "bc" calculator #my $pid = open3(\*WRITE, \*READ, \*ERROR,"bc"); my $pid = open3(\*WRITE, \*READ,0,"bc"); #if \*ERROR is false, STDERR is sent to STDOUT while(1){ print "Enter expression for bc, i.e. 2 + 2\n"; chomp(my $query = <STDIN>); #send query to bc print WRITE "$query\n"; #get the answer from bc chomp(my $answer = <READ>); print "$query = $answer\n"; }
This works fine and dosn't block because STDERR is sent to STDOUT. However the original poster had to ask for them to be separated, and it turned out to be trickier than I thought. So I'm posting the code I came up with below, and wondering if it's the best way to do it. At first I thought I could just read ERROR like READ and just print out both....but no...the filehandles blocked each other and the script would hang.

Also a few specific questions:

(1.) If I don't put the select(undef,undef,undef,.01) delay in, the script gives it's output on the next entry. Try it and see what I mean, the output is delayed by 1 cycle. The delay fixes it.

(2.) I did the sysread with a size of 4096. How can I just read the size of the filehandle? I tried using "-s ERROR" and "-s READ" instead of 4096, but would get no output.

(3.) Is there a way to do this without IO::Select?

#!/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 $selread = new IO::Select(); my $selerror = new IO::Select(); $selread->add(\*READ); $selerror->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"; #timing delay needed tp let bc output select(undef,undef,undef,.01); #see which filehandles have output for debugging # if($selread->can_read(0)){print "ready->read\n"} # if($selerror->can_read(0)){print "ready->error\n"} #get any error from bc sysread(ERROR,$error,4096) if $selerror->can_read(0); if($error){print "ERROR-> $error\n"} #get the answer from bc sysread(READ,$answer,4096) if $selread->can_read(0); if($answer){print "$query = $answer\n"} ($error,$answer)=('',''); }

Replies are listed 'Best First'.
Re: IPC::Open3 and blocking filehandles
by sgifford (Prior) on Oct 17, 2003 at 20:34 UTC

    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.

      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.
Re: IPC::Open3 and blocking filehandles
by pg (Canon) on Oct 17, 2003 at 19:54 UTC

    A side note, the way you used IO::Select is not quite right.

    One of the main reasons to have IO::Select is to use one function, check multiple channels, so that those channels will not block each other even on one thread. Usually there is no point to have multiple IO::Select (in general)