original in en Guido Socher en to es Hermán Mauricio Rodrígez
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.
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.
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.
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(!..)
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.
|
#!/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.
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; } |
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:
|
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; |
|
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:
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.
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__ |
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:
Lo mejor es bajar el codigo y probarlo.