Using perl open3 to pipe data through gnuplot

open3 is useful for piping through some program, and back. This is handy in many situations - personally I find it handy for turning database tables into graphs with gnuplot . It is very similar to open 2 with the added advantage of being able to capture standard error messages

You want to use open3 when you're doing something like this;

  1. You have a program which takes something on it's standard input, and changes into something on standard output, and may generate information on standard error. The simplest example of such a program is something like sort, though, in my example, I've used gnuplot .
  2. You have a bunch of stuff you want to feed to the program.
  3. In return, you want to get a whole bunch of stuff, after having being passed through the program

The following example takes an array and generates a PNG graph directly from it, using gnuplot. I've omitted code to check for sanity (For example, checking that $xArray and $yArray are the same length). You should also check out this book if you want more detailed perl examples.

 
        print "Content-type:image/png\n\n";                                                            

# Many examples for open3 don't include creation of the filehandles.
# You aren't meant to pass them in without creating them first!
# IMPORTANT - open2 and open3 have their parameters in a slightly different order.
# Be sure and look if you're switching between them! ;) 
        local (*READ, *WRITE, *ERROR);                                                                      
        my $pid = open3( \*WRITE, \*READ, \*ERROR, "gnuplot" );                                                                                                                                                                   

# We could have also done it like this 
# my ($readFH, $writeFH, $errorFH); 
# my $pid     = open3($writeFH, $readFH, $errorFH, "gnuplot");   

                                                                                                                   
                                                                                                                   
        print WRITE "set terminal png small\n";                                                                    
        print WRITE "set nokey\n";                                                                                 
        print WRITE "set grid\n";                                                                                  
        print WRITE "set size 0.5, 0.5\n";                                                                       
        print WRITE "set data style points\n";                                                                     
                                       
        print WRITE "set title \'$title\'\n";                                                                      
        print WRITE "set xlabel \'$xLabel\'\n";                                                                    
        print WRITE "set ylabel \'$yLabel\'\n";                                                                    
        print WRITE "plot \'-'\n";                                                                                 
                                                                                                                   
        foreach my $index (0..$#xArray)                                                                            
                {                                                                                                  
                print WRITE "$xArray[$index]\t$yArray[$index]\n";                                                  
                }                                                                                                  
                                                                                                                   
        close(WRITE); 

# Contrasting this with the open2 example, we need to 
# add in this wacky select code.  The reason is because there
# are two streams (STDOUT and STDERR) which could have data
# and we can't block and wait on either of them. 
# Instead, we monitor both, using select. 

       my $sel = IO::Select->new(); 
       $sel->add(\*ERROR, \*READ); 

while(my @ready = $sel->can_read)
{ 
        foreach my $handle (@ready) { 

               
                 
                 if (fileno($handle) == fileno(ERROR))  
                           { 
                          # Gnuplot has printed something on standard error 
                            exit; 
                           }        
                  else 
                            { 
                            my ($count, $data); 
                            $count = sysread($_, $data, 1024); 
 
                             # Business as usual. 
                             print $data; 
                             } 
                    $sel->remove($handle) if eof($handle); 
      
                    }                                                                                          


 }

close(READ); 
close(ERROR); 
 

Important note!

There are three important things to remember when using open3 . They are (listed in order of importance):
  1. open3 does not play well with mod_perl.
  2. open3 does not play well with mod_perl.
  3. open3 does not play well with mod_perl.
Please let me know if you find out why that is.