Pipes

Se puede utilizar open para lanzar un proceso en pipe con el programa actual:
open $DATE, "date|" or die "Falló la creación del pipe desde date: $!";
open $MAIL, "|mail alu1324@csi.ull.es" or die "Falló el pipe hacia mail: $!";
La barra al final indica que la salida del proceso lanzado alimenta la entrada a nuestro programa a través del manipulador. Del mismo modo, la barra al principio indica que la entrada del proceso lanzado se alimenta de las salidas de nuestro programa hacia el correspondiente manipulador.

Para leer desde el proceso ejecutando date basta usar el operador diamante sobre el manejador:

my $now = <$DATE>;
y para escribir al proceso $MAIL hacemos:
print $MAIL "Estimado alumno, ha obtenido usted un sobresaliente.\n";

Valor Retornado por open

Cuando se abre un pipe, open devuelve el identificador del proceso del comando al otro lado del pipe. Este entero puede ser utilizado para monitorizar o enviar posteriormente señales al proceso.

Asegúrese de comprobar los valores retornados por open y close cuando utilice un pipe.

Para entender la razón, piense que sucede cuando se arranca un pipe a un comando que no existe. El éxito del comando open refleja el hecho de que el fork pudo hacerse con éxito. Pero en cuanto intentemos la escritura en el pipe se producirá el error y se recibirá una señal PIPE que deberemos manejar. Si se trata de lectura Perl interpretará que se ha producido un final de entrada.

Ejercicio 1.6.1   Explique la diferencia de conducta entre estos dos ejemplos Pruebe a sustituir el print de la línea 6 por uno en el que se imprima una cadena de gran tamaño (Use print '*'x1E06). ¿Cambia la conducta? Si es así, ¿A que cree que puede deberse?

Sincronización mediante close

Al cerrar un filehandle:

close(MAIL);
die "mail: salida errónea, $?" if $?;
nuestro proceso se sincroniza con el proceso lanzado, esperando a que este termine y obtener el código de salida, el cual queda almacenado en la variable $? .

Esqueleto de un Pipe

En general el esquema de uso para leer desde un programa en pipe es

my $pid = open(my $README, "program arguments | ") or die("Couldn't fork: $|\n";
while ($input = <$README>) {
 ...
}
close($README);
# Check $?

La variable $?

La variable $? ( $CHILD_ERROR si se usa el módulo English ) contiene el estatus retornado por el cierre de un pipe (close), la ejecución de un comando qx, una llamada a wait o una llamada a system. Un ejemplo de uso, comprobando el estatus es:

 if ($? == -1) { # $? es -1 si no se pudo ejecutar
     print "No se pudo ejecutar: $!\n";
 }
 elsif ($? & 127) {
     printf "El proceso hijo a muerto con señal %d, %s coredump\n",
         ($? & 127),  ($? & 128) ? 'con' : 'sin';
 }
 else { # El valor de salida del proceso es $? >> 8
     printf "Proceso hijo termina con estatus %d\n", $? >> 8;
 }
Si quiere obtener mas información sobre $? lea perldoc perlvar.

Pipes con IO::File

Una alternativa es usar objetos IO::File:

pp2@nereida:~/LGRID_Machine/lib/GRID$ perl -wde 0
main::(-e:1):   0
  DB<1>  use IO::File
  DB<2> $DATE = IO::File->new
  DB<3> $DATE->open("date|") or die "Falló la creación del pipe desde date: $!"
  DB<4> p <$DATE>
lun mar  3 12:17:57 WET 2008

Ejecución de un Proceso Mediante Open con Pipe

Veamos un ejemplo:

lhp@nereida:~/Lperl/src$ cat -n rwho.pl
 1  #!/usr/bin/perl -w
 2  use strict;
 3
 4  my $host = shift;
 5
 6  my $pid = open(my $WHOFH, "ssh $host who |") or die "No se pudo abrir who: $!";
 7  print "PID of pipe process: $pid\n";
 8  system(<<"EOC");
 9    ssh $host ps -f  &&
10    echo --------------------------- &&
11    pstree -p $$ &&
12    echo ---------------------------
13  EOC
14
15  while (<$WHOFH>) {
16    print $_;
17  }
18
19  close($WHOFH) or die "error al cerrar: $!";
La opción -p de pstree hace que se muestren los PID de los procesos en el árbol. La opción -f de ps indica que queremos un listado completo.

Ejercicio 1.6.2   El programa anterior tiene tres comandos
  1. ssh $host who
  2. ssh $host ps -f
  3. pstree -p
¿En que orden ocurrirán las salidas a STDOUT de este programa? ¿Que procesos son mostrados por ssh $host ps -f? ¿Que procesos son mostrados por pstree -p?

Al ejecutar el programa obtenemos una salida como esta:

lhp@nereida:~/Lperl/src$ rwho.pl orion | cat -n
 1  PID of pipe process: 26100
 2  UID        PID  PPID  C STIME TTY          TIME CMD
 3  casiano  10885 10883  0 Mar02 ?        00:00:00 sshd: casiano@notty
 4  casiano  10886 10885  0 Mar02 ?        00:00:00 perl
 5  casiano  25679 25677  0 12:42 ?        00:00:00 sshd: casiano@pts/3
 6  casiano  26136 26134  0 13:13 ?        00:00:00 sshd: casiano@notty
 7  casiano  26137 26131  0 13:13 ?        00:00:00 sshd: casiano@notty
 8  casiano  26138 26136  0 13:13 ?        00:00:00 ps -f
 9  ---------------------------
10  rwho.pl(26098)-+-sh(26101)---pstree(26103)
11                 `-ssh(26100)
12  ---------------------------
13  cleon    pts/0        2008-02-28 17:05 (miranda.deioc.ull.es)
14  cleon    pts/1        2008-02-28 17:11 (miranda.deioc.ull.es)
15  boriel   pts/2        2008-02-28 18:37 (localhost.localdomain)
16  casiano  pts/3        2008-03-03 12:42 (nereida.deioc.ull.es)
Las líneas 2-8 corresponden a la llamada ssh $host ps -f) en la máquina remota. Las líneas 10-11 a la ejecución en local pstree -p. Por último, las líneas 13-16 se corresponden con la lectura y volcado a través del manejador $WHOFH del proceso remoto ejecutando el comando who.

Usando Pipes para Replicar la Salida de Nuestro Programa

Ejercicio 1.6.3   ¿Cómo se puede modificar la conducta de unas librerías existentes de manera que todas las salidas (mediante print u otras funciones) a STDOUT se replique en un grupo de ficheros?.

Los pipes nos dan una solución sencilla al problema de replicar la salida de nuestro programa a varios ficheros. Basta con hacer un pipe con el programa tee. El programa tee copia la entrada estandar a cada uno de los ficheros que se le pasan como argumentos y también a la salida estándar:

lhp@nereida:/tmp$ uname -a | tee /tmp/one.txt
Linux nereida.deioc.ull.es 2.4.20-perfctr #6 SMP vie abr 2 
lhp@nereida:/tmp$ cat one.txt
Linux nereida.deioc.ull.es 2.4.20-perfctr #6 SMP vie abr 2

En el siguiente código la subrutina tee abre un pipe del $stream especificado contra la aplicación tee:

lhp@nereida:~/Lperl/src/perl_networking/ch2$ cat -n tee.pl
 1  #!/usr/bin/perl -w
 2  use strict;
 3  use IO::File;
 4  use File::Spec;
 5
 6  sub tee {
 7    my ($stream, $echo, @files) = @_;
 8
 9    my $devnull = File::Spec->devnull();
10    my $redirect = $echo?"":"> $devnull";
11    open $stream, "| tee @files $redirect" or die "can't tee. $!\n";
12  }
13
14  #### main
15  die "Provide some file names\n" unless @ARGV;
16
17  my $file = IO::File->new();
18  open $file, ">& STDOUT";
19  tee(\*STDOUT, 1, @ARGV);
20    # Now any print goes to all the @files plus STDOUT
21    print "1) Hola Mundo\n";
22  close(STDOUT);
23
24
25  open STDOUT,">&", $file;
26    # STDOUT only
27    print "2) Hola Mundo\n";
Sigue un ejemplo de ejecución:
lhp@nereida:~/Lperl/src/perl_networking/ch2$ tee.pl one two three
1) Hola Mundo
2) Hola Mundo
lhp@nereida:~/Lperl/src/perl_networking/ch2$ cat one two three
1) Hola Mundo
1) Hola Mundo
1) Hola Mundo

Transparencia en Open

La sentencia open actúa sobre procesos y ficheros de manera trasparente.

Supongamos un programa que espera una secuencia de ficheros en @ARGV como este:

lhp@nereida:~/Lperl/src/perl_networking/ch2$ cat -n toupper
 1  #!/usr/bin/perl -w
 2  while (<>) {
 3    print "\U$_";
 4  }
Este programa usa el operador diamante para leer los ficheros pasados en @ARGV y pasar sus contenidos a mayúsculas.

Entonces podemos pasarle al programa en vez de una lista de ficheros una lista en la que se mezclan pipes y ficheros arbitrariamente:

 1 $ toupper toupper "sort toupper|" - "cat -n system.pl|" 
 2 #!/USR/BIN/PERL -W
 3 WHILE (<>) {
 4   PRINT "\U$_";
 5 }
 6 }
 7   PRINT "\U$_";
 8 #!/USR/BIN/PERL -W
 9 WHILE (<>) {
10 Esta linea la escribí interactivamente
11 ESTA LINEA LA ESCRIBí INTERACTIVAMENTE
12      1  SYSTEM('PS -FU LHP');
13      2  SYSTEM('ECHO -----------------');
14      3  SYSTEM('PS -FU LHP | GREP SYSTEM');

Las líneas 2-5 contienen el resultado de pasar el fichero toupper a mayúsculas. Las líneas 6-9 contienen el resultado de ordenar el fichero alfabéticamente y pasarlo a mayúsculas (sort toupper|"). Después vienen una línea leida interactivamente, correspondiente al "-" en la línea de argumentos y el resultado de filtrar el proceso "cat -n system.pl|".

Pipes versus Backticks

En muchos casos ocurre que lo que puedes hacer con un pipe lo puedes hacer con backticks:

pp2@nereida:~/LGRID_Machine/lib/GRID$ perl -wde 0
main::(-e:1):   0
  DB<1> print grep { /www/ } `netstat -a 2>&1`
tcp        0      0 *:www                   *:*                     LISTEN
tcp        0      0 nereida:56001           mg-in-f83.google.co:www ESTABLISHED
tcp        1      0 nereida:56003           mg-in-f83.google.co:www CLOSE_WAIT

  DB<2> p $?
0

En general la versión pipe consume menos memoria, ya que se procesa una línea de cada vez y nos da la oportunidad, si conviene, de matar el proceso hijo antes de su terminación.

Por otro lado, hay que reconocer la comodidad de usar backticks. Si además se está cronometrando el programa lanzado, la versión pipe da lugar a sincronizaciones que pueden perturbar el proceso de medición de tiempos.

Cierre Prematuro de un Pipe

Si un proceso cierra un pipe de lectura antes de leer el EOF, el programa al otro lado recibirá una señal PIPE en su siguiente intento de escribir en la salida estandard.

En el siguiente ejemplo, tomado de [1] el siguiente proceso intenta escribir 10 líneas al proceso al otro lado del pipe:

lhp@nereida:~/Lperl/src/perl_networking/ch2$ cat -n write_ten.pl
 1  #!/usr/bin/perl -w
 2  use strict;
 3  use IO::Handle;
 4
 5  open (my $PIPE,"| read_three.pl") or die "Can't open pipe: $!";
 6  $PIPE->autoflush(1);
 7
 8  my $count = 0;
 9  for (1..10) {
10    warn "Writing line $_\n";
11    print $PIPE "This is line number $_\n" and $count++;
12    sleep 1;
13  }
14  close $PIPE or die "Can't close pipe: $!";
15
16  print "Wrote $count lines of text\n"
pero el proceso al otro lado sólo lee las tres primeras:
lhp@nereida:~/Lperl/src/perl_networking/ch2$ cat -n read_three.pl
   1  #!/usr/bin/perl -w
   2  use strict;
   3
   4  my $line;
   5  for (1..3) {
   6    last unless defined($line = <>);
   7    warn "Read_three got: $line";
   8  }
Al ejecutar write_ten.pl se produce la muerte prematura del proceso, el cual recibe un señal PIPE :
lhp@nereida:~/Lperl/src/perl_networking/ch2$ write_ten.pl
Writing line 1
Read_three got: This is line number 1
Writing line 2
Read_three got: This is line number 2
Writing line 3
Read_three got: This is line number 3
Writing line 4
lhp@nereida:~/Lperl/src/perl_networking/ch2$
La solución al problema está en instalar un manejador para la señal PIPE. Veremos como hacerlo en la sección 3.4.11.



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