Introducción
Así como cantidad de personas existen en el mundo, cantidad de problemas que necesitan de una solución. Y esto fue el caso en mi trabajo.
Tenía un problema que necesitaba de una solución, un cliente mío recibe mensajes de correo por cada suscripción que hay en su sitio, en este mensaje viene información completa acerca de la nueva suscripción, nombre, email, fecha de suscripción, etc.
Lleva cerca de 2 años con este sistema, y recibe todos sus mensajes con el programa de mensajería Eudora.
La cuestión es que el cliente llega conmigo y me dice que quisiera de alguna manera ordenar esa información para poder hacer un pequeño sistema de estadísticas.
Perl al ataque
Eudora usa archivos planos (puro texto) que comunmente se llaman archivos tipos mailbox (comunes en sistemas UNIX), como son archivos planos básicamente los puedes abrir con un bloc de notas y ver su contenido.
Como es costumbre decidí entrar a CPAN y ver si alguien ya había creado un módulo para éste problema específico, y como siempre así fue.
Tie::Eudora un módulo que te permite leer y separar tus correos a partir de los archivos .mbx de Eudora.
Por ejemplo, si abrimos un archivo .mbx de Eudora podríamos ver algo similar a lo siguiente:
Es un .mbx con tres mensajes de correo, vamos a trabajar con este archivo de ejemplo en el resto del tutorial.
Poniendo a trabajar el módulo
Si estudiamos bien el archivo que crea Eudora vemos que tiene un patrón, y eso es que al inicio de cada mensajes podemos encontrar un:
From ???@??? <FECHA>
Entonces podríamos crear una expresión regular para que nos divida los mensajes, pero en vez de ello vamos a usar ese útil módulo que encontramos en CPAN que se llama Tie::Eudora.
Lo que hace el módulo es darnos la capacidad de hacer un tie entre el archivo .mbx y el módulo para hacer la lectura del archivo, por ello lo primero que hacemos es:
tie *MAILBOX, 'Tie::Eudora';
Aquí lo que estamos haciendo es un tie entre el módulo Tie::Eudora y el typeglob *MAILBOX.
Un typeglob es una variable con acceso dinámico a la tabla de símbolos en tiempo de ejecución, *MAILBOX contiene $MAILBOX, @MAILBOX, %MAILBOX, &MAILBOX y *MAILBOX{IO}. Esto es, contiene todas las entradas de símbolos posibles. Pero como en este caso específico se usa como FH, entonces lo que usamos es *MAILBOX{IO} al usarlo en open().
open MAILBOX,'<Inbox.mbx' or die('Unable to read file: $!');
my @mailbox = <MAILBOX>;
close MAILBOX;
Suponiendo el archivo .mbx se encuentre en el mismo directorio que el script que estamos ejecutando, se va a abrir el archivo y sus datos van a ser guardados en el arreglo @mailbox.
Pero ¿para qué necesitamos el módulo? Lo que sucede es que la magia de todo pasa en el primer tie que hicimos, pues ahora cada línea del archivo es procesada a través del módulo Tie::Eudora.
Después de leer el archivo @mailbox en realidad tendremos un arreglo multi-dimensional, pues cada elemento del arreglo a su vez es un arreglo, el cual contiene los elementos separados en nombre-de-campo y valor-de-campo.
Para comprender un poco más esto, usaremos el módulo Data::Dumper para ver como es la estructura de nuestro arreglo @mailbox:
use strict;
use Tie::Eudora;
use Data::Dumper;
tie *MAILBOX, 'Tie::Eudora';
open MAILBOX,'<Inbox.mbx';
my @mailbox = <MAILBOX>;
close MAILBOX;
print Dumper(\@mailbox);
Después de ejecutar el código veremos el siguiente resultado al hacer el Dump del arreglo:
$VAR1 = [
[
'X-Pickup-Date',
'Thu Sep 16 12:02:49 2004',
'X-Persona',
'<Perl en Español>',
'Return-path',
'<[email protected]>',
'Envelope-to',
'[email protected]',
'Delivery-date',
'Wed, 15 Sep 2004 18:00:44 -0500',
'To',
'[email protected]',
'From',
'[email protected]',
'Subject',
'SUBJECT #1',
'Message-Id',
'<[email protected]>',
'Date',
'Wed, 15 Sep 2004 18:00:44 -0500',
'X-Body',
'
EL BODY DE NUESTRO MENSAJE #1
Y OTRAS LÍNEAS
'
],
[
'X-Pickup-Date',
'Thu Sep 16 15:02:49 2004',
'X-Persona',
'<Perl en Español>',
'Return-path',
'<[email protected]>',
'Envelope-to',
'[email protected]',
'Delivery-date',
'Wed, 15 Sep 2004 18:00:44 -0500',
'To',
'[email protected]',
'From',
'[email protected]',
'Subject',
'SUBJECT #2',
'Message-Id',
'<[email protected]>',
'Date',
'Wed, 15 Sep 2004 18:00:44 -0500',
'X-Body',
'
EL BODY DE NUESTRO MENSAJE #2
Y OTRAS LÍNEAS
'
],
[
'X-Pickup-Date',
'Thu Sep 16 12:02:49 2004',
'X-Persona',
'<Perl en Español>',
'Return-path',
'<[email protected]>',
'Envelope-to',
'[email protected]',
'Delivery-date',
'Wed, 15 Sep 2004 18:00:44 -0500',
'To',
'[email protected]',
'From',
'[email protected]',
'Subject',
'SUBJECT #3',
'Message-Id',
'<[email protected]>',
'Date',
'Thu, 16 Sep 2004 18:00:44 -0500',
'X-Body',
'EL BODY DE NUESTRO MENSAJE #3
Y OTRAS LÍNEAS'
]
];
Como vemos nuestro arreglo tiene 3 elementos, uno por cada mensaje, los cuales a su vez tienen valores, como vemos los valores se alternan entre nombre-de-campo y valor-de-campo.
Así por ejemplo si quisiéramos imprimir el Subject de los mensajes podríamos hacer algo así:
foreach my $mbox(@mailbox){
print "$mbox->[14]: $mbox->[15]\n";
}
Lo cual nos daría como resultado:
Subject: SUBJECT #1
Subject: SUBJECT #2
Subject: SUBJECT #3
Pero como vemos es bastante tedioso tener que contar el número de elemento en el cual se encuentra el valor que queremos. Es por ello que lo mejor sería crear un hash, donde las llaves serían los nombres de los campos, y los valores pues los valores de los campos.
Entonces podrías hacer algo así, lo cual fue sugerido por Marco A. Manzo:
my %Mensajes;
for ( 0..$#mailbox ) {
$Mensajes{$_} = { @{ $mailbox[$_] } };
}
Lo que hace es hacer un iteración a través de cada elemento de nuestro arreglo, y después crea los sets de llaves, valor. Como sabes los elementos nones son los nombres de los campos y los elementos pares sus valores.
Después de hacer un Dump de nuestra variable %Mensajes podemos ver que su estructura quedo de la siguiente manera:
$VAR1 = {
'1' => {
'Subject' => 'SUBJECT #2',
'Delivery-date' => 'Wed, 15 Sep 2004 18:00:44 -0500',
'Date' => 'Wed, 15 Sep 2004 18:00:44 -0500',
'X-Body' => '
EL BODY DE NUESTRO MENSAJE #2
Y OTRAS LÍNEAS
',
'Message-Id' => '<[email protected]>',
'Return-path' => '<[email protected]>',
'X-Persona' => '<Perl en Español>',
'Envelope-to' => '[email protected]',
'To' => '[email protected]',
'X-Pickup-Date' => 'Thu Sep 16 15:02:49 2004',
'From' => '[email protected]'
},
'0' => {
'Subject' => 'SUBJECT #1',
'Delivery-date' => 'Wed, 15 Sep 2004 18:00:44 -0500',
'Date' => 'Wed, 15 Sep 2004 18:00:44 -0500',
'X-Body' => '
EL BODY DE NUESTRO MENSAJE #1
Y OTRAS LÍNEAS
',
'Message-Id' => '<[email protected]>',
'Return-path' => '<[email protected]>',
'X-Persona' => '<Perl en Español>',
'Envelope-to' => '[email protected]',
'To' => '[email protected]',
'X-Pickup-Date' => 'Thu Sep 16 12:02:49 2004',
'From' => '[email protected]'
},
'2' => {
'Subject' => 'SUBJECT #3',
'Delivery-date' => 'Wed, 15 Sep 2004 18:00:44 -0500',
'Date' => 'Thu, 16 Sep 2004 18:00:44 -0500',
'X-Body' => 'EL BODY DE NUESTRO MENSAJE #3
Y OTRAS LÍNEAS',
'Message-Id' => '<[email protected]>',
'Return-path' => '<[email protected]>',
'X-Persona' => '<Perl en Español>',
'Envelope-to' => '[email protected]',
'To' => '[email protected]',
'X-Pickup-Date' => 'Thu Sep 16 12:02:49 2004',
'From' => '[email protected]'
}
};
Vemos como cada uno de nuestros elementos ya quedo separado como una llave junto con su valor. De esta manera si quisiéramos leer Subject del mensajes número dos, simplemente debemos de hacer lo siguiente:
print $Mensajes{1}{Subject};
O si quisiéramos leer el Body del mensaje número 1 haríamos lo siguiente:
print $Mensajes{0}{'X-Body'};
Últimas Palabras
Con este pequeño tutorial vimos lo fácil que es realizar todo tipo de tareas con perl. En este caso leímos un archivo de mensajes de Eudora y los guardamos para su fácil acceso en un hash.
Cabe destacar de que entre más grande sea su archivo más memoria va a utilizar este script, pues todos los mensajes son guardados en memoria.
Código Final
use strict;
use Tie::Eudora;
use Data::Dumper;
#LEEMOS EL ARCHIVO .mbx DE EUDORA
tie *MAILBOX, 'Tie::Eudora';
open MAILBOX,'<Inbox.mbx';
my @mailbox = <MAILBOX>;
close MAILBOX;
#CREAMOS EL HASH CON LA INFO DE LOS MENSAJES
my %Mensajes;
for ( 0..$#mailbox ) {
$Mensajes{$_} = { @{ $mailbox[$_] } };
}
exit(1);
Agradecimientos
Quisiera agradecer a Marco A. Manzo por hacer una revisión del tutorial y haber dado recomendaciones en cambios en el código, haciendo del tutorial un tutorial mejor.