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";
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.
lhp@nereida:~/Lperl/src/perl_networking/ch2$ cat -n pipe2none 1 #!/usr/bin/perl -w 2 use strict; 3 4 open(FH, "|doesnotexist") or die "Can't open pipe: $!\n"; 5 print FH "Hi!\n" or die "Can't write to doesnotexist!: $!\n"; 6 close(FH) or die "Can't close doesnotexist!: $!\n. Status: $?";Al ejecutar este programa tenemos la salida:
lhp@nereida:~/Lperl/src/perl_networking/ch2$ perl -v This is perl, v5.8.7 built for i486-linux-gnu-thread-multi ... lhp@nereida:~/Lperl/src/perl_networking/ch2$ ./pipe2none Can't exec "doesnotexist": No existe el fichero o el directorio at ./pipe2none line 4. Can't open pipe: No existe el fichero o el directorio
lhp@nereida:~/Lperl/src/perl_networking/ch2$ cat -n pipe2none2 1 #!/usr/bin/perl -w 2 use strict; 3 4 my $pid = open(FH, "| doesnotexist > tutu") or die "Can't open pipe: $!\n"; 5 print "PID: $pid\n"; 6 print FH "Hi!\n" or die "Can't write to doesnotexist!: $!\n"; 7 close(FH) or die "Can't close doesnotexist!: $!\n. Status: $?";Al ejecutar produce la salida:
lhp@nereida:~/Lperl/src/perl_networking/ch2$ pipe2none2 PID: 18909 sh: doesnotexist: command not found Can't close doesnotexist!: . Status: 32512 at ./pipe2none2 line 7.
print '*'x1E06
). ¿Cambia la conducta? Si es así, ¿A que cree que puede deberse?
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 $? .
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
$?
( $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.
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
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.
ssh $host who
ssh $host ps -f
pstree -p
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
.
print
u otras funciones)
a STDOUT
se replique en un grupo de ficheros?.
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
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|"
.
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.
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.