lunes, 31 de agosto de 2009

La magia de Perl

[Google english translation]
Una de las características que hacen de perl una navaja suiza (por cierto afilada) son las construcciones mágicas del lenguaje, que permiten escribir micro programas rápida y fácilmente, y es una de las razones por las cuales el lenguaje tiene fama de incomprensible.
Aunque el uso de estas construcciones están contraindicadas en la programación de sistemas, son de gran utilidad en el día a día de los administradores de sistemas y programadores, por ello voy a explicar las más comunes, para que puedas apreciar su potencia y entender los scripts que se crucen en tu camino.

El operador de lectura

En Perl se puede leer una línea de la entrada estándar de la siguiente manera:

my $linea = readline(STDIN);
Sin embargo también existe el operador de lectura <ARCHIVO> que permite leer una línea así:

my $línea = <STDIN>;
adicionalmente si el archivo es STDIN se puede omitir el nombre en el operador, quedando simplemente:

my $linea = <>;
Cuando enfaticé "una línea" en los párrafos anteriores lo hice porque en Perl no se leen líneas, en realidad se leen registros, claro que por defecto los registros están definidos como texto terminado por fines de línea, que a su vez tienen un significado diferente en cada plataforma, por ejemplo en Unix el fin de línea es "\n", mientras que en Windows es "\r\n".

El acumulador o Variable Implícita ($_)

Es una variable global que se utiliza implícitamente, sobre todo en las operaciones mágicas, su nombre viene de la similitud con el acumulador en Ensamblador que se utiliza implícitamente, aunque es mejor llamarla "Variable Implícita", el nombre de esta variable en Perl es: "$_".
Uno de los usos más comunes de la variable implícita es en la prueba de expresiones regulares, normalmente cuando quieres saber si el contenido de una variable concuerda con una expresión regular haces:

if ( $texto =~ m/lo que se busca/ ) { ...
Pues bien, si el texto se encuentra en $_ entonces se puede hacer:

if ( m/lo que se busca/ ) { ...
Además $_ se utiliza en algunas funciones de la librería como split y print, por ejemplo:


@campos = split( /,/, $texto );
@campos = split /,/;
La primera línea pica el contenido de la variable $texto (por las comas) mientras la segunda hace lo mismo utilizando implícitamente el contenido de $_. Algo similar sucede con print. Por cierto, ¿se notó que la segunda llamada no tiene paréntesis?, eso es producto de que en Perl esos paréntesis son opcionales (pero dejaré eso para otro artículo).

El ciclo mágico

Cuando se combina $_ el operador de lectura <> y un ciclo while tenemos el ciclo mágico de Perl:

while ( <> ) {
    print;  # se imprime $_ implícitamente 
}
Este ciclo lee de la entrada estándar mientras haya líneas que leer, y el resultado de cada lectura queda en $_, en efecto el programa anterior se parece mucho a cat(1) de Unix, supongamos que tenemos este programa instalado con el nombre plcat, entonces:

$ plcat < archivo.txt
$ plcat < fuente.txt > destino.txt
$ ls -R / | plcat
Hacen exactamente lo mismo que cat(1), pero además hace más magia, así que:

$ plcat archivo.txt
$ plcat archivo.txt otro.txt
Incluso el argumento "-" de cat funciona igual!:

$ plcat archivo.txt - otro.txt

El ciclo mágico implícito

El ciclo mágico puede omitirse si perl se invoca con alguna de las opciones -p ó -n, la primera envuelve todo el código de Perl en el ciclo que antes vimos, con el print al final, la segunda hace lo mismo pero omite el print. Así que el programa anterior se puede escribir en una sola línea de perl:

$ perl -ne 'print' archivo.txt
Aunque como -p también incluye el print podemos hacer:
$ perl -pe '' archivo.txt

Un ejemplo práctico

Una forma de escribir grep en Perl sería:

#!/usr/bin/env perl
my $patron = shift(@ARGV);
while (<>) {
    print "$ARGV:$.: $_" if m/$patron/; 
}
La variable $ARGV contiene el nombre del archivo actualmente abierto por el ciclo mágico, y la variable $. contiene el número de la última línea leída. Todo esto se puede hacer directamente desde la línea de comandos:

$ perl -ne 'print "$ARGV:$.: $_" if m/lo que busco/'
Y después de saber como funcionan los operadores lógicos en Perl, puedo hacerlo así también:

$ perl -ne '/lo que busco/ && print "$ARGV:$.: $_"'

El separador de registros

Ya mencione que el concepto de línea en Perl en realidad significa registro, y que los registros se definen por defecto para coincidir con el fin de línea del sistema operativo. La variable global que configura como se interpretan los registros en Perl es $/, así que si se necesita procesar un archivo separado por una cadena particular solamente se asigna esa cadena a $/.
Esta variable tiene valores especiales para lograr diversos efectos, el primer valor especial es indefinido, cuando $/ = undef, el registro es todo el archivo, así que:

$todo = readline(STDIN);

Lee todo el archivo en la variable, se imaginarán que si el archivo es un DVD probablemente te quedarás sin memoria antes de que el programa termine.
Otro valor interesante es la cadena vacía, que permite leer registros de varias líneas delimitados por líneas en blanco, el siguiente programa lee un archivo de paquetes de Debian paquete por paquete imprimiendo el nombre de cada paquete:

#!/usr/bin/env perl
$/ = '';
while (<>) {
    /^Package:\s+(.*)/m or die "Error de sintaxis";
    print "$1\n";
}

Y por supuesto su correspondiente programa de una línea:

$ perl -ne '/^Package:\s+(.*)/m && print "$1\n"'

Recomendaciones finales

El propósito de este artículo es dar a conocer técnicas importantes que ayudan a resolver problemas puntuales, sin embargo, casi todo lo aquí expuesto debe evitarse para programas que sean de más de una docena de líneas o cuando se pretenda instalar un programa como herramienta en una organización.
Para programas grandes ó duraderos:
  • Evita $_ excepto cuando no haya más remedio, o en pequeños trozos de código donde esté topicalizada y aún así es mejor usar una variable local (declarada con my), si no sabes que es eso de la topicalización no uses $_.
  • Usa preferiblemente readline() en vez del operador de lectura, porque hay mas chance de que un novato entienda el programa.
  • No uses $/ pues cambia el comportamiento de todos los archivos abiertos en perl, esto es común para algunas otras variables que configuran el ambiente operativo en perl y que puedes consultar con el comando: perldoc perlvar
Suficiente magia por hoy.

No hay comentarios:

Publicar un comentario