Perl Parte II

ArticleCategory: [Es gibt verschiedene Artikel Kategorien]

Software Development

AuthorImage:[Ein Bild von Dir]

[Foto del Autor]

TranslationInfo:[Author and translation history]

original in en Guido Socher en to es Hermán Mauricio Rodrígez

AboutTheAuthor:[Eine kleine Biographie über den Autor]

Guido es un seguidor de Linux y un hacker del Perl desde hace mucho tiempo. En estos días también está muy ocupado remodelando la casa y sembrando plantas y otras cosas en el jardín.

Abstract:[Here you write a little summary]

Perl Parte II es el primer artículo en nuestra serie Perl donde mostraremos un programa util. La Parte I de Perl provee una vista general sobre Perl.

ArticleIllustration:[This is the title picture for your article]

[Ilustración]

ArticleBody:[The article body]

Una plantilla para tu programa

Perl es mas adecuado para escribir programas pequeños, especializados en una tarea. Para acelerar el proceso de desarrollo es una buena idea tener una plantilla a la mano que ofrece alguna estructura basica y funcional que desee tener en la mayoria de los programas. El siguiente código brinda una opción estructural basica y tiene lista una subrutina para imprimir un mensaje de ayuda.

!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
#
# descomentar strict para hacer que el compilador perl sea
# muy estricto con las declaraciones:
#use strict;
# global variables:
use vars qw($opt_h);
use Getopt::Std;
#
&getopts("h")||die "ERROR: No existe tal opción. -h para ayuda.n";
&help if ($opt_h);
#
#>>tu código<<
#
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
sub help{
print "mensaje de ayuda\n";
exit;
}
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
__END__

Observe el código. &getopts() lee las opciones de la línea de comandos. Este fija las variables globales de nombre $opt_<option> de acuerdo a las opciones dadas en la línea de comandos. Todas las opciones comienzan con un "-" (signo menos) y deben ir despues del nombre del programa y antes de algunos otros argumentos. La cadena dada a getopts (la "h" en el programa anterior) lista todas las letras de opciones que son permitidas. Si la opción toma un argumento entonces los dos puntos deben ser escritos despues de la letra de opción. &getsopt("d:x:h") dice que este programa tiene las opciones -d, -x y -h. Las opciones -d y -x toman un argumento. Asi por ejemplo "-o algunacosa" sería valido pero "-o -x algunacosa" es un error porque la opcion -o no esta acompañada por un argumento.
El comando &help if ($opt_h); llama a la subrutina de ayuda si la opción -h fue dada en la línea de comandos. El comando sub help{ declara la subrutina. No es importante por el momento que comprenda todos los detalles del código. Sencillamente tómelo como una plantilla donde hace falta adicionar su funcionalidad principal.

Usando la plantilla

Escribamos un pequeño conversor de numeros el cual hace uso de esta plantilla. El programa, que llamaremos numcov, debe convertir números hex y decimales.
numconv -x 30 debe imprimir el equivalente hexadecimal del decimal 30.
numconv -d 1A debe imprimir el equivalente decimal del hexadecimal 1A.
numconv -h debe imprimir el texto de ayuda.
La funcion hex() de perl convierte los numeros hex a decimal y la función printf() puede ser usada para convertir un decimal en un hex. Insertando esto en nuestra plantilla obtendremos rapidamente un bonito programa:

#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
#
# uncomment strict to make perl compiler very
# strict about declarations:
#use strict;
# global variables:
use vars qw($opt_d $opt_x $opt_h);
use Getopt::Std;
#
&getopts("d:x:h")||die "ERROR: No such option. -h for help.n";
&help if ($opt_h);
if ($opt_d && $opt_x){
    die "ERROR: options -x and -d are mutual exclusive.\n";
}
if ($opt_d){
    printf("decimal: %d\n",hex($opt_d));
}elsif ($opt_x){
    printf("hex: %X\n",$opt_x);
}else{
    # wrong usage -d or -x must be given:
    &help;
}
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
sub help{
    print "convert a number to hex or dec.
USAGE: numconv [-h] -d hexnum
    umconv [-h] -x decnum

OPTIONS: -h this help
EXAMPLE: numconv -d 1af
\n";
    exit;
}
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
__END__

click aqui para descargar el código del programa numconv que se observa arriba.
En los siguientes párrafos analizaremos este programa por partes e intentaremos comprenderlo.

Sentencia If

La sentencia If en perl viene en 2 formas:
expr if (cond);
o
if (cond) BLOQUE [[elsif (cond) BLOQUE ...] else BLOQUE]

BLOQUE es un numero de sentencias encerradas en llaves {}. Esto significa que puede, por ejemplo, escribir:

printf("hello\n") if ($i);

if ($i == 2){
   printf("i is 2\n");
}elsif ($i == 4){
   printf("i is 4\n");
}else{
   printf("i is neither 2 nor 4\n");
}

Como en C es posible tambien usar los operadores && y ||.
printf("hello\n") if ($i);
Por consiguiente puede ser escrito tambien asi
($i) && printf("hello\n");
Especialmente el || como el usado en nuestra plantilla se traduce bastante bien en el lenguaje hablado.
&getopts("d:x:h")||die "ERROR\n";
"Obtiene las opciones o muere". La función die() es basicamente equivalente a un printf seguido de exit. Asi que muestra un mensaje y termina el programa.
&getopts("d:x:h")||die "ERROR\n";
es equivalente a
die "ERROR\n"; if (! &getopts("d:x:h"));
Donde el ! es un operador no lógico. Esto también puede ser escrito como
die "ERROR\n"; unless (&getopts("d:x:h"));
unless es lo mismo que if-not y es más agradable de leer que if(!..)

Variables

En el primer artículo de perl vimos que las variables escalares (las $-variables) se usan sin declararlas. Se originan en el mismo momento en que ellas son usadas. Es una buena ventaja para programas pequeños pero puede conducir a errores dificiles de encontrar en programas largos. Declarar una variable le da al compilador la posibilidad de hacer algunos chequeos extras para determinar errores.
"use strict;" te obliga a declarar todo.
Considere el siguiente código.

#!/usr/bin/perl
#
$i=1;
print "i is $tpyerr\n";

Este codigo se ejecuta bien en perl y produce "i is ". El módulo de perl "use strict;" puede obligar al compilador a protestar sobre un programa. Cuando usas "strict" todas las cosas deben ser declaradas y sino es un error.

#!/usr/bin/perl
use strict;
my $i=1;
print "i is $tpyerr\n";

Esto ocasiona el siguiente mensaje y facilita ubicar el lugar del error.

Global symbol "$tpyerr" requires explicit package name at ./vardec line 4.
Execution of ./vardec.txt aborted due to compilation errors.
Exit 255

Ahora es fácil corregir el código del programa:

#!/usr/bin/perl
use strict;
my $i=1;
print "i is $i\n";

Las Variables pueden ser declaradas en perl usando "my" o, tal como ya vimos en la plantilla, con "use vars":
use vars qw($opt_h);

Las variables globales son declaradas con use vars. Estas variables son globales incluso para todas las librerías incluidas.
Las variables locales al archivo del programa actual (globales entre las subrutinas en este archivo) son declaradas con my al comienzo del programa (fuera de subrutina).
Las variables locales a la subrutina actual son declaradas con my dentro de la subrutina.

La gente con experiencia en programación del shell podría ser tentada a dejar por fuera el signo $ cuando declara la variable o le asigna un valor. Esto no es posible en perl. Debe escribir siempre un signo $ cuando usa una variable escalar sin importar que hará con ella.

Puede tambien asignar directamente un valor a la variable cuando la declara. my $myvar=10; declara la variable $myvar y le asigna su valor inicial a 10.

Subrutinas

Ya hemos usado la subrutina "help" en el programa anterior numconv. Las subrutinas pueden ser usadas para programar funciones personalizadas. Ellas ayudan a estructurar su programa. Una subrutina puede ser insertada en algun lugar en el texto del programa (antes o despues de que sea llamada. Eso no importa). Usted comienza una subrutina con sub nombre(){... y la llama con $retval=&nombre(...argumentos...). El valor retornado es el valor de la ultima sentencia ejecutada en la subrutina. Los argumentos dados en la subrutina son pasados al codigo interno de la subrutina en el arreglo especial @_. Analizaremos esto con mas detalle cuando hablemos sobre arreglos. Por el momento es suficiente saber que los valores de variables escalares pueden ser ser leidos en la subrutina usando shift. Aqui esta un ejemplo:

#!/usr/bin/perl
use strict;
my $result;
my $b;
my $a;
$result=&add_and_duplicate(2,3);
print "2*(2+3) is $result\n";

$b=5;$a=10;
$result=&add_and_duplicate($a,$b);
print "2*($a+$b) is $result\n";

# add two numbers and multiply with 2:
sub add_and_duplicate(){
    my $locala=shift;
    my $localb=shift;
    ($localb+$locala)*2;
}

Un programa real

Ahora que hemos avanzado un poco en la sintaxis de perl y elementos del lenguaje, es hora de escribir un programa real.
Perl fue designado para manipular archivos de texto haciendo muy poco esfuerzo en programacion. Nuestro primer programa Perl deberia comparar una lista de abreviaciones y descubrir los duplicados en esa lista. Con duplicados nos referimos a las abreviaciones que aparecen varias veces en la lista. La lista aparece como sigue:

Es facil manipular archivos de texto con Perl

AC Access Class
AC Air Conditioning
AFC Automatic Frequency Control
AFS Andrew File System
...

Usted puede descargar la lista aqui . La sintaxis de este archivo es:

Como leer un archivo de texto? Aqui esta algo de codigo perl para leer un texto linea por linea:


....
open(FD,"abb.txt")||die "ERROR: can not read file abb.txt\n";
while(){
   #do something
}
close FD;
....

La funcion open toma un descriptor de archivo como primer argumento y el nombre del archivo a leer como segundo argumento. Los descriptores de archivo son un tipo especial de variables. No es de interes comprender que es realmente un descriptor de archivo. Usted solo coloquelo en la funcion open, uselo en la funcion que lee los datos desde el archivo y finalmente paselo en la funcion close. La lectura del archivo es hecha con <FD>. <FD> puede ser pasado como argumento al ciclo while y esto produce una lectura linea por linea.
Tradicionalmente los descriptores de archivo estan escritos completamente en mayusculas en Perl.

Donde van nuestros datos? Perl tiene un numero de variables implicitas. Estas son variables que usted no declaro. Ellas siempre estan ahi. Una de dichas variables es $_. Esta variable almacena aquella linea leida en el momento por el ciclo while. Intentemoslo (descargar el codigo):

#!/usr/bin/perl
use strict;
my $i=0;
open(FD,"abb.txt")||die "ERROR: can not read file abb.txt\n";
while(<FD>){
   # increment the line counter. You probably
   # know the ++ from C:
   $i++;
   print "Line $i is $_";
}
close FD;
La variable implicita $_ almacena la linea actual.

Tal como puede observar NO escribimos print "Line $i is $_ \n". La variable $_ guarda la linea actual del archivo de texto incluyendo el caracter de nueva linea (\n).

Ahora sabemos como leer el archivo. Para completar nuestro programa necesitamos aprender 2 cosas mas:

  1. Como leer la abreviacion desde el comienzo de la linea.
  2. Como trabajar con tablas de revuelto (hash) de Perl

Las expresiones regulares proveen medios sofisticados para buscar un patron en un cadena de texto. Nosotros buscamos la primera cadena en la linea hasta el primer espacio. En otras palabras nuestro patron es "comienzo de linea-->un numero de caracteres pero sin espacio-->un espacio". En terminos de expresiones regulares de perl esto es ^\S+\s. Si colocamos esto dentro de un m//; entoces perl aplicara esta expresion a la variable $_ (Recuerde: esta variable almacena la linea actual, como aparece). \S+ en la expresion regular corresponde a "un numero de caracters pero sin espacio". Si colocamos parentesis alrededor de \S+ entonces obtenemos los "caracteres sin espacio" de vuelta en la variable $1. Nosotros podemos adicionar esto a nuestro programa:

#!/usr/bin/perl -w
# vim: set sw=8 ts=8 si et:
#
use strict;
# global variables:
use vars qw($opt_h);
my $i=0;
use Getopt::Std;
#
&getopts("h")||die "ERROR: No such option. -h for help.n";
&help if ($opt_h);
#
open(FD,"abb.txt")||die "ERROR: can not read file abb.txt\n";
while(<FD>){
    $i++;
    if (m/^(\S+)\s/){
        # $1 holds now the first word (\S+)
        print "$1 is the abbreviation on line $i\n";
    }else{
        print "Line $i does not start with an abbreviation\n";
    }
}
close FD;
#
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
sub help{
     print "help text\n";
     exit;
}
#-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
__END__

El operador de emparejamiento (m/ /) retorna 1 si le expresion regular podria ser aplicada exitosamente a la linea actual. Por consiguiente podemos usarla dentro de una sentencia if. Deberia usar siempre una sentencia if antes de usar $1 (o $2, $3 ...) para asegurarse de que $1 realmente contiene un dato valido.

 

Tablas de revuelto (Hash Tables)

Ahora que podemos leer el archivo para obtener la abreviacion, falta algun medio para ver si ya leimos esa abreviacion antes. Aqui necesitamos un nuevo tipo de datos de perl: Hash Tables. Las tablas de revuelto (Hash Tables) son arreglos que pueden ser indexados por una cadena. Cuando se refiera a una tabla Hash en el codigo perl escriba un signo % al frente del nombre de la variable. Para leer un valor individual use $nombre_variable{"cadena_indice"}. Usamos el mismo $ como en otras variables escalares ya que un campo dentro de la tabla hash es precisamente una variable escalar normal. Aqui esta un ejemplo:

#!/usr/bin/perl -w
my %htab;
my $index;
# load the hash with data:
$htab{"something"}="value of something";
$htab{"somethingelse"}=42;
# get the data back:
$index="something";
print "%htab at index \"$index\" is $htab{$index}\n";
$index="somethingelse";
print "%htab at index \"$index\" is $htab{$index}\n";

When running this program we get:

%htab at index "something" is value of something
%htab at index "somethingelse" is 42

Now our program is complete:

 1  #!/usr/bin/perl -w
 2  # vim: set sw=4 ts=4 si et:
 3  # 
 4  use strict;
 5  # global variables:
 6  use vars qw($opt_h);
 7  my %htab;
 8  use Getopt::Std;
 9  #
10  &getopts("h")||die "ERROR: No such option. -h for help.n";
11  &help if ($opt_h);
12  #
13  open(FD,"abb.txt")||die "ERROR: can not read file abb.txt\n"; 
14  print "Abbreviations with several meanings in file abb.txt:\n";
15  while(<FD>){ 
16      if (m/^(\S+)\s/){
17          # we use the first word as index to the hash:
18          if ($htab{$1}){
19              # again this abbrev:
20              if ($htab{$1} eq "_repeated_"){
21                  print; # same as print "$_";
22              }else{
23                  # this is the first duplicate we print first
24                  # occurance of this abbreviation:
25                  print $htab{$1};
26                  # print the abbreviation line that we are currently reading:
27                  print;
28                  # mark as repeated (= appears at least twice)
29                  $htab{$1}="_repeated_";
30              }
31          }else{
32              # the first time we load the whole line:
33              $htab{$1}=$_;
34          }
35      }
36  } 
37  close FD; 
38  #
39  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
40  sub help{
41          print "finddup -- Find abbreviations with several meanins in the
42  file abb.txt. The lines in this file must have the format:
43  abrev meaning
44  \n";
45          exit;
46  }
47  #-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
48  __END__ 
Usted puede descargar el programa haciendo click aqui.

Como trabaja? Leemos el archivo linea por linea y almacenamos las lineas en nuestra tabla llamada %htab (linea 33). El indice de la tabla hash es la abreviacion. Antes de que carguemos la tabla comprobamos si ya hay algo almacenado en la tabla (linea 18). Si hay algo en la tabla entonces tenemos 2 posibilidades:

  1. Este es el primer duplicado
  2. Ya tenemos varios duplicados para esta abreviacion
Para diferenciar entre los 2 casos escribimos la cadena "_repeated_" en la tabla hash para indicar que ya hemos encontrado un duplicado en el archivo (linea 29).

Lo mejor es bajar el codigo y probarlo.

Que viene?

En este articulo hemos aprendido algunos detalles del lenguaje perl. Aun no hemos cubierto todos los tipos de datos que tiene perl y usted probablemente desea saber si tambien es posible desarraigar el nombre del archivo "abb.txt" del codigo del programa anterior. Ya sabe como podria usar una opcion para evitarlo (e.g finddup -f abb.txt). Intente cambiar el programa! La forma general de como leer la linea de comandos en perl sera cubierta en el proximo articulo.