Comunicación Bidireccional con el Módulo IPC::Run

Una mejor solución al problema de la comunicación bidireccional la provee el módulo IPC::Run . El módulo IPC::Run facilita la ejecución de procesos hijo con los que se puede interaccionar utilizando ficheros, pipes y seudoterminales (denominadas también pseudo-ttys 6.1 ó pty ).

Veamos un ejemplo sencillo:

lhp@nereida:~/Lperl/src/ipcrun$ cat -n ipccat
 1  #!/usr/bin/perl -w
 2  use strict ;
 3
 4  my @cat = qw( cat ) ;
 5  my ( $in_q, $out_q, $err_q ) ;
 6
 7  use IPC::Run qw( start pump finish timeout ) ;
 8
 9  # Incrementally read from / write to scalars.  Note that $in_q
10  # is a queue that is drained as it is used. $h is for "harness".
11  my $h = start \@cat, \$in_q, \$out_q, \$err_q, timeout( 10 ); 
12
13  $in_q .= "some input\n" ;
14  pump $h until $out_q =~ /input\n/g ;
15  print "---\n$out_q" ;
16
17  $in_q .= "some more input\n" ;
18  pump $h until $out_q =~ /\G.*more input\n/ ;
19  print "---\n$out_q" ;
20
21  $in_q .= "some final input\n" ;
22  pump $h until $out_q =~ /.*final input\n/ ;
23  print "---\n$out_q" ;
24
25  $in_q .= "some extra input\n" ;
26  finish $h or die "cat returned $?" ;
27
28  warn $err_q if $err_q ;
29  print "---\nFinally:\n$out_q" ;
Los procesos hijos son reunidos en un harness, arrancados (línea 11) y ejecutados hasta su finalización o hasta que sean abortados (por ejemplo, con un die).

Existen dos modos de trabajo. Uno utiliza run() el cual funciona como una extensión de system(). El otro, al que corresponde el ejemplo anterior viene dado por la tripleta start, pump , finish .

La función start se encarga de crear el harness y lanzar los subprocesos vía fork o exec y arranca los cronómetros (véase perldoc( IPC::Run::Timers ). El primer argumento de start es el comando y sus argumentos. Por eso es un array. La función pump nos permite muestrear la actividad de los mismos. La función finish (línea 26) permite monitorizar la actividad del harness hasta su terminación.

Como se muestra en las líneas 14, 18 y 22 la lectura con pump utiliza expresiones regulares para detectar los puntos de parada. Se puede usar una sintáxis de función, como en el ejemplo, o la de flecha como método:

$h->pump

Si la expresión regular no casa se produce un atasco.

Los Timeouts lanzan excepciones que hacen que pump retorne después de pasado un cierto tiempo.

El método finish debe ser llamado después del último pump. En caso contrario se acumularan los procesos defunct y podemos tener pérdidas de descriptores de ficheros. El método finish retorna TRUE si y sólo si todos los hijos retornan 0 y su terminación no fué la consecuencia de una señal (véase sección 3.4) y no se produjo un coredump , esto es la variable $? está undef (véase sección 1.6).

La ejecución del código anterior produce la siguiente salida:

lhp@nereida:~/Lperl/src/ipcrun$ ipccat
---
some input
---
some input
some more input
---
some input
some more input
some final input
---
Finally:
some input
some more input
some final input
some extra input

Veamos un segundo ejemplo, en el que se establece una sesión con un proceso que ejecuta la calculadora bc de Unix. La sesión calcula el factorial de un número enviando sucesivas entradas a bc y obteniendo sus resultados:

lhp@nereida:~/Lperl/src/ipcrun$ cat -n fact.pl
 1  #!/usr/bin/perl -w
 2  use strict ;
 3  use IPC::Run qw( start timeout ) ;
 4
 5  die "usage: $0 <num>\n\nwhere <num> is a positive integer\n" unless @ARGV ;
 6  my $i = shift ;
 7  die "\$i must be > 1, not '$i'" unless $i =~ /^\d+$/ && $i > 1 ;
 8
 9  my ( $in, $out ) ;
10
11  my $h = start ['bc'], \$in, \$out, timeout( 5 ) ;
12
13  $in = "fact = i = $i ; i\n" ;
14
15  while () {
16     $out = '' ;
17     $h->pump until $out =~ s/.*?([-+]?\d+)\n/$1/g ;
18     print "bc said: $out\n" ;
19     if ( $out  <= 0 ) {
20        print "result = ",-$out,"\n" ;
21        $in = undef ;
22        last ;
23     }
24     elsif ( $out eq '2' ) {
25        ## End of calculation loop, get bc to output the result
26        $in = "-fact\n" ;
27     }
28     else {
29        $in = "i = i - 1 ; fact = fact * i ; i\n" ;
30     }
31  }
32
33  $h->finish ;
Para detectar el final hacemos que la calculadora devuelva el resultado cambiado de signo (línea 26). Al ejecutar obtenemos la salida:
lhp@nereida:~/Lperl/src/ipcrun$ ./fact.pl 6
bc said: 6
bc said: 5
bc said: 4
bc said: 3
bc said: 2
bc said: -720
result = 720

Casiano Rodríguez León
2012-02-29