viernes, 18 de noviembre de 2011

Pedido de articulos

Hace un tiempo que estamos poniendo algún que otro artículo en este blog. Y la verdad es que (en lo personal), las ideas de escribir cosas se me agotó. Con lo que, si alguien tiene dudas sobre algo en particular de Perl, o tiene una pregunta (como resolver el cifrar un socket en perl, como mandar mensajes XML a un webservice, etc, etc), postee un comentario a este post y veremos de investigar y hacer un artículo.

convirtiendo AM,PM a formato 24 horas (con Perl)

Aprovecho este post para mostrar también como se podría ejecutar un código dentro de una expresion regular.

#!/usr/bin/perl

sub nonAMPM {
    my $hour = $2;
    if ($4 eq "PM") {
        $hour = $hour+12;
    }
    return "$1 $hour:$3 $4";
}

$a= "11/18/2011 7:29:14 PM";
print "$a, ";
$a =~ s/(.*) (.*):(.*:.*) (..)/&nonAMPM/e;
print "$a\n";


Saludos

jueves, 3 de noviembre de 2011

Alarmas y subprocesos en PERL

Poniendo el despertador...

En determinadas ocasiones se da la situación de necesitar ejecutar un comando de sistema desde PERL, bien para capturar su salida y usarla para algo o bien descartarla. Si no tenemos cuidado y controlamos lo que estamos ejecutando podemos caer en el error de disparar procesos que queden colgados e interrumpir el flujo principal de nuestro programa, o peor aún podemos no advertir que el comando que pedimos ejecutar falló por algun error y el programa principal no recibió alerta alguna continuando como si todo fuese normal. Para salvar estos inconvenientes vamos a usar la instrucción eval y la señal ALRM.

Veamos ejemplo.pl.
 
#! /usr/bin/perl

use strict;

my $log; 
my $status;
eval { 
        local $SIG{ALRM} = sub {die "timeout\n"};
        alarm 15;
        $log        = `micomando.sh` || die;
        $status     = $?>>8;
        alarm 0;
};
if ( $@ ) 
{
    if ( /timeout/ )
    {
        print "Salida por timeout\n";
    }
      else
    {
        print   "Comando fallido\n".
                "Valor en '\$\!': $!\n".
                "Valor en '\$\@': $@\n";
    }
}
  else
{
    print "Ningun error\nLog: $log\n";
}

Comandos a utilizar.

eval:
Ejecutara el bloque de codigo contenido entre las llaves y si se produce un error podremos realizar la evaluacion necesaria usando la variable especial $@, esto se conoce como "Excepciones". Cuando se produce la "excepción" tendremos la posibilidad de ejecutar algun codigo destinado a brindar mas informacion al usuario para indicarle que fue lo que salio mal.

alarm:
El comando alarm toma como argumento el nro de segundos en el que se producira la llamada. Es decir en este caso 15 segundos. Pasado ese tiempo se dispara la llama a la señal ALRM y por consiguiente se ejecuta el codigo indicado entre llaves die "timeout\n" .

Variables especiales:

%SIG
Hash interno de PERL que contiene "signal handlers". Es decir podemos asignar una funcion o bloque de codigo a una señal en particular. Que es exactamente lo que se hace en esta linea local $SIG{ALRM} = sub {die "timeout\n"}; concretamente esta linea indica que cuando se produzca la señal ALRM se ejecutara el codigo que esta entre llaves. Al ejecutarla con local nos aseguramos de que la modificación no se propague por el resto del programa y permanezca dentro del bloque eval.

$@
Otra variable interna de PERL que captura el mensaje de error del ultimo bloque eval, aqui se almacenara el mensaje de error que arroje el bloque eval en el caso de producirse una falla.

$!
Variable interna de PERL, esta variable toma un valor cuando una llamada al sistema falla. El valor solo es valido inmediatamente despues de la llamada que falló.Esta variable tiene una forma particular de comportarse, si se la invoca como un string, devolvera el mensaje en formato "human readable" pero si se la invoca en forma numerica devolvera el valor de la constante definida en errno.h. Un experimento interesante para realizar es printf("%d: %s\n", $!, $! ); y podremos ver ambos valores.

$?
Variable inerna de PERL, esta variable es bastante especial en el sentido de que en realidad es una palabra de 16 bits (o 2 bytes) que almacena diferente información dependiendo del caso.
La información que devolverá es la siguiente:

  1. El valor de salida del subproceso.
  2. La señal, si existiese una, que recibio el subproceso.
  3. Si se generó core dump o no (boolean) .
Hay que tener en cuenta que los valores que contiene esta variable son bits, por lo tanto deberemos utilizar los operadores de bit sobre la misma.En la man page se explica con detalles.

Ejecutando comandos externos.

Cuando comienza a correr el script ejecuta el codigo que se encuentra en el bloque eval, carga la funcion anonima en el handler ALRM y setea la alarma para dentro de 5 segundos. Luego procede a lanzar el programa "micomando.sh" seguido del operador "or", tambien indicado como ||, y la instrucción die. Seguidamente se captura la salida del comando en la variable $log y el status de la ejecucion en $status y se procede a poner la alarma nuevamente en 0.

En el caso de que el programa "micomando.sh" quede en espera por mas de 5 segundos, el programa principal toma el control y ejecuta el comando die con el mensaje "timeout". Una aclaración aqui, el comando lanzado de fondo sigue corriendo, el die no lo matará sino que simplemente devolverá el control al flujo principal con la excepción. Una buena modificación seria obtener el PID del proceso lanzado y enviarle una señal SIGKILL para matarlo si hemos detectado que no responde.

En el caso de que la ejecución falle, por ejemplo por falta de permisos o porque el comando no existe, se ejecutará die y saldrá tambien con una excepción pero el error ya no será "timeout", si examinamos la variable $@ veremos que tiene el mensaje "Died at ./child_die.pl line 11". Continuando con la ejecución se puede ver que se declara la variable $status pero no se usa en ninguna parte del script. En realidad en la segunda entrega voy a agregar mas codigo para hacer un poco mas inteligente el script y que pueda tomar alguna decisión con respecto al status que devolvio el comando ejecutado. Por ahora solo quedara definida la variable y almacenará el status de la ejecución del ultimo comando si este fue exitoso ( que será siempre 0 ); caso contrario nunca se llenará ya que por ahora sale en la linea anterior con el die.

Esto es solamente una introducción al tema, PERL tiene documentación muy interesante y muy variada al respecto y hay capítulos enteros dedicados a esto, pero para implementar algo rapido y sencillo sirve.

Mas info en paginas del manual de PERL a traves de perldoc:
  • perlipc
  • perlvar
  • perlfunc
Libros de OREILLY:
  • Mastering PERL
  • PERL CookBook

miércoles, 4 de mayo de 2011

pensando expresioens regulares para que no exageren en lo que abarcan

Muchas veces pasa que necesitamos obtener un texto que está entre medio de dos cosas que entendemos bien, pero que lamentablemente, alguna parte se repite considerablemente. Por ejemplo:

$a="hola mundo, esto es un planeta, chau mundo";

Este string es bastante simple, y ahora el siguiente regex:

/hol
    (.*)
        mundo
/x

Esto es lo que resultaría (dentro de $1):


"a mundo, esto es un planeta, chau "

(sin las comillas, lo puse para que se vean los espacios)

Por defecto, el * es glotón, esto quiere decir, que va a intentar agarrar lo más posible, o sea, para nuestro caso, el último mundo.

Para evitar esto, y que tengamos algo más simple:

/
     hol(.*?)
              mundo.*
/x

El resultado de esto:

"a "

Que, en la mayoría de los casos, es lo que se intentaba obtener.

miércoles, 27 de abril de 2011

escribiendo regex más claras

Como el ejemplo anterior (substitución con ejecución con lo obtenido), hay veces que las epresiones regulares que escribimos, sobre todo en un programa largo, terminan siendo algo engorroso. Para lo cual, Perl nos deja escribir las regex en múltiples líneas.

El anterior POL, podría ser reescrito como un archivo en disco:

#!/usr/bin/perl -pw

s@
(
  \d+
  (?:
    \.\d{3}
  )
)
@
"Fecha: " . scalar(localtime($1))
@ex

Esto básicamente hace los mismo, nada más que agrega "Fecha: " por delante de la hora que noosotros estamos parseando.

Notese que lla regex, teniendo mucho espacio, no le da importancia. Esto es lo que hace (básicamente) al usar el /x.

Básicamente, es una forma que voy a utilizar para poner algunas regex más complejas, e ir comentandola para que pueda ser leida más fácilmente por todos.

substitución con ejecución con lo obtenido

Hay varias veces, sobre todo procesando logs, que necesitamos verlo de mejor manera (por ejemplo, los logs del squid, que ponen la hora en epoch).

Ejemplo:
1303873213.829     21 127.0.0.1 TCP_MISS/200 2993 GET cache_object://localhost/info - NONE/- text/plain
1303873217.363     21 127.0.0.1 TCP_MISS/200 2993 GET cache_object://localhost/info - NONE/- text/plain

Para poder ver mejor los logs, simplemente con POL (Perl One Liner), es simple:

$ perl -pe 's/^(\d+(?:\.\d{3}))\s/scalar(localtime($1)) /e' access.log

La salida va a pasar a ser:

Wed Apr 27 00:00:13 2011    21 127.0.0.1 TCP_MISS/200 2993 GET cache_object://localhost/info - NONE/- text/plain
Wed Apr 27 00:00:17 2011    21 127.0.0.1 TCP_MISS/200 2993 GET cache_object://localhost/info - NONE/- text/plain

miércoles, 20 de abril de 2011

referencias en Perl

Muchas veces es necesario usar una función, y que modifique la variable que se le pasa, como lo hace el chomp. Eso se hace usando referencias en Perl.

El código de ejemplo es:

#!/usr/bin/perl -w


my $a = "hola";

sub NoRef{
  my $tmp = $_[0];

  $tmp =~ s/hola/chau/g;

  print "dentro: '" . $tmp . "'\n";

  return 0;
}

sub WithRef{
  my $tmp = \$_[0];

  $$tmp =~ s/hola/chau/g;

  print "dentro: '" . $$tmp . "'\n";

  return 0;
}

print "primero a es $a\n";

NoRef($a);

print "ahora es a es $a\n";

WithRef($a);

print "y por ultimo a es $a\n";
 

La explicación (gíbara) es, al usar referencias, la variable pasa a ser un puntero, con lo que la salida es:

primero a es hola
dentro: 'chau'
ahora es a es hola
dentro: 'chau'
y por ultimo a es chau

Esto es de utilidad, para entender el funcionamiento de funciones como uc o push. La primera no hace nada con la variable, regresa el resultado, pero la variable queda intacta. Mientras que la segunda, regresa el número de elementos del array, pero modifica el array que se le pasó agregando el listado de objetos pasados.

subrutina en una variable

Todavía no se bien que utilidad tiene, pero estaba jugando con variables, y me encontré esto

$a = sub { return $_[0] . " mundo"; }
print &$a("Mundo");

Lo que imprime
Hola Mundo

Sin el &, básicamente lo que imprime es:
CODE(0x6188e8)

domingo, 17 de abril de 2011

buscar algo en un código (expresiones regulares básicas) Parte #2

Continuando con el subject anterior, esta me pareció una solución elegante para buscar en un array algun item único:


if ( grep { $_ eq "$tmpuserid" } @PredictedProductBuyers) {
   ## Aca, hacer algo si el item fue encontrado,
   ## por ejemplo, contabilizar el producto.
   cuenta++;
}

buscar algo en un código (expresiones regulares básicas)

Algo básico para todo sysadmin es buscar en los logs por errores o avisos, y luego disparar alguna acción en base a eso.

Este ejemplo lo que hace es:
  • Obttener un bloque de texto (512 bytes, es una lectura que puede tener muchas líneass o no)
  • Buscar al principio de una línea, un string "HTTP..." (esto es una respuesta de HTTP básica)
  • si lo encuentra, disparar otra funcion

#!/usr/bin/perl -w

$text = "HTTP/1.0 302 Found
Date: Sun, 17 Apr 2011 04:29:37 GMT
Content-Length: 222
Content-Type: text/html; charset=UTF-8
Cache-Control: private
Set-Cookie: PREF=ID=1847c960b2585ff2:FF=0:TM=1303014577:LM=1303014577:S=nIWoylYn45zN4QZi; expires=Tue, 16-Apr-2013 04:29:37 GMT; path=/; domain=.google.com
GMT; path=/; domain=.google.com; HttpOnly
Server: gws
X-XSS-Protection: 1; mode=block
Location: http://www.google.com.ar/
";

if($text =~ /^HTTP/m){
  print "correcto\n";
}

En caso de que quiera evaluar que la primer línea tenga un HTTP al principio, simplemente hay que reemplazar la expresión regular con:

if($text =~ /^HTTP/){

Esto hace que se evalue solamente la primer línea solamente.