Variables de entorno

El hash %ENV contiene las variables de entorno. Su modificación implica la modificación del entorno del programa.
$ENV{PATH} = $ENV{PATH}:"/home/casiano/bin";
delete $ENV{DISPLAY};
system 'myls';
En este ejemplo el program myls es ejecutado en un entorno en el cual el PATH incluye a /home/casiano/bin y la variable DISPLAY ha sido suprimida.

Ejercicio 1.3.1   En Perlmonks apareció la siguiente pregunta (puede encontrarla buscando en http://www.perlmonks.org/ por 'Environmental Settings'.
I require some assistance. I am writing a CGI script in Perl on Solaris
that needs to run external commands using the 'system' operator. These
commands are part of a larger package that needs the system initialized
(via a command) properly before execution, or else each individual
command cannot interact with the other, like the following.

1: system "/PATH/initialize_system"; # init the environment
2: system "/PATH/skew.k ARG_FOR_FRAME_NUMBER"; # create image
3: system "/PATH/savegif.k FRAME_NUMBER"; # save image off
4: system "exit"; # exit the environment created with initialize_system

When running a command using 'system', the environment appears to only
lasts until the next line of Perl code, so line 2 can't talk to line
3 because the environment required for them to communicate isn't there
which is initialized by line 1. The above code snippet would the way it
is run from a command line. 

Any help would be appreciated. 
Thanks.

¿Cuál es su respuesta?

Ejercicio 1.3.2   Ejecute los comandos:
$ set | grep "^[A-Z]" |wc
$ perl -e 'print "$_ = $ENV{$_}\n" for sort keys %ENV;' | wc
¿A que se debe la diferencia?. Consulte el módulo Env::Bash . Discuta su funcionalidad.

Casiano Rodríguez León
2010-03-22
lamada 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
2010-04-20