Enviando correos con Perl

Regularmente los administradores de sistemas requieren notificar, vía correo electrónico, a sus usuarios de ciertos cambios o nuevos servicios disponibles. La experiencia me ha indicado que el usuario aprecia más un correo personalizado que uno general. Sin embargo, lograr lo primero de manera manual es bastante tedioso e ineficaz. Por lo tanto, es lógico pensar en la posibilidad de automatizar el proceso de envío de correos electrónicos personalizados, en este artículo, explicaré una de las tantas maneras de lograrlo haciendo uso del lenguaje de programación Perl.

En CPAN podrá encontrar muchas alternativas, recuerde el principio TIMTOWTDI. Sin embargo, la opción que más me atrajo fue MIME::Lite:TT, básicamente este módulo en Perl es un wrapper de MIME::Lite que le permite el uso de plantillas, vía Template::Toolkit, para el cuerpo del mensaje del correo electrónico. También puede encontrar MIME::Lite::TT::HTML que le permitirá enviar correos tanto en texto sin formato (MIME::Lite::TT) como en formato HTML. Sin embargo, estoy en contra de enviar correos en formato HTML, lo dejo a su criterio.

Una de las ventajas de utilizar Template::Toolkit para el cuerpo del mensaje es separar en capas nuestra script, si se observa desde una versión muy simplificada del patrón MVC, el control de la lógica de programación reside en el script en Perl, la plantilla basada en Template Toolkit ofrecería la vista de los datos, de modo tal que podríamos garantizar que la presentación está separada de los datos, los cuales pueden encontrarse desde una base de datos o un simple fichero CSV. Otra ventaja evidente es el posible reuso de componentes posteriormente.

Un primer ejemplo del uso de MIME::Lite:TT puede ser el siguiente:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

my %params;
$params{first_name} = "Milton";
$params{last_name}  = "Mazzarri";
$params{username}   = "milmazz";
$params{groups}     = "sysadmin";

my $msg = MIME::Lite::TT->new(
    From        => 'jdoe@example.com',
    To          => 'milmazz@example.com',
    Charset     => 'utf8',
    TimeZone    => 'America/Caracas',
    Subject     => 'Example',
    Template    => 'example.txt.tt',
    TmplOptions => \%options,
    TmplParams  => \%params,
);

$msg->send();

Y el cuerpo del correo electrónico, lo que en realidad es una plantilla basada en Template::Toolkit, vendría definido en el fichero example.txt.tt de la siguiente manera:

Hola [% last_name %], [% first_name %].

Tu nombre de usuario es [% username %].

Un saludo, feliz día.

Su querido BOFH de siempre.

En el script en Perl mostrado previamente podemos percatarnos que los datos del destinario se encuentran inmersos en la lógica. Por lo tanto, el siguiente paso sería desacoplar esta parte de la siguiente manera:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;
use Class::CSV;

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

# Lectura del fichero CSV
my $csv = Class::CSV->parse(
    filename       => 'example.csv',
    fields         => [qw/last_name first_name username email/],
    csv_xs_options => { binary => 1, }
);

foreach my $line ( @{ $csv->lines() } ) {
    my %params;

    $params{first_name} = $line->first_name();
    $params{last_name}  = $line->last_name();
    $params{username}   = $line->username();

    my $msg = MIME::Lite::TT->new(
        From        => 'jdoe@example.com',
        To          => $line->email(),
        Charset     => 'utf8',
        TimeZone    => 'America/Caracas',
        Subject     => 'Example',
        Template    => 'example.txt.tt',
        TmplOptions => \%options,
        TmplParams  => \%params,
    );

    $msg->send();
}

Ahora los datos de los destinarios los extraemos de un fichero en formato CSV, en este ejemplo, el fichero en formato CSV lo hemos denominado example.csv.

Cabe aclarar que $msg->send() realiza el envío por medio de Net::SMTP y podrá usar las opciones que se describen en dicho módulo. Sin embargo, si necesita establecer una conexión SSL con el servidor SMTP es oportuno recurrir a Net::SMTP::SSL:

#!/usr/bin/perl

use strict;
use warnings;
use MIME::Lite::TT;
use Net::SMTP::SSL;
use Class::CSV;

my $from = 'jdoe@example.com';
my $host = 'mail.example.com';
my $user = 'jdoe';
my $pass = 'example';

my %options;
$options{INCLUDE_PATH} = '/home/jdoe/example';

# Lectura del fichero CSV
my $csv = Class::CSV->parse(
    filename       => 'example.csv',
    fields         => [qw/last_name first_name username email/],
    csv_xs_options => { binary => 1, }
);

foreach my $line ( @{ $csv->lines() } ) {
    my %params;

    $params{first_name} = $line->first_name();
    $params{last_name}  = $line->last_name();
    $params{username}   = $line->username();

    my $msg = MIME::Lite::TT->new(
        From        => $from,
        To          => $line->email(),
        Charset     => 'utf8',
        TimeZone    => 'America/Caracas',
        Subject     => 'Example',
        Template    => 'example.txt.tt',
        TmplOptions => \%options,
        TmplParams  => \%params,
    );

    my $smtp = Net::SMTP::SSL->new( $host, Port => 465 )
      or die "No pude conectarme";
    $smtp->auth( $user, $pass )
      or die "No pude autenticarme:" . $smtp->message();
    $smtp->mail($from)                 or die "Error:" . $smtp->message();
    $smtp->to( $line->email() )        or die "Error:" . $smtp->message();
    $smtp->data()                      or die "Error:" . $smtp->message();
    $smtp->datasend( $msg->as_string ) or die "Error:" . $smtp->message();
    $smtp->dataend()                   or die "Error:" . $smtp->message();
    $smtp->quit()                      or die "Error:" . $smtp->message();
}

Note en este último ejemplo que la representación en cadena de caracteres del cuerpo del correo electrónico viene dado por $msg->as_string.

Para finalizar, es importante mencionar que también podrá adjuntar ficheros de cualquier tipo a sus correos electrónicos, solo debe prestar especial atención en el tipo MIME de los ficheros que adjunta, es decir, si enviará un fichero adjunto PDF debe utilizar el tipo application/pdf, si envía una imagen en el formato GIF, debe usar el tipo image/gif. El método a seguir para adjuntar uno o más ficheros lo dejo para su investigación ;)

Generar reporte en formato CSV de tickets en Trac desde Perl

El día de hoy recibí una llamada telefónica de un compañero de labores en donde me solicitaba con cierta preocupación un “pequeño” reporte del estado de un listado de tickets que recién me había enviado vía correo electrónico puesto que no contaba con conexión a la intranet, al analizar un par de tickets me dije que no iba a ser fácil realizar la consulta desde el asistente que brinda el mismo Trac. Así que inmediatamente puse las manos sobre un pequeño script en Perl que hiciera el trabajo sucio por mí.

Es de hacer notar que total de tickets a revisar era el siguiente:

$ wc -l tickets
126 tickets

Tomando en cuenta el resultado previo, era inaceptable hacer dicha labor de manera manual. Por lo tanto, confirmaba que realizar un script era la vía correcta y a la final iba a ser más divertido.

Tomando en cuenta que el formato de entrada era el siguiente:

#3460
#3493

El formato de la salida que esperaba era similar a la siguiente:

3460,"No expira la sesión…",closed,user

Básicamente el formato implica el id, sumario, estado y responsable asociado al ticket.

Net::Trac le ofrece una manera sencilla de interactuar con una instancia remota de Trac, desde el manejo de credenciales, consultas, revisión de tickets, entre otros. A la vez, se hace uso del módulo Class::CSV el cual le ofrece análisis y escritura de documentos en formato CSV.

#!/usr/bin/perl

use warnings;
use strict;

use Net::Trac;
use Class::CSV;

# Estableciendo la conexion a la instancia remota de Trac
my $trac = Net::Trac::Connection->new(
    url => 'http://trac.example.com/project',
    user => 'user',
    password => 'password'
);

# Construccion del objecto CSV y definicion de opciones
my $csv = Class::CSV->new(
    fields => [qw/ticket sumario estado responsable/],
    line_separator => "\r\n",
    csv_xs_options => { binary => 1, } # Manejo de caracteres non-ASCII
);

# Nos aseguramos que el inicio de sesion haya sido exitoso
if ( $trac->ensure_logged_in ) {
    my $ticket = Net::Trac::Ticket->new( connection => $trac );

    # Consultamos cada uno de los tickets indicados en el fichero de entrada
    while ( my $line = <> ) {
        chomp($line);
        if ( $line =~ m/^#\d+$/ ) {
            $line =~ s/^#(\d+)$/$1/;
            $ticket->load($line);

            $csv->add_line(
                {
                    ticket => $ticket->id,
                    sumario => $ticket->summary,
                    estado => $ticket->status,
                    responsable => $ticket->owner
                }
            );
        }
        else {
            print "[INFO] La linea no cumple el formato requerido: $line\n";
        }
    }
    $csv->print();
}
else {
    print "No se pudieron asegurar las credenciales";
}

view raw trac_query.pl This Gist brought to you by GitHub.

La manera de ejecutar el script es la siguiente:

$ perl trac_query.pl tickets

En donde trac_query.pl es el nombre del script y tickets es el fichero de entrada.

Debo aclarar que el script carece de comentarios, mea culpa. Además, el manejo de opciones vía linea de comandos es inexistente, si desea mejorarlo puede hacer uso de Getopt::Long.

Cualquier comentario, sugerencia o corrección es bienvenida.

Subversion: Notificaciones vía correo electrónico

Al darse un proceso de desarrollo colectivo es recomendable mantener una o varias listas de notificación acerca de los cambios hechos (commits) en el repositorio de código fuente. Para este tipo de actividades es muy útil emplear SVN::Notify.

SVN::Notify le ofrece un número considerable de opciones, a continuación resumo algunas de ellas.

  • Obtiene información relevante acerca de los cambios ocurridos en el repositorio Subversion.
  • Realiza análisis sobre la información recolectada y brinda la posibilidad de reconocer distintos formatos vía filtros (Ej. Textile, Markdown, Trac).
  • Puede obtener la salida tanto en texto sin formato como en XHTML.
  • Le brinda la posibilidad de construir correos electrónicos en base a la salida obtenida.
  • Permite el envío de correo, ya sea por el comando sendmail o SMTP.
  • Es posible indicar el método de autenticación ante el servidor SMTP.

Para instalar el SVN::Notify en sistemas Debian o derivados proceda de la siguiente manera:

# aptitude install libsvn-notify-perl

Una vez instalado SVN::Notify, es hora de definir su comportamiento. Aunque es posible hacerlo vía comando svnnotify y empotrarlo en un script escrito en Bash he preferido hacerlo en Perl, es más natural y legible hacerlo de este modo.

#!/usr/bin/perl -w

use strict;
use SVN::Notify;

my $path = $ARGV[0];
my $rev  = $ARGV[1];

my %params = (
    repos_path      =&gt; $path,
    revision        =&gt; $rev,
    handler         =&gt; 'HTML::ColorDiff',
    trac_url        =&gt; 'http://trac.example.com/project',
    filters         =&gt; ['Trac'],
    with_diff       =&gt; 1,
    diff_switches   =&gt; '--no-diff-added --no-diff-deleted',
    subject_cx      =&gt; 1,
    strip_cx_regex  =&gt; [ '^trunk/', '^branches/', '^tags/' ],
    footer          =&gt; 'Administrado por: BOFH ',
    max_sub_length  =&gt; 72,
    max_diff_length =&gt; 1024,
    from            =&gt; 'noreply@example.com',
    subject_prefix  =&gt; '[project]',
    smtp            =&gt; 'smtp.example.com',
    smtp_user       =&gt; 'example',
    smtp_pass       =&gt; 'example',
);

my $developers = 'dev@example.com';
my $admins     = 'admins@example.com';

$params{to_regex_map} = {
    $developers =&gt; '^trunk|^branches',
    $admins     =&gt; '^tags|^trunk'
};

my $notifier = SVN::Notify-&gt;new(%params);
$notifier-&gt;prepare;
$notifier-&gt;execute;

Si seguimos con el ejemplo indicado en el artículo anterior, Instalación básica de Trac y Subversion, este hook lo vamos a colocar en /srv/svn/project/hooks/post-commit, dicho fichero deberá tener permisos de ejecución para el Servidor Web Apache.

Con este sencillo script en Perl se ha logrado lo siguiente:

  • La salida se generará en XHTML.
  • Las diferencias de código serán resaltadas o coloreadas, esto es posible por el handler SVN::Notify::ColorDiff
  • El sistema de notificación está integrado a la sintaxis de enlaces de Trac. Por lo tanto, los commits que posean este tipo de enlaces serán interpretados correctamente. Ej. #123 changeset:234 r234 [/source]

Muestra del coloreado que ofrece SVN::Notify

Aunque el código es sucinto y claro, trataré de resumir cada uno de los parámetros utilizados.

repos_path
Define la ruta al repositorio Subversion, la cual es obtenida a partir del primer argumento que pasa Subversion al ejecutar el hook post-commit.
revision
El número de la revisión del commit actual. El número de la revisión se obtiene a partir del segundo argumento que pasa Subversion al ejecutar el hook post-commit
handler
Especifica una subclase de SVN::Notify que será utilizada para el manejo de las notificaciones. En el ejemplo se hace uso de HTML::ColorDiff, el cual permite colorear o resaltar la sintaxis del comando svnlook diff
trac_url
Este parámetro será usado para generar enlaces al Trac para los números de revisiones y similares en el mensaje de notificación.
filters
Especifica la carga de más módulos que terminan de difinir la salida de la notificación. En el ejemplo, se hace uso del filtro Trac, filtro que convierte el log del commit que cumple con el formato de Trac a HTML.
with_diff
Valor lógico que especifica si será o no incluida la salida del comando svnlook diff en la notificación vía correo electrónico.
diff_switches
Permite el pase de opciones al comando svnlook diff, en particular recomiendo utilizar --no-diff-deleted y --no-diff-added para evitar ver las diferencias para los archivos borrados y añadidos respectivamente.
subject_cx
Valor lógico que indica si incluir o nó el contexto del commit en la línea de asunto del correo electrónico de notificación.
strip_cx_regex
Acá se indican las expresiones regulares que serán utilizadas para eliminar información del contexto de la línea de asunto del correo electrónico de notificación.
footer
Agrega la cadena definida al final del cuerpo del correo electrónico de notificación
max_sub_length
Indica la longitud máxima de la línea de asunto del correo electrónico de notificación.
max_diff_length
Máxima longitud del diff (esté adjunto o en el cuerpo de la notificación).
from
Define la dirección de correo que será usada en la línea From. Si no se especifica será utilizado el nombre de usuario definido en el commit, esta información se obtiene vía el comando svnlook.
subject_prefix
Define una cadena de texto que será el prefijo de la línea correspondiente al asunto del correo electrónico de notificación.
smtp
Indica la dirección para el servidor SMTP que el cual se enviarán las notificaciones de correo electrónico. Si no se utiliza este parámetro, SVN::Notify utilizará el comando sendmail para el envío del mensaje.
smtp_user
El nombre de usuario para la autenticación SMTP.
smtp_pass
Contraseña para la autenticación SMTP
to_regex_map
Este parámetro contiene un hash que mantiene referencias de direcciones de correo electrónico contra expresiones regulares. La idea es enviar las notificaciones si y solo si el nombre de uno o más directorios son afectados por un commit y dicha ruta coincide con las expresiones regulares definidas. Este parámetro resulta muy útil en proyectos de desarrollo de software grandes y donde es posible disponer de varias listas de correo para informar a los desarrolladores interesados en secciones específicas.

Para mayor detalle de las opciones mencionadas previamente vea SVN::Notify, acá también encontrará más opciones de configuración.

Observación

Hasta ahora he encontrado que el coloreado o resaltado de la sintaxis no funciona en algunos sistemas Webmail, como por ejemplo Gmail, SquirrelMail. Sin embargo, en otros sistemas Webmail como RoundCube si funciona. Este comportamiento se presenta porque en sistemas como Gmail las hojas de estilos en cascada (CSS) internas no son aplicadas en la interfaz. Es por ello que en estos casos es necesario recurrir a la definición de estilos en línea.

Generando contraseñas aleatorias con Perl

El día de hoy se manifestó la necesidad de generar una serie de claves aleatorias para un proyecto en el que me he involucrado recientemente, la idea es que la entrada que tenemos corresponde más o menos al siguiente formato:

username_1
username_2
...
username_n

La salida que se desea obtener debe cumplir con el siguiente formato:

username_1 pass_1
username_2 pass_2
username_3 pass_3
...
username_n pass_n

En este caso debía cumplir un requisito fundamental, las contraseñas deben ser suficientemente seguras.

No pensaba en otra cosa que usar el lenguaje de programación Perl para realizar esta tarea, así fue, hice uso del poderío que brinda Perl+CPAN y en menos de 5 minutos ya tenía la solución al problema planteado, el tiempo restante me sirvió para comerme un pedazo de torta que me dió mi hermana, quien estuvo de cumpleaños el día de ayer.

En primer lugar, debemos instalar el módulo String::MkPasswd, el cual nos permitirá generar contraseñas de manera aleatoria. Si usted disfruta de una distribución decente como Debian1 la instalación del módulo es realmente trivial.

# aptitude install libstring-mkpasswd-perl

Además, si usted se detiene unos segundos y lee la documentación del módulo String::MkPasswd2, se dará cuenta que la función mkpasswd() toma un hash de argumentos opcionales. Si no le pasa ningún argumento a esta función estará generando constraseñas aleatorias con las siguientes características:

  • La longitud de la contraseña será de 9.
  • El número mínimo de dígitos será de 2.
  • El número mínimo de caracteres en minúsculas será de 2.
  • El número mínimo de caracteres en mayúsculas será de 2.
  • El número mínimo de caracteres no alfanuméricos será de 1.
  • Los caracteres no serán distribuidos entre los lados izquierdo y derecho del teclado.

Ahora bien, asumiendo que el fichero de entrada es users.data, la tarea con Perl es resumida en una línea de la siguiente manera.

perl -MString::MkPasswd=mkpasswd -nli -e 'print $_, " ", mkpasswd()' users.data

La línea anterior hace uso de la función mkpasswd del módulo String::MkPasswd para generar las contraseñas aleatorias para cada uno de los usuarios que se encuentran en el fichero de entrada users.data, además, la opción -i3 permite editar el fichero de entrada in situ, ahora bien, quizá para algunos paranoicos (me incluyo) no sea suficiente todo esto, así que vamos a generar contraseñas aleatorias aún más complicadas.


#!/usr/bin/perl -li

use strict;
use warnings;
use String::MkPasswd qw(mkpasswd);

while(<>){
        chomp;
        print $_, " ", mkpasswd(
                -length => 16,
                -minnum => 5,
                -minlower => 5,
                -minupper => 3,
                -minspecial => 3,
                -distribute => 1
        );
}

En esta ocasión el la función mkpasswd() generará claves aún más complejas, dichas claves cumplirán con las siguientes condiciones.

-length

La longitud total de la contraseña, 16 en este caso.
-minnum
El número mínimo de digitos. 5 en este caso.
-minlower
El número mínimo de caracteres en minúsculas, en este caso 5.
-minupper
El número mínimo de caracterés en mayúsculas, en este caso 3.
-minspecial
El número mínimo de caracteres no alfanuméricos, en este caso será de 3.
-distribute

Los caracteres de la contraseña serán distribuidos entre el lado izquierdo y derecho del teclado, esto hace más díficil que un fisgón vea la contraseña que uno está escribiendo. El valor predeterminado es falso, en este caso el valor es verdadero.

El script mostrado anteriormente lo podemos reducir a una línea, aunque preferí guardarlo en un fichero al que denomine genpasswd.pl por cuestiones de legibilidad.

  1. Recuerde, Debian es inexorable[regresar]
  2. Este modulo en particular no solo se encuentra perfectamente integrado con nuestra distribución favorita, sino que además sus dependencias están resueltas. Esto es una simple muestra del poderío que ofrece una distribución como Debian.[regresar]
  3. Para mayor información acerca de las distintas opciones usadas se le sugiere referirse a man perlrun[regresar]

El algoritmo de Dijkstra en Perl

Hace ya algunos días nos fué asignado en la cátedra de Redes de Computadoras encontrar el camino más corto entre dos vértices de un grafo dirigido que no tuviese costos negativos en sus arcos, para ello debíamos utilizar el algoritmo de Dijkstra. Además de ello, debía presentarse el grafo y la ruta más corta en una imagen, para visualizarlo de mejor manera.

Cuando un profesor te dice: No se preocupen por la implementación del algoritmo de Dijkstra, utilice la que usted prefiera, pero les agradezco que la analicen. Además, pueden utilizar el lenguaje de programación de su preferencia. Estas palabras te alegran el día, simplemente eres feliz.

Por algunos problemas que tuve con algunos módulos en Python, no lo hice en dicho lenguaje de programación, no vale la pena explicar en detalle los problemas que se me presentaron.

Lo importante de todo esto, es que asumí el reto de realizar la asignación en un lenguaje de programación practicamente nuevo para mí, aunque debo reconocer que la resolución no me causó dolores de cabezas en lo absoluto, a continuación algunos detalles.

En primer lugar, formularse las preguntas claves, ¿existe algún módulo en Perl que implemente el algoritmo de Dijkstra para encontrar el camino más corto?, ¿existe algún módulo en Perl que implemente GraphViz?

En segundo lugar, buscar en el lugar correcto, y cuando hablamos de Perl el sitio ideal para buscar es search.cpan.org, efectivamente, en cuestión de segundos encontre los dos módulos que me iban a facilitar la vida, Bio::Coordinate::Graph, para encontrar la ruta más corta entre dos vértices en un grafo y GraphViz, para pintar el grafo.

En tercer lugar, verificar en tu distro favorita, Debian, si existe un paquete precompilado listo pasa ser usado, para GraphViz existe uno, libgraphviz-perl, así que para instalarlo es tan fácil como:

# aptitude install libgraphviz-perl

Para instalar el módulo Bio::Coordinate::Graph, primero debía debianizarlo, eso es tan sencillo como hacer.

# dh-make-perl --build --cpan Bio::Coordinate::Graph

Luego debe proceder a instalar el fichero .deb que se generó con dh-make-perl, para lograrlo hago uso del comando dpkg, eso es todo. Por cierto, acerca del comando dh-make-perl ya había hablado en la entrada Perl: Primeras experiencias.

Encontrando el camino más corto

Según dice la documentación del módulo Bio::Coordinate::Graph debemos hacer uso de un hash de hashes anónimos para representar la estructura del grafo, en ese momento recordé algunas palabras que José Luis Rey nos comentó en la primera parte del curso de Perl, vale resalta la siguiente: Si usted no utiliza un hash, no lo está haciendo bien.

my $hash = {
		'6' => undef,
		'1' => {
			'2' => 1
			},
		'2' => {
			'6' => 4,
			'4' => 1,
			'3' => 1
			},
		'3' => {
			'6' => 1
			},
		'4' => {
			'5' => 1
			},
		'5' => undef
	};

Algo que es importante saber es que en Perl las estructuras de datos son planas, lo cual es conveniente. Por lo tanto, vamos a tener que utilizar referencias en este caso, pero luego nos preocuparemos por ello. Ahora solo resta decir que, la estructura hecha a través de un hash de hashes es sencilla de interpretar, las llaves o keys del hash principal representan los vértices del grafo, ahora bien, las llaves de los hashes más internos representan los vértices vecinos de cada vértice descrito en el hash principal, el valor de los hashes más internos representa el peso, distancia o costo que existe entre ambos vértices, para aclarar la situación un poco: El vértice 2, tiene como vecinos a los vértices 3, 4 y 6, con costos de 1, 1 y 4 respectivamente. Puede ver una muestra del grafo resultante.

De manera alternativa puede utilizar un hash de arrays para representar la estructura del grafo, siempre y cuando todos los costos entre los vértices sean igual a 1, este método es menos general que el anterior y además, este tipo de estructura es convertida a un hash de hashes, así que a la final resulta ineficiente.

Una vez definida la estructura del grafo corresponde crear el objeto, esto es realmente sencillo.

my $graph = Bio::Coordinate::Graph->new(-graph => $hash);

Lo que resta es definir el vértice de inicio y el vértice final, yo los he definido en las variables $start y $end, luego de ello, debemos invocar al método shortest_path, de la siguiente manera:

my @path = $graph->shortest_path($start, $end);

En el array @path encontraremos los vértices involucrados en el camino más corto.

Pintando el grafo

Una vez hallado el camino más corto entre dos vértices dados en un grafo dirigido con un costo en los arcos siempre positivos, lo que resta es hacer uso del módulo GraphViz, hacemos uso del constructor del objeto de la siguiente manera:

my $g = GraphViz->new();

Usted puede invocar al constructor con distintos atributos, para saber cuales usar le recomiendo leer la documentación del módulo.

Ahora bien, yo quería distinguir aquellos vértices involucrados en el camino más corto de aquellos que no pertenecían, así que lo más sencillo es generar una lista de atributos que será usada posteriormente. Por ejemplo:

my @shortest_path_attrs = (
				color => 'red',
			);

Una vez definidos los atributos, debemos generar cada uno de sus vértices y además, establecer los arcos entre cada uno de ellos. Para ello haremos uso de los métodos add_node, add_edge.

foreach my $key (keys %$hash){
	$g->add_node($key);
	foreach my $neighbor (keys %{$hash->{$key}}){
		$g->add_edge($key => $neighbor, label => $hash->{$key}->{$neighbor});
	}
}

El bloque de código anterior lo que hace es agregar cada uno de los vértices que se encuentran en el hash que representa la estructura del grafo, para cada uno de estos vértices, se añade cada uno de los arcos que lo conectan con sus vecinos, nótese el manejo del operador flecha (->) para desreferenciar las referencias al hash de hashes anónimos.

Una vez construidos los nodos y sus arcos, ¿cómo reconocer aquellos que pertenecen al camino más corto?, bueno, toda esa información la podemos extraer del array que hemos denominado @path.

for (my $count=0; $count < = $#path; $count++){
	$g->add_node($path[$count], @shortest_path_attrs);
	$g->add_edge($path[$count] => $path[$count+1], @shortest_path_attrs) unless ($count+1 > $#path);
}

Según dice la documentación del módulo GraphViz todos los atributos de un vértice del grafo no tienen que definirse de una sola vez, puede hacerse después, ya que las declaraciones sucesivas tienen efecto acumulativo, eso quiere decir lo siguiente:

$g->add_node('1', color => 'red');

Es equivalente a hacer las siguientes declaraciones sucesivas.

$g->add_node('1');
$g->add_node('1', color => 'red');

Lo que resta por hacer en este instante es exportar el objeto creado, puede crear por ejemplo un PNG, texto sin formato, PostScript, entre otros.

Yo decidí generar un fichero PostScript:

$g->as_ps("dijkstra.ps");

Por supuesto, les dejo una muestra de los resultados. Los vértices cuyo borde es rojo, son aquellos involucrados en el camino más corto desde el vértice inicio al vértice final, los arcos que marcan la ruta más corta también han sido coloreados.

Como siempre, todas las recomendaciones, comentarios, sugerencias son bienvenidas.

Perl y su poderío

Al igual que José, considero que el hilo de discusión Borrar línea X de un archivo es bastante interesante, este hilo fué discutido en la lista de correos técnica 1 del Grupo de Usuarios de Linux de Venezuela (VELUG), el problema planteado 2 consistía en eliminar un registro completo, en donde se pasara como argumento el primer campo (tal vez el identificador) de dicho registro.

Suponga que el fichero tiene la siguiente estructura:

 :( 123
	... # otros campos
)

 :( 234
	... # otros campos
)

 :( 456
	... # otros campos
)

Se dieron soluciones en lenguajes como Bash y C 3 y ciertas en Perl, éstas últimas son las que llaman mi atención, vamos por partes 4.

José propuso lo siguiente:

#!/usr/bin/perl -n
$deadCount = 7 if ($_ =~ /${ARGV[0]}/);
--$deadCount if ($deadCount);
print unless ($deadCount);

El programa debe ejecutarse así:

$ perl script.pl archivo 123

Este programa hace el trabajo, pero al final emitirá un error porque cree que el argumento 123 es otro fichero, y por supuesto, no lo encuenta.

Mi solución fue la siguiente:

perl -ne 'print unless /:\\(123/../\\)/' input.data > output.data

Por supuesto, en este caso solamente estaría eliminando el registro cuyo primer elemento es 123, funciona, pero genera un problema al igual que el hecho por José, se deja una línea de espacio vacía adicional en los registros, cuando el separador de los grupos de datos debe ser una 5 línea en blanco, tal cual como apunto Ernesto Hernández en una de sus respuestas.

Otro apunte realizado por el profesor Ernesto, fué que las soluciones presentadas hasta el momento de su intervención fué el análisis de fondo que estabamos haciendo, el problema no consistía en procesar cada una de las líneas, el trabajo en realidad consistía en analizar un registro multilínea (o párrafo), en términos más sencillos, cada grupo de datos (registros) está separado por una línea en blanco.

El profesor continuaba su excelente explicación diciendo que el problema se reduce al analizarlo de esta manera en lo siguiente:

…si se cuenta con un lenguaje de programación que está preparado para manejar el concepto de “registro” y puede definir el separador de registro como una línea en blanco, simplemente se trata de ignorar aquellos registros que tengan la expresión regular (X, donde X es la secuencia de dígitos que no nos interesa preservar.

La solución presentada por el profesor Ernesto fue:

perl -000 -i -ne 'print unless /\\(XXX/' archivo

En donde se debe sustituir las XXX por los dígitos cuyo bloque no nos interesa conservar.

De este hilo aprendí cosas nuevas de Perl, en realidad estoy comenzando, muchos pensarán que este código es críptico, por ello considero conveniente aclarar algunas cosas.

Lo críptico de un código no es inherente a un lenguaje particular, eso depende más del cómo se programe.

En este caso particular una sola línea de código nos proporciona mucha información, evidentemente para comprender dicho contenido es necesario leer previamente cierta documentación del lenguaje, pero ¿quien comienza a programar en un lenguaje en particular sin haber leído primero la documentación?, la respuesta parece lógica, ¿cierto?.

La existencia de opciones predefinidas y maneras de ejecutar el interprete de Perl permiten enfocarse únicamente en la resolucion de tareas, cero burocracia.

Un ejemplo de lo mencionado en el párrafo anterior es el siguiente, un bucle lo puedo reducir con la opción de ejecución -n del interprete Perl, simplemente leyendo un poco perlrun6 nos enteramos del asunto, eso quiere decir que podemos reducir a una simple opción de ejecución del interprete de Perl todo esto:

#!/usr/bin/perl
while(<>){
#...
}

¿Qué es ese operador que parece un “platillo volador” 7 (null filehandle)?, bueno, lea perlop, en especial la sección I/O Operators.

La opción -i me permite editar (reescribir) in situ el fichero que vamos a procesar, en el caso de no añadir una extensión no se realizara un respaldo del fichero, el fichero original se sobreescribe. Mayor detalle en perlrun.

Lo que si no sabía hasta ahora, es lo explicado por el profesor acerca de los párrafos (registros multilínea) en Unix, la opción -0 tiene un valor especial, el cual es 00, si este valor es suministrado hace que Perl entre en “modo párrafo”, en pocas palabras, se reconoce una línea en blanco como el separador de los registros.

El resto del código es solo manejo de una sencilla expresión regular, se asume que el lector conoce algo del tema, solo indica el registro que queremos ignorar.

Así que podemos concluir lo siguiente, Perl no es críptico, asumiendo que el programador ha leido suficiente documentación acerca del lenguaje en cuestión, evitamos la burocracia y atendemos el problema de raíz en el menor tiempo posible.

La solución que propone Perl con el lema Hay más de una manera de hacerlo, es ofrecerle al programador libertad en su forma de expresarse, ¿acaso todos hablamos el mismo idioma?, ¿acaso debemos seguir las malas prácticas que intenta difundir el maligno Java?, coartar el pensar del programador y obligarlo a hacer las cosas al estilo Java, ¿dónde queda la imaginación? 8, ¿paso a ser un lujo?.

A todos los que lo deseen, les invito a participar en la lista de correos técnica (l-linux) del Grupo de Usuarios de Linux de Venezuela (VELUG), les recomiendo leer detenidamente las normas de uso antes de inscribirse en la lista.

  1. l-linux es la la lista de correos para Consultas Técnicas sobre Linux y Software Libre en VELUG[regresar]
  2. Quien inicio el hilo de discusión fue José Luis Bazo Villasante[regresar]
  3. ¿Con intención de autoflagelación?[regresar]
  4. Diría Jack El Destripador[regresar]
  5. No dos, ni tres, …[regresar]
  6. man perlrun, perlrun se incluye en la documentación de Perl, en sistemas Debian lo encontramos en el paquete perl-doc, para instalar simplemente hacer ejecutar el comando aptitude install perl-doc como superusuario[regresar]
  7. Según nos conto José Luis Rey, el profesor Ernesto Hernández le llama así de manera informal[regresar]
  8. De hecho, se dice que, un programador experto en Java está muy cerca de convertirse en un autómata[regresar]

Perl: Primeras experiencias

La noche del sábado pasado, después de terminar de estudiar con Ana la cátedra Programación Paralela y Distribuida, me dispuse a revisar las distintas instancias de Planeta Linux, como normalmente hago, pero eso no fué suficiente, me puse a validar el feed que se estaba generando en ese momento (en particular, el feed de la instancia venezolana). Me percate de varios errores y advertencias, entre ellos me llamo la atención:

This feed does not validate.

In addition, this feed has an issue that may cause problems for some users. We recommend fixing this issue.

line 11, column 71: title should not contain HTML (20 occurrences) [help]

Luego de leer la ayuda noto que es recomendable cambiar todos los nombres de las entidades html a su equivalente decimal, es decir, si tenemos por ejemplo: &copy; debe modificarse &#169;, de igual manera con el resto.

Tenía varias opciones, una de ellas era realizar el reemplazo masivo desde vim, pero esta tarea es realmente ineficiente por el hecho de tener que reemplazar todos los nombres de las entidades html a su equivalente decimal uno por uno, además de eso, debía hacerlo para las tres instancias presentes en Planeta Linux, primera opción descartada de entrada.

Aprovechando que la semana pasada, al igual que el profesor Francisco Palm, estuve presente en un curso sobre el lenguaje de programación Perl, el cual fué dictado por José Luis Rey con la ayuda de Daniel Rodríguez en las instalaciones de Fundacite Mérida, quería poner en práctica algunas de las cosas que aprendí en dicho curso.

Antes de continuar debo agradecer al profesor José Aguilar, a la ingeniera Blanca Abraham, y a la Sra. Tauka Shults por la oportunidad que me brindaron.

Una de las cosas que nos recalcó José Luis fué acerca de las virtudes que debía tener un programador, una de ellas debe ser la flojera, es decir, comenzar a escribir código realmente útil de inmediato, sin ningún requerimiento adicional como ocurre en lenguajes de programación como el C/C++, en donde es necesario realizar una serie de procedimientos antes de comenzar a escribir código útil.

Como mencione en el párrafo anterior, la idea es llegar a ser lo más productivo en el menor tiempo posible. Generar un hash de entidades de nombres html no me parecía el camino idóneo, así que recorde el tema de la flojera, sin pensarlo dos veces comence a buscar en search.cpan.org un módulo que me permitiera convertir los nombres de las entidades html a su equivalente decimal, como primer resultado obtuve lo que buscaba, el módulo HTML::Entities::Numbered, había encontrado mi salvación, leo un poco acerca de su uso y es más sencillo de lo que pensaba, siguiente paso, proceder a instalarlo.

Para debianizar un módulo en Perl es muy sencillo, en primer lugar debemos recurrir al comando dh-make-perl, si no lo tenemos instalado ya, debemos proceder como sigue:

# aptitude install dh-make-perl

Ahora ya podemos debianizar el módulo que requerimos, para ello tuve que realizar lo siguiente:

# dh-make-perl --build --cpan HTML::Entities::Numbered

Como lo puede apreciar, su uso es realmente sencillo, para una mejor explicación acerca de este último comando le recomiendo leer la entrada Instalando módulos de Perl en Debian escrita por Christian Sánchez, como era la primera vez que hacia uso del comando cpan tuve que configurarlo, esto no tomo mucho tiempo, el asistente ofrece explicaciones bastante detalladas.

Una vez realizado el proceso más complicado de toda la operación, el resto era escribir el código fuente que me permitiese convertir los nombres de las entidades html a su equivalente decimal, he aquí el resultado.

#!/usr/bin/perl -l

use strict;
use warnings;
use HTML::Entities::Numbered;

unless(open(INPUT, $ARGV[0])) { die "ERROR: No se especifico archivo para abrir. $!"; }
open(OUTPUT, ">$ARGV[0].bak");
while(<INPUT>){ print OUTPUT name2decimal($_) if chomp; }

¡Listo!, en tan pocas líneas de código he logrado resolver el problema, por supuesto, todo se redujo a buscar el módulo apropiado, una vez hecho los cambios a los ficheros de configuración de Planeta Linux procedí a actualizar la última versión en subversion.

Puede apreciar el antes y después de los cambios realizados.

Vulnerabilidad en Perl

Las siguientes versiones se encuentran afectadas ante este fallo de seguridad:

  • Ubuntu 4.10 (Warty Warthog)
  • Ubuntu 5.04 (Hoary Hedgehog)
  • Ubuntu 5.10 (Breezy Badger)

En particular, los siguientes paquetes se encuentran afectados:

  • libperl5.8
  • perl-base

El problema puede ser corregido actualizando los paquetes a sus últimas versiones en las respectivas versiones de Ubuntu. En general, el modo estándar de actualizar la distribución será mas que suficiente.

$ sudo aptitude dist-upgrade

La actualización pretende solucionar una vulnerabilidad del interprete Perl, el cual no era capaz de manejar todos los posibles casos de una entrada malformada que podría permitir la ejecución de código arbitrario, así que es recomendable actualizar su sistema de inmediato.

Sin embargo, es importante hacer notar que esta vulnerabilidad puede ser aprovechada en aquellos programas inseguros escritos en Perl que utilizan variables con valores definidos por el usuario en cadenas de caracteres y en donde no se realiza una verificación de dichos valores.

Si desea mayor detalle, le recomiendo leer el anuncio hecho por Martin Pitt en [USN-222-1] Perl vulnerability.