Perl

De Astillas.net
Perl.png

Enlaces y referencias

Plantillas

En este wiki existe algunas plantillas útiles para cuando se crean documentación sobre Perl y sus módulos:

Documentación para un programa

Las siguientes páginas proporcionan ayuda sobre documentación:

  • POD habla sobre el formato de la documentación.
  • Pod::Usage es una herramienta que automatiza los textos de ayuda en programas
  • Hay algunas plantillas para insertar en los fuentes de un programa.

y estas otras también pueden ser útiles:

  • Gettext simplifica la traducción de textos

Configuración

Recursos X del depurador ptkdb

Para situar dentro de ~/.Xdefaults o ~/.Xresources, o para cargar directamente con xrdb.

! Perl Tk Debugger XResources.
ptkdb*scrollbars: sw
! controls where the code pane is oriented, down the left side, or across
! the top values can be set to left, right, top, bottom 
ptkdb*codeside: left
! Background color for the balloon
ptkdb.frame2.frame1.rotext.balloon.background: blue
ptkdb.frame2.frame1.rotext.balloon.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-* 
! Hot Variable Balloon Font 
ptkdb.frame*font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.frame.menubutton.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.frame2.frame1.rotext.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.notebook.datapage.frame1.hlist.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.notebook.subspage*font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.notebook.brkptspage*entry.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-* 
ptkdb.notebook.brkptspage*button.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.notebook.brkptspage*button1.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.notebook.brkptspage*checkbutton.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.notebook.brkptspage*label.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-* 
ptkdb.toplevel.frame.textundo.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.toplevel.frame1.text.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-* 
ptkdb.toplevel.button.font:      -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.toplevel.button1.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.toplevel.button2.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
ptkdb.toplevel.button3.font: -b&h-lucidatypewriter-medium-r-normal-*-20-*-*-*-m-*-*-*
! Background color for where the debugger has stopped
ptkdb*stopcolor: blue
! Background color for set breakpoints
ptkdb*breaktagcolor*background: yellow
ptkdb*disabledbreaktagcolor*background: white
! Font for where the debugger has stopped
ptkdb*stopfont:-b&h-lucidabright-demibold-r-normal-*-20-*-*-*-p-*-*-*
! Background color for the search tag
ptkdb*searchtagcolor: blue
ptkdb*background: black
ptkdb*foreground: white

La tipografía empleada es Lucida TypeWriter y he empleado el programa xfontsel del paquete x11-utils para obtener su nombre completo.


Perl en sólo una línea

Perl es utilizado en muchas tareas de administración de sistemas porque, entre otras ventajas, dispone de un nutrido grupo de parámetros de llamada del propio intérprete que hacen que éste haga cosas.

Dos fuentes de referencia para abrir boca:

Y ahora vamos con esas cosas que se supone que el intérprete Perl hace automágicamente.

Ejecutar expresiones Perl: -e

Este parámetro, que puede repetirse varias veces, permite ejecutar expresiones Perl directamente. No se buscarán nombres de programa en los restantes argumentos y se añade un salto de línea al final de cada expresión.

$ perl -e 'print "Hello\n";' -e 'print " and goodbye\n";'

Procesar fin de línea automáticamente: -l

Habilita el proceso de final de líneas leídas, con las siguientes acciones:

  • Aplica chomp() en todas las líneas cuando se usan los parámetros -n y/ó p

Lectura y proceso de la entrada: -n

Envuelve todas las expresiones de código alrededor de lo siguiente:

LINE: while (<>) {
          # aqui va nuestro codigo 
          ...
      }

Es decir, lee la entrada estándar, línea a línea, y ejecuta el código indicado dentro del bucle.

Lectura y proceso de la entrada con copia a la salida estándar: -p

Envuelve todas las expresiones de código alrededor de lo siguiente:

LINE:
while (<>) {
    # aqui va nuestro codigo
    ...
} continue {
    print;
}

Lo que provoca que, tras ejecutar nuestro código, en cada pasada del bucle, envía a la salida estándar del proceso el contenido de la variable $_.

Partir la entrada en campos: -a

Utilizado con -p ó -n habilita un preproceso de cada línea de entrada, efectuando una división por espacios mediante la función split sobre el array @F. El separador puede cambiarse utilizando el parámetro -F.

while (<>) {
    @F = split(' ');
    ...                 # aqui va nuestro codigo 
}

Modificando archivos in situ: -i

Este parámetro habilita un modo especial en el que la construcción <> opera sobre los archivos in situ, modificándo su contenido, y con la posibilidad de efectuar una copia de seguridad sobre los mismos.

Es equivalente a la siguiente construcción:

#!/usr/bin/perl
$extension = '.orig';
LINE: while (<>) {
    if ($ARGV ne $oldargv) {
        if ($extension !~ /\*/) {
            $backup = $ARGV . $extension;
        }
        else {
            ($backup = $extension) =~ s/\*/$ARGV/g;
        }
        rename($ARGV, $backup);
        open(ARGVOUT, ">$ARGV");
        select(ARGVOUT);
        $oldargv = $ARGV;
    }

    ....        # aqui va nuestro codigo

}
continue {
    print;      # this prints to original filename
}

select(STDOUT);

Para lograr esto, Perl realiza las siguientes tareas:

  1. Renombra el archivo original para conservar una copia de seguridad siempre que se proporcione una extensión a este parámetro:
    1. Si la extensión no contiene un asterisco (*) se añade al final del nombre del archivo como un sufijo.
    2. Si contiene uno ó más caracteres asterisco se sustituyen por el nombre del archivo original, de manera que es posible crear prefijos, sufijos ó incluso moverlos a otros directorios (siempre que éstos existan de antemano).
  1. Abre el archivo de salida hacia el nombre original.
  2. Selecciona dicho archivo como el destino predeterminado del operador print.

Es posible comprobar el final de cada fichero utilizando el operador eof.

Preguntas y respuestas

¿ Cómo comenzar un módulo Perl ?

Usando el programa module-starter que está incluido en el paquete Module::Starter con los parámetros adecuados para quitarnos trabajo de encima:

Parámetro Argumento Opcional Uso y descripción
--module name No Define el nombre del módulo
--author full_name Nombre del autor
--email email Dirección de correo del autor
--dir path Directorio donde se creará el árbol de trabajo.
--builder Module name

Nombre del módulo Perl que emplearemos como constructor. Existen varias opciones que además pueden acortarse empleando otros parámetros:

--license type Tipo de licencia a utilizar con el módulo. Se reconocen por nombre las siguientes: perl, bsd, gpl, lgpl y mit.

Árbol de trabajo

Aunque depende de factores como el módulo constructor definido -y los complementos instalados- el árbol contiene más o menos lo siguiente:

$ module-starter --module=Astillas --license=perl --builder=Module::Build \ 
  --verbose --dir=astillas-perl
Created astillas-perl
Created astillas-perl/lib
Created astillas-perl/lib/Astillas.pm
Created astillas-perl/t
Created astillas-perl/t/pod-coverage.t
Created astillas-perl/t/pod.t
Created astillas-perl/t/manifest.t
Created astillas-perl/t/boilerplate.t
Created astillas-perl/t/00-load.t
Created astillas-perl/ignore.txt
Created astillas-perl/Build.PL
Created astillas-perl/Changes
Created astillas-perl/README
Created astillas-perl/MANIFEST
Created starter directories and files
$ 
/lib
Directorio raíz donde situará el código fuente Perl
/t
Directorio raíz de los programas de test
Build.PL
Archivo base de todo el proyecto
Changes
Registro de cambios
README
Primera documentación sobre el paquete
MANIFEST
Relación de contenido del paquete
ignore.txt
Archivo plantilla para excluir archivos del proyecto.

Configuración

El módulo Module::Starter admite configuración. Esta se sitúa en el archivo $HOME/.module-starter/config (o en lo que indique la variable de entorno MODULE_STARTER_DIR) y consiste en una lista de pares nombre y valor separados por dos puntos. Si el valor contiene además una lista los elementos de ésta se separarán con espacios en blanco.

Los nombres corresponden a los valores que puede recibir un objeto Module::Starter y que coinciden con los parámetros del programa module-starter:

author: Victor Moral
email: victor@astillas.net
license: perl
builder: mb
ignores_type: git manifest

El último nombre define los dos sistemas SCM que vamos a emplear y para los que queremos crear listas de exclusión. En este caso son Git y el propio archivo Manifest.

Aviso: las variables desconocidas se ignoran sin advertencia alguna.

¿ Dónde instalar los módulos de mi aplicación ?

Hablando de Debian y ya que esta distribución adopta una jerarquía de archivos estándar podemos anotar lo siguiente:

  1. La jerarquía /usr/lib está pensada para alojar programas y componentes dependientes de la arquitectura donde se instale y datos de sólo lectura.
  2. La jerarquía /usr/share/ está indicada para componentes Perl puros, independientes de la arquitectura subyacente en ese caso, y para almacenes de datos de sólo lectura.
  3. Se anima a las aplicaciones a que empleen directamente su propio directorio raíz dependiente de alguno o todos estos lugares.

Así que si tenemos una aplicación llamada vespasiano podemos incluir sus módulos Perl puros en /usr/share/vespasiano/.

Empaquetando para Debian

Para realizar esta tarea existe la herramienta dh-make-perl del paquete del mismo nombre. Con ella es relativamente sencillo descargar un paquete desde el repositorio CPAN, extraerlo, comprobar las dependencias, añadir la parte debian/ e incluso construir el paquete final. Todo esto si no hay otros problemas por medio.

Para empezar a empaquetar un módulo como MooseX::Clone haríamos:

 $ dh-make-perl --cpan MooseX::Clone

Conceptos

Prototipos

Los prototipos de funciones en Perl son un mecanismo limitado de comprobación de parámetros de funciones en tiempo de compilación. Estas declaraciones deben ser, pues, visibles en ese momento y afectan sólo a las llamadas a funciones con el estilo nuevo, en las que no se emplea el carácter & como prefijo. Afectan por tanto a este ámbito por lo que se pueden seguir empleando de la forma antigua.

No tienen influencia con las referencias a subrutinas o a uso indirecto de las mismas:

  • \&foo
  • &{$foo_ref}
  • $foo_ref->()


Recetario

Ordenación numérica

Si se emplea la función interna sort los elementos se comparan como textos a menos que se emplee un bloque o una función donde poder variarlo usando los operadores adecuados.

En este caso:

my @ordered = sort { $a <=> $b } keys %lista;

el operador <=> es el apropiado.

Textos multilínea en el código fuente

my $texto = <<EOF;
Esta es mi declaración 
de disconformidad ...
.
.
y firmo y sello a fecha ...
EOF


Datos en archivos fuente

Dentro de un archivo fuente Perl es posible incluir datos accesibles como si de un archivo se tratase mediante el uso del token __DATA__ en una única línea en el fuente. Todo lo que aparezca tras ella no se considera parte del archivo fuente por lo que el intérprete Perl lo ignora.

Para acceder a su contenido se utiliza el símbolo DATA, que se define automágicamente en el módulo, de forma similar a ésta:

my $line = undef;

while (<DATA>) { $line .= $_; }

__DATA__
Primera línea de datos 
Segunda línea de datos 
... 
Enésima línea de datos

Hay que entender que el símbolo DATA hace referencia en realidad al símbolo PACKAGE::DATA y que, por tanto, es similar a STDOUT y STDIN, los cuales referencian los descriptores de archivos estándar para la entrada y la salida. Perl se encarga de conectarlos en el script.

También a tener en cuenta:

  • Existe otro símbolo también usado para ésto, __END__, pero como es un alias para main::DATA hace referencia únicamente al paquete principal y, obviamente, no se debe emplear en los módulos porque en realidad no estaría haciendo referencia al módulo (al espacio de nombres del módulo más bien), sino al espacio de nombres principal main.
  • Sí se emplean los dos símbolos en un mismo fuente -__END__ para indicar el comienzo de la documentación y __DATA__ para el comienzo de los datos- los resultados serán erróneos en la lectura o en la generación de la documentación. En caso necesario se puede imbuir la documentación en el fuente usando =pod y =cut y situar los datos al final del módulo.

Otro ejemplo más citando lo anterior:

package MyPackage;

use Pod::Parser;

sub extract { 
    my $parser = Pod::Parser->new();

    # Aquí vamos a leer en realidad de <MyPackage::DATA>;
    return $parser->parse_from_filehandle(\*DATA);
}

=pod 

=encoding utf8

=head1 MyPackage

Esta es la auténtica documentación y bla,bla,bla.

=cut

1;
__DATA__

=head1 Mis funciones

Este texto es lo que lee el método I<extract> y con el que hace su trabajo ...

=head2 funcion1()

=head2 funcion2()

Acortando nombres de módulos

Cuando creamos módulos para una aplicación podemos encontrarnos ante el dilema de definir jerarquías de nombres largos y clarificadores pero pesados de emplear en los fuentes, o acortarlos hasta que sean manejables pero difíciles de distinguir. Existen un módulo en CPAN llamado aliased que nos permite usar jerarquías largas y manejables a un tiempo.

En el ejemplo siguiente la primera llamada crea e importa en el espacio de nombres del paquete activo una función cuyo nombre coincide con la última parte de la ruta indicada. En la segunda llamada le indicamos el nombre que nos resulte más cómodo para emplear después.

use aliased "MyCompany::Bancos::AEB::Norma58::RegistroPresentador";
my $registro = RegistroPresentador->new();

...

use aliased "MyCompany::Bancos::AEB::Norma58::RegistroPresentador" => 'Presentador';
my $registro = Presentador->new();

Sólo sirve para programación orientada a objetos dado que el módulo crea una función que devuelve el nombre completo de la clase y que se integra bien en el mecanismo de despacho de métodos. Para la programación procedimental el autor explica que tendría que usar algunos trucos empleando typeglobs y no ve la necesidad.

Fork

Libro: Learning Perl de Randall Schwartz (capítulo 14.4: Using fork )

El algoritmo básico al emplear fork es el que sigue:

my $child_pid;

if (!defined($child_pid = fork())) {
    die "cannot fork: $!";
} elsif ($child_pid) {
    # I'm the parent
} else {
    # I'm the child
}

Primera letra en mayúsculas

Para transformar un texto de manera que pase de todo mayúsculas, por ejemplo, a sólo la primera letra de cada palabra conviene realizar una división de dicho texto en palabras y aplicar la función lcfirst a cada una.

my $texto = q(LACES TRANSPARENT IN BLISTER 060 CM ROUND FINE BLACK);
$texto = lc( $texto );
$texto =~ s/(^(\w)|\b(\w))/\U$1/g;
print $texto;
# Laces Transparent In Blister 060 Cm Round Fine Black

En el ejemplo cambiamos primero a minúsculas y luego lo separamos por palabras teniendo en cuenta aquellas que comienzan el texto o las que son precedidas por un espacio en blanco. Extraemos la palabra y ponemos en mayúsculas sólo la primera.

Como también recomiendan en la referencia anterior, se puede utilizar el módulo Text::AutoFormat de Damian Conway con mayor comodidad.

Ejemplo de uso:

use Text::AutoFormat;

my $text = q(LACES TRANSPARENT IN BLISTER 060 CM ROUND FINE BLACK);
my $formatted = autoformat $text, { case => 'highlight' });
print $formatted;
# Laces Transparent in Blister 060 Cm Round Fine Black

Adviértase que lo que el autor considera palabras triviales corresponden con el idioma inglés y no hay una forma sencilla (ni documentada) de cambiarlo.

Dependencias de otros módulos

El programa scandeps parte del módulo Perl Module::ScanDeps y está diseñado para proporcionar una lista de dependencias de otros módulos Perl en un proyecto.

Dada la naturaleza de Perl es difícil obtener una lista precisa. El código puede cargarse dinámicamente e incluso construirser al vuelo por lo que las dependencias pueden ser sorpresivas.

El uso del programa es muy simple, se invoca sobre los fuentes Perl (uno o varios) sobre los que se pretenda obtener información, y el resultado es un bloque de texto que puede incluirse en el archivo de construcción del proyecto, ya sea un Build.PL o un Makefile.PL.

$ scandeps lib/MyModule.pm
'List::Util'   => '1.47',
'Modern::Perl' => '1.20170117',
$

Los parámetros más comunes son los siguientes:

  • -R: búsqueda no recursiva
  • -c: compilar los fuentes e inspeccionar %INC depués
  • -x: compilar y ejecutar el código para inspeccionar después %INC

Como desventaja podemos señalar que:

  1. Los resultados son módulo a módulo y no tienen en cuenta si pertenecen a una librería Perl
  2. Los números de versión son fijos, algo que puede dar problemas en el futuro.

Así que sirve como punto de partida pero no como herramienta final automática.

Permisos de archivos en octal y demás

Módulos de CPAN

En las siguientes secciones anoto qué módulos se pueden emplear para realizar tareas concretas. De esta forma consigo una relación de aquellos que me son más útiles según su naturaleza.

Desarrollo

  • Test::More y otros que sirven para crear pruebas unitarias.
    • Test::Net::Service permite cortocicuitar test dependiendo de la conectividad de un servidor.
  • Module::Find permite encontrar y/o cargar módulos de o dentro de una subcategoría.
  • Devel::Local modifica la variable PERL5LIB para usar otros repositorios en el desarrollo de otro.

Programación orientada a objetos

  • Moose: creación de clases orientadas a objetos con ayuda de los siguientes extras:

Archivos y directorios

Configuración de programas

Registro de sucesos y errores

Cálculos

Redondeo de cifras

La siguiente función soluciona un problema de redondeo con la función sprintf dado que ésta emplea como principio el redondeo de la mitad hacia los números enteros pares más cercanos (véase la entrada de la Wikipedia al respecto). En los foros de StackOverflow he encontrado una respuesta a la que mejorado ligeramente y que consigue el tipo de redondeo que considero apropiado: de la mitad hacia la siguiente cifra sea ésta par o impar.

# Dada una función que redondea a 2 decimales por defecto
sub redondear {
    my $cifra     = shift;
    my $decimales = shift || 2;

    return _fix_sprintf( "%${decimales}f", $cifra);
}

sub _fix_sprintf {
    my $format  = shift;
    my $value   = shift;

    # Si termina en cinco (los posibles ceros a continuación 
    # son despreciados)
    if ($value =~ m/\d\..*50*$/){
        # y el formato incluye un número concreto de cifras decimales
        $format =~ /.*(\d)f$/;
        if (defined $1){
            # le añadimos una fracción de 5 al final para que sprintf redondee
            # correctamente 
            my $coef = "0." . "0" x $1 . "05";  
            $value = $value + $coef;    
        }
    }

    $value = sprintf( "$format", $value );

    return $value;
}

Es cierto que añade cierta sobrecarga en el proceso pero vista la ventaja de tener un método de redondeo de decimales preciso el precio no es alto.

Validación de datos

  • CheckDigits es un módulo que permite validar diferentes códigos entre los que se encuentran:
  • Regexp::Common es un compendio de expresiones regulares que pueden utilizarse en cualquier parte de una aplicación.