Apuntes: Introducción al lenguaje Perl
Éstos apuntes se corresponden con el material de partida
de un curso de introducción al lenguaje Perl que confeccioné
durante el año 2004. Pretenden ser una guía inicial al lenguaje
y están pensados para ser estudiados a través de la
realización
práctica de los ejemplos expuestos. Si bien dichos ejemplos son en su
mayor parte muy elementales, están pensados para poner de manifiesto
las peculiaridades del lenguaje.
Índice
Introducción
Qué es Perl ?
Perl es un lenguaje de programación de propósito general originalmente
enfocado hacia el tratamiento y manipulación de textos. Su nombre se
corresponde con las siglas:
Practical Extraction and
Report Language
A continuación figura la descripción de la página man original
de la versión Perl 1.000 lanzada el 18 de Diciembre de 1987 por Larry Wall :
NAME
perl | Practical Extraction and Report Language
SYNOPSIS
perl [options] filename args
DESCRIPTION
Perl is a interpreted language optimized for scanning arbi-
trary text files, extracting information from those text
files, and printing reports based on that information. It's
also a good language for many system management tasks. The
language is intended to be practical (easy to use, effi-
cient, complete) rather than beautiful (tiny, elegant,
minimal). It combines (in the author's opinion, anyway)
some of the best features of C, sed, awk, and sh, so people
familiar with those languages should have little difficulty
with it. (Language historians will also note some vestiges
of csh, Pascal, and even BASIC|PLUS.) Expression syntax
corresponds quite closely to C expression syntax. If you
have a problem that would ordinarily use sed or awk or sh,
but it exceeds their capabilities or must run a little fas-
ter, and you don't want to write the silly thing in C, then
perl may be for you. There are also translators to turn
your sed and awk scripts into perl scripts. OK, enough
hype.
|
Desde sus inicios, Perl fue concebido como un lenguaje orientado al desarrollo
de herramientas de monitorización, extracción de información y elaboración
de informes;
no obstante, a día de hoy, el uso de Perl se ha extendido del mismo modo
que sus prestaciones para ofrecer la posibilidad de soportar muchos tipos de
aplicaciones destacando por ejemplo:
- Administración de sistemas
- Desarrollo Web
- Programación en Red
Características de Perl
Perl es un lenguaje diseñado para ser
interpretado, es decir, a diferencia de otros lenguajes
como C o Fortran no requiere compilación del código
por parte del usuario para poder ser ejecutado. Así pues,
en la mayor parte de las ocasiones, el usuario utilizará un
fichero de texto en el que almacenará una serie de directivas
(i. e. un script) y a continuación ejecutará dicho script
a través del intérprete de Perl :
La inmediatez que proporciona el esquema de un lenguaje interpretado
junto con las prestaciones que proporciona para tareas como el tratamiento
de cadenas de texto y las comunicaciones por internet,
la flexibilidad que aporta para tratar y estructurar grandes cantidades de información
a través de una sintaxis flexible y elegante
además de la existencia de una comunidad de usuarios muy extendida que
aporta soluciones modulares a través de CPAN (Comprehensive
Perl
Archive Network -> http://www.cpan.org) han hecho de Perl uno
de los lenguajes más atractivos a día de hoy para el desarrollo de todo
tipo de aplicaciones.
Primeros pasos
Para empezar, podemos considerar la creación de nuestro primer script
en Perl, mediante el uso de un editor de textos convencional (Notepad
en Windows, Kwrite en Linux, etc ...) podemos editar un primer
script :
1 #!/usr/bin/perl
2 print "Hola, Mundo !!\n";
|
Una vez guardado dicho script en un fichero con el nombre holamundo.pl
ya podemos ejecutarlo invocando al intérprete del siguente modo:
Con lo que obtendremos el mensaje
impreso en la consola del sistema.
La primera linea del script consiste en un comentario (los comentarios
en perl van precedidos del carácter almohadilla -> #) que no tiene
efectos sobre el flujo de ejecución del programa y en éste caso, indican
al intérprete de comandos del sistema la ruta del intérprete empleado.
La segunda linea es la que contiene el código Perl y consiste en una llamada
a la función print con el argumento entre comillas correspondiente
a la cadena de texto que queremos mostrar por pantalla :
"Hola, Mundo !!\n".
Por último, la linea concluye con el
punto y coma ; que indica al intérprete la conclusión de la directiva.
Fundamentos de Perl
Variables
Las variables en Perl representan espacios
de memoria con un indicador o nombre asociado a través
del cual podemos recuperar sus contenidos para operar con los datos
que dichos espacios contienen. Las variables (escalares) en Perl
se diferencian
de otras piezas de código por ir precedidas del carácter $.
A modo de ejemplo tomemos el siguiente script:
1 #!/usr/bin/perl
2 print "Como te llamas ? ";
3 $nombre = <STDIN>;
4 print "Hola $nombre\n";
|
Las primeras dos lineas son análogas a las del anterior ejemplo holamundo.pl.
En la linea 3, se cita la etiqueta asociada a la entrada por defecto STDIN
(standard input) mediante el operador readline representado por los
carácteres < > y lo que hace es almacenar la cadena de texto introducida
por el usuario a través del teclado en la variable representada como $nombre
dentro del contexto del script mediante el operador de asignación
=. Por último se cita dicha variable en un contexto
apropiado produciéndose un saludo personalizado.
Una de las características particulares de Perl es la existencia de una serie
de variables cuyo contenido está predefinido en función del contexto en el
que se citen y en algunos casos son pasadas como argumentos implícitos
a funciones u operadores concretos.
Valores literales y sustitución
Literales numéricos
En el caso de hacer referencia a valores numéricos, uno puede escoger
entre varias opciones:
# valor entero
$x = 12345;
# valor punto flotante
$x = 12345.67;
# notacion cientifica
$x = 6.02e23;
# uso de guion (legibilidad)
$x = 4_287_283_209;
# valor octal
$x = 0377;
# hexadecimal
$x = 0xfa6b;
# binario
0b1100_0000;
|
Los valores numéricos pueden ser procesados en operaciones
algebraicas comunes como la adición, sustracción, multiplicación y
etc ... y además en caso
de encontrarse en el contexto de una cadena de carácteres se
sustituyen sin más por un valor legible cuyo formato de salida
puede controlarse tal y como se verá más adelante.
Cadenas de texto
En el caso de tratar con cadenas de texto, existen numerosas posibilidades
de inserción dentro del código. Lo más habitual consiste en delimitarlas
mediante comillas normales :
$nombre_y_apellido = "Bart Simpson";
$nombre = "Bart";
$apellido = "Simpson";
$nombre_completo = "$nombre $apellido";
|
De éste modo, los nombres de variables se sustituyen por sus respectivos
contenidos de forma adecuada ... igualmente, los carácteres especiales
de tabulación -> \t, retorno de carro
-> \n y etc ...
también surten su efecto. En caso que exista la necesidad de expresar
un carácter especial como $ dentro de una cadena, podemos hacerlo mediante
el prefijo \ del siguiente modo:
O de forma alternativa, hacer uso de los apóstrofes a modo de delimitadores
que evitan cualquier tipo de sustitución:
También existe la posibilidad de tratar una cadena de texto como un comando
a ejecutar a través del intérprete de comandos correspondiente (DOS,
Bash, Csh, ...). Para ello utilizaremos los delimitadores correspondientes al
acento abierto ...
1 #!/usr/bin/perl
2 $txt = `dir`;
3 print "$txt\n";
|
Así pues, en la segunda linea del ejemplo, se almacena en la
variable $txt la salida del comando
dir que contiene un listado de los archivos y directorios presentes en el
directorio en el que nos encontremos en el momento de ejecutar el script. A continuación
en la linea 3, dicha variable se muestra por pantalla.
En caso de tener que tratar con una cadena larga en la que existan saltos de carro,
tabulaciones, símbolos especiales como los mismos delimitadores ", etc ... podemos
hacer uso de la construcción print <<FINAL; que nos permite mostrar
el contenido de la cadena que sigue hasta el momento en que se encuentra la
la palabra FINAL. A modo de ejemplo podríamos implementar un script
que inserte en un documento HTML el listado de contenidos del directorio actual:
1 #!/usr/bin/perl
2 $txt = `dir`;
3 print <<FINAL_HTML;
4 <html>
5 <head>
6 <title>Listado del directorio actual</title>
7 </head>
8 <body>
9 <p align="center">$txt</p>
10 </body>
11 </html>
12 FINAL_HTML
13
|
Por último, hay que hacer notar que Perl proporcina la posibilidad de
hacer uso de una notación alternativa para los delimitadores más comunes:
| Notación común |
Alternativa |
Significado |
Sustitución |
| Apóstrofes |
q// |
Cadena literal |
No |
| Comillas |
qq// |
Cadena literal |
Si |
| Acentos abiertos |
qx// |
Ejecución |
Si |
Según lo expuesto, podríamos decir que las siguientes construcciones
son equivalentes. Cabe notar que en caso de hacer uso de la notación
alternativa, los delimitadores son personalizables; se pueden usar
indistintamente símbolos cualesquiera @@, (), !!, ?? etc ... :
`dir $directorio_actual`
qx!dir $directorio_actual!
|
Arrays
Si bien, en los ejemplos anteriores, se ha considerado exclusivamente
un tipo de variable escalar capaz de contener un único valor numérico o
cadena de texto cuyo nombre es
precedido por el símbolo $; para representar una
lista ordenada de valores se emplea un segundo tipo de variable denominado
array. Para indicar que una variable es de tipo array se emplea el símbolo
@ a modo de prefijo del mismo modo que el símbolo $
precedía a los nombres de variables escalares.
Para construir un array a partir de valores literales o contenidos de otras
variables se puede emplear la siguiente construcción:
@dias_laborables = ("Mon","Tue","Wed","Thu","Fri");
|
o alternativamente hacer uso de un tipo de delimitador similar a los expuestos
en la sección anterior que se emplea en la construcción de arrays ahorrando
algo de tecleo:
@dias_laborables = qw(Mon Tue Wed Thu Fri);
|
En los dos casos, hemos construido un array que contiene las abreviaturas
de los días de la semana, para poder utilizar dicho array con posterioridad
se emplea la notación referida a continuación:
$primer_dia_laborable = $dias_laborables[0]; # Mon
$tercer_dia_laborable = $dias_laborables[2]; # Wed
|
Hay que hacer notar que para acceder a un elemento de un array se necesita
citar el índice de dicho elemento (el índice debe estar
contenido entre dos corchetes cuadrados []), dicho índice consiste en un valor
entero que empezando desde 0 va hasta el número de elementos
menos uno. Además, puesto que los valores almacenados en el array son
siempre escalares, hay que hacer uso de el prefijo $ en el momento
de requerir un elemento concreto.
Una de las particularidades de Perl a la hora de tratar con arrays es la posibilidad
de utilizar índices negativos :
$ultimo_dia_laborable = $dias_laborables[-1]; # Fri
$penultimo_dia_laborable = $dias_laborables[-2]; # Thu
|
Además existen numerosas funciones capaces de facilitar enormemente
la gestión de arrays en un script, a modo de ejemplo cabría destacar
la función push :
push(@dias_de_la_semana,@dias_laborables);
push(@dias_de_la_semana, qw(Sat Sun) );
|
que acepta como primer argumento un array al que se pretende añadir
al final el(los) elemento(s) del array(escalar) especificado como segundo
argumento.
Es también de gran utilidad la función scalar que devuelve el
número de elementos que contiene un array, o también la función
shift que extrae y devuelve el primer elemento del array.
Hay que destacar que el paso de argumentos desde la linea de comandos
al script se realiza a través de un array predefinido bajo el nombre:
@ARGV (del inglés Argument Values). De éste modo,
si generamos el script newdir.pl :
1 #!/usr/bin/perl
2 $dir = shift(@ARGV);
3 $txt = `dir $dir`;
4 print "$txt\n";
|
Y lo invocamos mediante:
C:\> perl newdir.pl C:\usuarios\
|
Obtendremos en pantalla el listado de contenidos del directorio pasado como
argumento al script.
En Perl, además de funciones específicas para la manipulación de arrays,
existen también funciones cuyo valor de retorno es un array. Por ejemplo
podríamos citar la función split cuyo primer argumento representa
un carácter o combinación de carácteres mediante los cuales se requiere
el troceado de una
cadena de texto para posteriormente devolver la lista o array asociada al contenido
diseccionado:
1 #!/usr/bin/perl
2 @dir = split(/\s+/,`dir`);
3 print "$dir[3]\n";
|
En el ejemplo, se puede apreciar como en la segunda linea se invoca a la función
split haciendo uso de la construcción /\s+/ como primer
argumento para hacer constar que nos interesa trocear todas aquellas partes
de la cadena que estén separadas por uno o más (modificador
+)
espacios en blanco (notado \s). La cadena a considerar se
recoge como segundo argumento y sería la salida correspondiente al
comando del sistema dir. Por último, se muestra por pantalla el nombre
del cuarto
archivo contenido en el directorio.
La función join podría considerarse como la inversa de split pues
lo que hace es devolver una cadena escalar que contiene el conjunto
de términos de un array separados por una cadena de texto especificable:
de éste modo obtendríamos un escalar $dir que albergaría
una cadena de texto en la figurarían
los valores contenidos en @dir separados
por comas.
Llegados a este punto y aprovechando el ejemplo anterior, convendría
mencionar el hecho de que una variable de un tipo (p. ej. escalar)
y otra de otro tipo (p. ej. array) pueden tener el mismo nombre
sin que entre ellas se produzca interferencia alguna. Dicho de otro modo,
la variable escalar $dir y el array @dir ocupan
lugares independientes
y separados en la tabla de símbolos que el intérprete utiliza
para procesar las directivas.
Hashes
Los hashes también conocidos como
arrays asociativos constituyen el tercer tipo fundamental de variables con las que
Perl puede trabajar. Del mismo modo que un array, un hash es una variable
susceptible de contener toda una colección de valores. La diferencia con
respecto a un array radica en el modo de indexar sus elementos; es decir,
en ocasiones, puede ser interesante prescindir de una indexación numérica
en detrimento de una indexación basada en etiquetas constituidas por cadenas
de carácteres; el hash es el tipo de variable que nos
proporciona ese tipo de indexación. Para hacernos una idea consideremos el
ejemplo de asignación de un hash:
%color_de = qw(manzana rojo pera verde);
|
Hasta aquí, la única diferencia apreciable con respecto de los ejemplos
en los que construiamos arrays a partir de valores literales consiste en
que un hash tiene un prefijo %
en contraposición con los arrays
(@) o los escalares ($).
|
Es interesante recalcar un detalle que se pone de manifiesto a través
del ejemplo y que consiste en el hecho de que la parte derecha de la asignación
es idéntica a la utilizada en ejemplos anteriores para la instanciación de un
array; no obstante y dado que la parte izquierda contiene el nombre de un
hash, el intérprete construye un hash puesto que Perl es extremadamente
sensible al contexto en el que se situan determinadas expresiones o piezas
de código. Dicha dependencia del contexto contribuye facilitar el desarrollo
de un estilo de programación más compacto y que hace de Perl un lenguaje
de programación
mucho más próximo y potente acercándolo al lenguaje común que empleamos
para comunicarnos de forma cotidiana a su vez tremendamente proteico y sensible al contexto en el que
se utiliza; más adelante, se expondrá con más detalle ésta característica.
|
No obstante, el modo en que Perl
nos da acceso a los elementos de un hash es totalmente distinto. Para
apreciarlo basta el siguiente ejemplo:
$fruta = "manzana";
print qq@El color de una $fruta es $color_de{$fruta}\n@;
$fruta = "pera";
print qq@El color de una $fruta es $color_de{$fruta}\n@;
|
La salida por pantalla del script resultaría ser:
C:\>perl color.pl
El color de una manzana es rojo
El color de una pera es verde
|
Así pues, parece que los literales manzana y pera
especificados en la asignación del hash han sido considerados
como las etiquetas de indexado (de ahora en adelante keys o claves del hash
enmarcadas por corchetes {} en lugar de corchetes cuadrados
[] como en el caso de los índices de un array)
asociadas a los elementos del hash rojo y verde (valores
del hash). Con objeto de diferenciar claramente etiquetas de valores
en la instanciación de un hash, se suele emplear el operador => que
podría considerarse como un sinónimo de la coma en construcciones
del estilo de:
%color_de = ("manzana" => "rojo","pera" => "verde");
|
Al igual que en el caso de los arrays, existen diversas funciones incorporadas
a Perl que operan con hashes. Por ejemplo podríamos considerar la
función keys que devuelve un array que contiene el conjunto de
claves del hash:
@frutas = keys %color_de;
|
del mismo modo, podemos recuperar los valores de un hash mediante la
función values:
@colores = values %color_de;
|
Hay que destacar que el orden en el que se disponen
los elementos del array resultante
en uno u otro ejemplo no tiene por que coincidir con
el orden explicitado en la construcción
del hash. Dependiendo de la implementación concreta de
Perl que se esté utilizando,
el orden puede variar en función del tipo de algoritmos de recuperación
de valores o claves empleado; para garantizar la integridad a tal efecto y
evitarnos sorpresas desagradables, lo más apropiado suele ser recurrir
a una función que opera sobre arrays llamada sort que proporciona
un array con los mismos elementos que el original reordenados alfabéticamente,
en éste caso:
@frutas = sort keys %color_de;
|
Al igual que en el caso del array predefinido @ARGV, también
existe por ejemplo un hash predefinido llamado %ENV
(del inglés environment) en el que
se albergan las variables de entorno del sistema en el momento de ejecutarse
el script:
1 #!/usr/bin/perl
2 print "USUARIO : $ENV{USER}\n";
3 print "HOSTNAME : $ENV{HOSTNAME}\n";
4 print "SHELL : $ENV{SHELL}\n";
5 print "TERM : $ENV{TERM}\n";
6 print "PWD : $ENV{PWD}\n";
7 print "HTTP_PROXY : $ENV{HTTP_PROXY}\n";
|
El script anterior produciría una salida como la que sigue:
C:\>perl muestra_entorno.pl
USUARIO : pepito
HOSTNAME : isingx.isabenax.es
SHELL : /bin/bash
TERM : xterm
PWD : /home/pepito/cursos/perl/tex/ejemplos
HTTP_PROXY : http://pepito:correo@proxy.isabenax.es:8080
|
Operadores
Precedencia, asociatividad, etc ...
En Perl, existe un gran número de operadores distintos que proporcionan
un grado de flexibilidad considerable a la hora de implementar cualquier conjunto
de directivas. Dichos operadores se pueden clasificar en función del número
de elementos sobre los que operan: unarios, binarios, trinarios, ...
además del tipo de elementos sobre los que actuan: cadenas,
valores numéricos, referencias, ... o su precedencia y también asociatividad
relacionada con el orden en el que evalua sus operandos: p. ej. primero
el de la izda. o viceversa, etc ...
De hecho, un operador puede ser considerado como una función del lenguaje
que tiene una expresión sintáctica especial. En realidad, podemos considerar
como operadores, aquellas funciones que llamamos sin hacer uso de paréntesis
para enmarcar los argumentos, por ejemplo:
1 #!/usr/bin/perl
2 chdir 'C:\usuarios\' || die 'Error de acceso a C:\usuarios\';
3 $txt = `dir`;
4 print "$txt";
|
En éste caso, hacemos uso de la instrucción chdir a modo
de operador que sirve para
ubicarnos en un directorio concreto; la expresión chdir TERMINO
cambia el directorio en el que trabaja el script y devuelve un 1 en
caso de que la operación se complete con éxito y un 0 en caso
contrario; igualmente
podríamos realizar la llamada
del siguiente modo:
chdir('C:\usuarios\') || die 'Error de acceso a C:\usuarios\';
|
En ambos casos, encontramos el operador || que representa a la
operación lógica OR y según lo expuesto la directiva podría
leerse del siguiente modo:
- i) La directiva, es una operación lógica OR || entre dos
términos
- ii) Puesto que la asociatividad de || es tal que el primer término
en ser evaluado es el de la izda. se procesa la pieza chdir que
efectua la tentativa de cambio de directorio actual. En caso de éxito,
devuelve 1 resolviendo la operación lógica y concluyendo
el proceso.
- iii) En caso de que la operación fallase debido a la no existencia
del directorio o a la falta de permisos del usuario, la pieza chdir
devolvería un 0 y esta situación requeriría la evaluación
del operando a la derecha de la función lógica. Dicho operando es una
instrucción que detiene la ejecución del programa i.e. die
mostrando un mensaje a modo de justificación.
La diferencia entre las dos formas de expresar la directiva consisten en que
en el segundo caso, hacemos explícito el hecho de que nuestro propósito
es que el intérprete lea la directiva en el orden adecuado; en cambio, en el
primer caso, no explicitamos ningún tipo de orden pues las reglas de
precedencia de los operadores || y chdir establecen un orden implícito
que en éste caso coincide con nuestras pretensiones (ver Figuras
adjuntas)
 |
 |
|
Orden de evaluación implícito basado en las reglas de precedencia
|
Orden incorrecto de evaluación de la expresión
|
Dado que en Perl, existe una gran cantidad de operadores con muy diversas funciones,
también existe una gran colección de reglas de precedencia y asociatividad;
normalmente dichas reglas suelen encajar con el sentido común de la mayoría
de los mortales y además en muchos casos se establecen en función de la
comodidad de uso. De todos modos, en caso de dudas con respecto a la precedencia
de tal o cual operador, suele ser aconsejable consultar las especificaciones
y en cualquier caso, forzar la precedencia deseada mediante el uso de los paréntesis.
Operadores de uso común
A continuación, se recopila una serie de descripciones de operadores de
uso cotidiano junto con ejemplos relacionados.
|
En lo que sigue se ha obviado
la descripción
de operadores matemáticos presentes en Perl como *, +, -, \% y etc ...
puesto que su uso es muy generalizado en aplicaciones como Excel y lenguajes
como C, Fortran o Java
|
- Auto(incremento - decremento) -> Se expresan mediante ++
o -- respectivamente, y tienen el efecto de incrementar o decrementar
en una unidad el resultado de la expresion a la que se aplican; dependiendo
de si se emplean a modo de prefijo o sufijo, tienen efectos diferentes;
print ++($numero = 99); # imprime 100
print $numero++; # imprime 99, fija a 100 $numero
|
- Operador multiplicativo x -> A diferencia de la multiplicación
algebraica *, x debe ser entendido como un operador de
repetición mediante el cual podemos mostrar 80 guiones o también
anular todos los elementos de un array :
print "-" x 80;
@array = (0) x scalar @array;
|
- Operador aditivo . -> Dicho operador sirve para
concatenar distintas expresiones:
$var = "Bart "."Simpson"; # se asigna a $var "Bart Simpson"
|
- Condicional trinario ? : -> Dicho operador
necesita tres términos para ser procesado, el primero (a la izquierda del
interrogante) representa una condición que puede o no satisfacerse. En caso
satisfactorio se procesa la pieza de código que precede a los dos puntos; en
caso contrario, se procesa el término situado a la derecha de los dos puntos:
(1 > 2) ? print "Imposible !!" : print "Falso, 2 > 1 !!!";
|
- Testeo de ficheros -> Perl nos proporciona más
de 20 operadores para recuperar información acerca de ficheros o directorios
presentes en nuestro sistema. De entre ellos, cabe destacar el operador
-e que devuelve 1 en caso de que el fichero especificado por la
expresión que le siga exista, 0 en caso contrario:
-e $fichero ? unlink $fichero : ();
|
De éste modo, eliminamos el fichero apuntado por la cadena contenida
en la variable $fichero y en caso de que no exista no efectuamos
ninguna operación emplazando un término vacío () cómo último operando.
Para comprobar si un fichero es un directorio podemos utilizar el operador
-d tal y como se ilustra en el siguiente ejemplo:
(-e $dir) && (-d $dir) ? chdir $dir : ();
|
En éste caso se utiliza el operador lógico AND expresado mediante
&& para testear la validez del directorio
apuntado por $dir antes
de proceder al cambio de directorio. También se suelen utilizar de forma asidua
los operadores -M y -A que devuelven respectivamente el
número de días que han transcurrido desde la última modificación
o acceso a un fichero concreto y el tiempo de inicio de interpretación del
script:
print "$file fue modificado hace ".(-M $file)." dias\n";
|
- Operadores comparativos -> Se emplean
para efectuar comparaciones entre sus operandos, tenemos entre ellos
el operador == que devuelve 1 en caso de que se comparen
dos valores numéricos iguales y 0 en caso contrario. El operador
!= que consistiría en testeo de desigualdad numérica:
$a != 0 ? $b = 100.0/$a : print "Division por 0 !!\n";
|
En el caso de cadenas de texto, los operadores análogos a ==
y != se representan mediante eq (equal) y ne
(not equal).
- Operador rango .. -> Se utiliza para construir
un rango de valores:
@ultimos_cinco_colores = @colores[-5..-1];
@alfabeto = ('A'..'Z');
|
- Operadores de asignación -> A modo de ejemplo
utilizado extensivamente hasta el momento podríamos citar el operador
=, también podemos emplear algunas variantes como
+=, .=, ...
$numero = 100.0;
$numero += 50.0; # $numero contiene 150.0
$cadena = "El precio es de ";
$cadena .= $numero;
$cadena .= " $"; # El precio es de 150.0 $
|
Contextos
Tal y como se ha reflejado en alguno de los ejemplos expuestos hasta el momento,
la forma que tiene el intérprete de Perl de procesar una determinada pieza de
código depende en ocasiones del contexto en el que se encuentre.
Contexto escalar y contexto de lista
Constituyen los principales contextos en los que se evaluan la mayoría
de expresiones en Perl. En el caso de que por ejempo requiramos una asignación
situando a la izquierda un escalar tal y como una variable escalar, un elemento
concreto de un array o un hash, el intérprete atribuye automáticamente
un contexto escalar a la parte derecha de la asignación:
$x = funcion();
$x[1] = funcion();
$x{"manzana"} = funcion();
|
En caso de que en la parte izda. de la asignación figure un array, hash, etc ...
la parte dcha. se evaluará en un contexto de lista:
Igualmente, podemos atribuir una interpretación en contexto de lista a asignaciones
del tipo:
($x,$y,$z) = funcion();
($x) = funcion("X");
|
En los ejemplos se ha empleado una función hipotética denominada funcion
que es capaz de saber en que contexto se está evaluando devolviendo un escalar
o una lista en consecuencia. Se puede decir que la función funcion() está
sobrecargada en el tipo de retorno. Del mismo modo que las combinaciones
del operador de asignación citadas pueden proveer de diferentes contextos
a sus operandos, cualquier operador en Perl proporciona un determinado contexto
en función del modo en que se utilize. Normalmente dicha contextualización
se establece de forma bastante intuitiva pero en caso de duda siempre resulta
recomendable consultar las especificaciones.
Contexto booleano
En algunos casos, existen términos que se evaluan en lo que se conoce como
contexto booleano, es decir en aquellos casos en los que se requiera un valor
falso o verdadero. En caso de que un escalar sea evaluado en dicho contexto
existen varias posibilidades:
| | Verdadero | Falso |
| Escalar numérico | ≠ 0 | = 0 |
| Cadena de texto | ne '' | eq '' |
Para el caso de los arrays no existe un contexto booleano tal como el de los
escalares, no obstante, podemos emplear un array en una decisión tal y como
sigue:
(@array) ? print "$array[0]\n" : print "Array vacio\n";
|
Puesto que dado que @array está siendo evaluado en contexto escalar,
Perl interpreta el término como un escalar y en éste caso se produce una
llamada implícita a la función scalar que proporciona el número
de elementos de un array
(Cualquier array @array
citado en contexto escalar
es equivalente a la expresión scalar @array proporcionándonos un esquema
bastante afortunado):
| | Verdadero | Falso |
| Array | No vacío | Vacío |
Variables predefinidas
A continuación se va a describir brevemente la función de algunas
de las variables que Perl utiliza por defecto para realizar determinadas
tareas. En la página perlvar del manual, se pueden consultar
de un modo más exhaustivo todas las variables obviadas en ésta sección.
Escalares
- $_ -> Se corresponde con la variable de entrada por defecto
y en caso de omisión, es la que se pasa como argumento a las funciones
print, chomp, etc ..
- $a, $b -> Se emplean para adecuar los criterios de
la función sort y representan dos términos de una lista a ordenar
entre los cuales se establece el criterio de ordenación.
- $. En ella encontramos el número de linea en el que se encuentra el
proceso de lectura del último fichero accedido.
- $/ -> En ella se almacena una cadena de carácteres
a considerar como separador
de lineas en operaciones de lectura, por defecto se fija a \n.
- $¡ -> Si se fija a un valor distinto de cero, fuerza las operaciones
de escritura a producirse en el momento de procesamiento de las directivas
correspondientes eliminando el paso de lineas por el buffer de salida. Suele
ser de utilidad para establecer comunicaciones entre procesos a través
de sockets o pipes.
- $$ -> Nos proporciona el PID del intérprete que ejecuta nuestro
script. Dicho valor suele ser de sólo lectura aunque puede ser alterado
mediante la instrucción fork().
- $0 -> Contiene el nombre del script en ejecución
- $] -> Contiene información acerca de la versión de la distribución
de Perl empleada para ejecutar el script.
Arrays y Hashes
Tal y como fueron descritas con anterioridad, las variables @ARGV y
%ENV contienen respectivamente los argumentos pasados al script
mediante la linea de comandos y la tabla de variables de entorno vigente
en el momento de ejecutar el script.
Entrada y salida
Entrada y salida STD
Para empezar a tratar con el tema de la entrada y salida en Perl, resulta
conveniente destacar que cualquier operación de ésta índole
en Perl hace referencia implícita o explícitamente a una etiqueta
de fichero también conocida como FILEHANDLE. Para evitar
confusiones con etiquetas de fichero y otras variables o instrucciones,
se suele emplear el convenio de destacar en mayúsculas cualquier
filehandle.
Existen tres filehandles que podemos usar por defecto sin necesidad de realizar
ninguna operación de apertura. éstos son el STDIN para entrada
por teclado, STDOUT para salida por pantalla y el STDERR
para salida por pantalla excepcional debido a un error, etc ...
1 #!/usr/bin/perl
2 $nombre = <STDIN>;
3 chomp($nombre);
4 $nombre ?
print STDOUT "Hola $nombre\n" : print STDERR "Error !\n";
|
Así pues, en el ejemplo se puede apreciar como se hace uso del operador
<> readline sobre la etiqueta correspondiente a la entrada por
teclado STDIN y por lo tanto el script espera a que el usuario inserte
su nombre. A continuación la instrucción chomp recorta el retorno
de carro introducido por el usuario y toma la decisión de saludar al usuario
por la salida de pantalla STDOUT
en caso de que haya escrito una cadena no vacía o notificar un error
por la salida de pantalla excepcional STDERR en caso contrario.
En cualquier caso, podríamos haber obviado las etiquetas STDIN y
STDOUT puesto que se consideran entradas y salidas por defecto
y haber escrito $nombre = <>; ó print $nombre."\n";
Lectura y escritura de ficheros
La instrucciones open y close nos permiten abrir y cerrar ficheros
con el propósito de leer o escribir
datos. Su sintaxis es la que se ilustra a continuación:
1 #!/usr/bin/perl
2 open(OUT,">archivo.txt"); # prefijo > (creacion y escritura)
3 print OUT "Esta es la primera linea de archivo.txt\n";
4 print OUT "Esta es la segunda linea de archivo.txt\n";
5 close(OUT);
6 open(TXT,"archivo.txt"); # apertura en modo de lectura
7 $primera_Linea = <TXT>;
8 chomp($primera_Linea);
9 print $primera_Linea."\n";
10 $segunda_Linea = <TXT>;
11 close(TXT);
|
En la segunda linea se requiere
la apertura de un fichero para operaciones de escritura (notar el prefijo >)
llamado archivo.txt. El hecho de hacer constar dicho prefijo
inmediatamente antes del nombre del fichero indica que en caso de
que el fichero exista, éste será eliminado y creado de nuevo. En la
sexta linea se abre el archivo previamente creado sin incluir ningún
prefijo y por lo tanto Perl entiende que deseamos realizar operaciones
de lectura (modo de apertura por defecto).
En el ejemplo también
se hace uso del operador readline <> que usado en
contexto escalar devuelve sucesivamente cada una de las lineas del fichero
a medida en que se invoca. En contexto de lista, el operador <> devuelve
la totalidad de lineas del fichero en forma de array:
1 #!/usr/bin/perl
2 $archivo = "ejemplos/holamundo.pl";
3 open(TEX,$archivo) || die "No puedo abrir $archivo\n";
4 @array = <TEX>;
5 close(TEX);
6 @array ? print join("",@array)."\n" : die "$archivo vacio\n";
|
Existen otras formas de abrir ficheros como por ejemplo en modo append
que se utiliza para escribir lineas al final de un fichero ya existente sin eliminar
su contenido anterior. Dicho modo de escritura se requiere
mediante el prefijo >> antepuesto al nombre del fichero.
Del mismo modo que podemos realizar operaciones de apertura de ficheros,
también podemos abrir procesos a modo de pipes, por ejemplo,
podemos abrir una aplicación cualquiera para interactuar con ella:
1 #!/usr/bin/perl
2 open(OCT,"|octave");
3 print OCT "2+3";
4 close(OCT);
|
En éste caso, abrimos la aplicación octave (Aplicación GNU
similar a Matlab) mediante el prefijo pipe indicando de ese modo que
nuestro propósito no es abrir un archivo llamado octave sinó comunicarnos
con una instancia de la aplicación octave. A continuación imprimimos
en la consola de la aplicación la operación requerida (en éste caso
2+3). octave la procesa y obtenemos la siguiente respuesta por pantalla:
[alfonso@isingx tex]$ perl ejemplos/pipa.pl
GNU Octave, version 2.1.40 (i386-redhat-linux-gnu).
Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002 John W. Eaton.
This is free software; see the source code for copying conditions.
There is ABSOLUTELY NO WARRANTY; not even for MERCHANTIBILITY or
FITNESS FOR A PARTICULAR PURPOSE. For details, type `warranty'.
Please contribute if you find this software useful.
For more information, visit http://www.octave.org/help-wanted.html
Report bugs to <bug-octave@bevo.che.wisc.edu>.
ans = 5
|
Tal y como se puede apreciar, en la última linea de la salida obtenemos
la respuesta (answer) = 5.
Expresiones regulares
La gestión de expresiones regulares en Perl
constituye una de las más potentes herramientas para el tratamiento
y la gestión de cadenas de texto más o menos estructuradas. En ésta
sección se expone una introducción a su modo de uso junto con
algunos ejemplos que permitan anticipar el tremendo potencial que
proporcionan.
Coincidencia simple de palabras
Una cadena de carácteres literal podría considerarse como la expresión
regular más simple:
"Hola Mundo" =~ m/Mundo/; # Coincidencia !!
|
La novedad del ejemplo consiste en el uso del operador ~
que se emplea para relacionar una expresión con una expresión
regular o regex. En este caso, la pieza de código retornaría
un valor cierto en caso de que la expresión a la izda. del operador
contuviera en algún punto la secuencia expresada literalmente en
la regex. La letra m que precede al delimitador / procede
del inglés match -> coincide ...
$cadena !~ m/Mundo/ ? () : print "Coincidencia\n";
|
En éste caso se ha empleado el operador !~ que constituye el
complementario del anterior en un condicional en el que se imprime
un mensaje en caso de que exista coincidencia entre el contenido
de la cadena de carácteres $cadena y la
palabra Mundo.
Dada la naturaleza reservada de los carácteres:
{} [] () ^ $ . │ * + ? \
|
Necesitaremos la presencia de un backslash \ precediéndolos
para poder hacerlos constar de forma explícita dentro de una regex. Por otro
lado, carácteres ASCII especiales pueden explicitarse haciendo uso de
secuencias comunes como:
Bytes arbitrarios pueden ser especificados mediante por secuencias octales
o hexadecimales:
"cat" =~ m/\143\x61\x74/ # Coincide !!!!
|
Hay que recalcar que dentro de una regex, podemos incluir nombres
de variables pues Perl interpreta su contenido de un modo similar al
de las expresiones enmarcadas entre comillas.
Uso de clases de carácteres
El uso de clases de carácteres permite incluir en una regex un conjunto
determinado de combinaciones:
"cata" =~ m/[bcr]ata/; # Coincide
"bata" =~ m/[bcr]ata/; # Coincide
"rata" =~ m/[bcr]ata/; # Coincide
m/[yY][eE][sS]/; # Coincide con yes, YES, yES y etc ...
|
Tal y como se puede apreciar en el último ejemplo, en muchas ocasiones
convendrá que una regex se evalue sin distinguir letras mayúsculas de
minúsculas, a tal efecto, se puede posponer un modificador i
-> (del inglés case insensitive) después del último delimitador:
Los carácteres reservados de una clase de carácteres son distintos
de aquellos que se emplean fuera y son los siguientes:
El carácter $-$ actua como un operador de rango de modo que:
[0123456789] = [0-9]
[abc........xyz] = [a-z]
[0-9a-fA-F] -> numero hexadecimal
|
Si al principio de la clase de carácteres emplazamos un acento circunflejo,
obtenemos el efecto de negar la clase de carácteres, es decir, coinciden
todos los carácteres exceptuando los contenidos en la clase:
m/[^a]ata/; # Coincidencia con bata pero no con aata o ata
|
Perl proporciona algunas abreviaciones para clases de carácteres de uso
común:
\d = [0-9]
\s = [\ \t\r\n\f]
\w = [0-9a-zA-Z]
\D = [^\d]
\W = [^\w]
. = [^\n]
|
Las abreviaciones \d, \s, \w,
\D, \S y \W pueden ser empleadas
dentro de una clase de carácteres:
m/\d\d:\d\d:\d\d/; # Coincide con el formato horario hh:mm:ss
m/fin\./; # Coincide con fin.
m/fin[.]/; # Lo mismo.
|
La secuencia \b -> word boundary coincide en
la posición en la que exista un carácter alfanumérico precedido
o seguido de uno que no lo es:
$x =~ m/\bcata\b/i; # Ni con catadura ni con bocata
|
Por último hay que destacar que el acento circunflejo expresado al inicio
de una regex y fuera de una clase de carácteres [ ], representa el inicio
de la cadena y el metacarácter $ su final:
/^Erase\suna\svez/;
/comieron\sperdices$/;
|
Alternativas
Existe un metacarácter de alternación (pipe) que se puede utilizar
para forzar coincidencias entre varias alternativas posibles:
"perros y gatos" =~ m/gato|perro|pato/; # Coincide con perro
|
Tal y como se puede apreciar en el ejemplo, la coincidencia se produce
con perro pues es la primera secuencia coincidente
que aparece en la cadena literal a la derecha del operador.
Agrupación
Los paréntesis se pueden emplear para agrupar un conjunto de elementos
dentro de una regex:
$palabra =~ m/cas(ero|a|ita|eta)/; # Casero, casa, casita o caseta
|
Extracción
Los metacarácteres de agrupación (i. e. paréntesis) permiten
la extracción de la cadena coincidente con su contenido. Los valores
obtenidos por orden de derecha a izquierda dentro de una regex, se pueden
recuperar mediante las variables especiales $1, $2, $3 y etc ...
$tiempo = "17:20:18";
$tiempo =~ m/(\d\d):(\d\d):(\d\d)/;
($hora, $minuto, $segundo) = ($1, $2, $3);
|
En contexto de lista, una regex coincidente con agrupaciones, devolverá
la lista de los valores coincidentes:
$t = "17:21:16";
($hora, $minuto, $segundo) = ($t =~ m/(\d\d):(\d\d):(\d\d)/);
|
Los agrupaciones en una regex se pueden anidar, de modo que el contenido
de $1 se corresponderá con la coincidencia del primer paréntesis
abierto por la izda. en el caso de $2 trataremos con el contenido del
segundo paréntesis abierto igualmente por la izda. y así sucesivamente
tal y como se ilustra a continuación:
m/(ab(cd|ef)((gi)|j))/;
1 2 34
|
Dentro de una regex se pueden recuperar variables extraidas previamente
mediante el uso de las referencias (backreferences) \1,
\2, ... de modo que:
Coincidiría con cadenas en las que existan secuencias de tres carácteres
alfanuméricos idénticos y dispuestos en el mismo orden separados por un espacio.
Iteradores
En muchos casos, nos interesa cuantificar el número de veces en el
que se debe producir una secuencia para coincidir dentro de una regex.
A tal efecto deben emplearse los metacarácteres:
Su uso se ilustra a continuación:
- a? -> coincide con 'a' 1 o 0 veces
- a* -> coincide con 'a' 0 o más veces
(Cualquier número de veces)
- a+ -> coincide con 'a' 1 o más veces
(Al menos una vez)
- a{n,m} -> coincide con 'a' entre n y m veces
- a{n,} -> coincide con 'a' un mínimo de n veces
- a{n} -> coincide con 'a' repetida n veces
Modificadores
Hasta ahora hemos visto el modificador //i que se utiliza para establecer
coindencias case insensitive. Existen otros modificadores como //o
que realizan tan sólo una vez las sustituciones de variables en una
regex.
Se trata de una optimización pues antes de procesar la regex,
el intérprete de Perl llama a un compilador de expresiones regulares. En caso
de que una regex permanezca invariable en varias
llamadas sucesivas; haciendo uso del modificador //o
evitamos la utilización innecesaria de dicho compilador.
El modificador //g permite realizar una búsqueda de coincidencias
a lo largo de una cadena pues coincide tantas veces cómo sea posible a lo
largo de una cadena. En contexto escalar se realiza un salto a la siguiente coincidencia
para cada operador =$sim$ empleado. La posición de la cadena
coincidente en cada caso se puede obtener gracias a la función pos():
$x = "gato perro caseta";
$x =~ m/(\w+)/g;
print "La palabra $1, termina en la pos.",pos $x,"\n";
$x =~ m/(\w+)/g;
print "La palabra $1, termina en la pos.",pos $x,"\n";
$x =~ m/(\w+)/g;
print "La palabra $1, termina en la pos.",pos $x,"\n";
|
La salida del ejemplo resultaría como sigue:
La palabra gato, termina en la pos.4
La palabra perro, termina en la pos.10
La palabra caseta, termina en la pos.17
|
En caso de que la regex con el modificador //g se evalue en un contexto de
lista, se devuelve la lista de secuencias coincidentes del siguiente modo:
$x = "gato perro caseta";
@x = ( $x =~ m/(\w+)/g );
print join(",",@x)."\n"; # imprime : gato,perro,caseta
|
Ejemplo -> Un parser de URLs
Gracias a lo expuesto hasta el momento, podríamos considerar el ejemplo
de un parser de URLs ... una URL es más o menos una dirección
de internet, como por ejemplo :
http://www.google.com/webmasters/sitemaps, se trata de una
cadena de texto en la que uno especifica un protocolo de comunicaciones,
en éste caso el HyperText Transfer Protocol -> http,
el nombre de un host (en éste caso el de Google) y además
la ruta hacia el contenido que se desea explorar: para el ejemplo se trataría
de
(/webmasters/sitemaps). Cualquier navegador como por
ejemplo Internet Explorer o Mozilla lo primero que debe hacer para poder
recuperar los contenidos de la página, es decodificar la URL que el usuario
introduce en la casilla a tal efecto. Dicha operación en Perl podría
resumirse en una sóla linea:
1 #!/usr/bin/perl
2 $URL = <STDIN>;
3 print "\n$URL";
4 ($protocolo,$servidor,$puerto,$path) =
5 $URL =~ m@(\w+)*://([^/:]+):*(\d*)?([^#]*)@;
6 $protocolo ?
7 print "Protocolo = $protocolo\n" : ();
8 $servidor ?
9 print "Servidor = $servidor\n" : ();
10 $puerto ?
11 print "Puerto = $puerto\n" : ();
12 $path ?
13 print "Path = $path\n" : ();
|
En éste caso, la expresión regular se encuentra en la linea 5 y puede
ser un buen ejercicio tratar de leerla teniendo en cuenta todo lo expuesto
hasta ahora. A modo de ejemplos de salida del script podemos considerar:
http://www.ub.es/fis/ecm/asignaturas.html
Protocolo = http
Servidor = www.ub.es
Path = /fis/ecm/asignaturas.html
http://www.yahoo.es/deportes/index.html
Protocolo = http
Servidor = www.yahoo.es
Path = /deportes/index.html
ftp://ftp.upc.es:80
Protocolo = ftp
Servidor = ftp.upc.es
Puerto = 80
|
Búsqueda y sustitución
Hasta ahora, todas nuestras expresiones regulares venían acompañadas
del prefijo m//. Existe otro modo de uso de una regex con el objeto de
conseguir sustituciones de palabras, consiste en utilizar el prefijo s///
(s -> substitution) del siguiente modo:
$guion =
"Vilma y Pedro Picapiedra viven una crisis matrimonial ...";
$guion =~ s/Picapiedra/Simpson/gi;
$guion =~ s/Pedro/Homer/gi;
$guion =~ s/Vilma/Marge/gi;
print "$guion\n";
|
produciéndose la salida:
Marge y Homer Simpson viven una crisis matrimonial ...
|
Una manera fácil de hacer remakes ?
El operador split
El operador split citado en la sección que hacía referencia a los
arrays toma como primer argumento una regex, así pues, por ejemplo
podemos servirnos de él para separar la siguiente lista de números separados
por comas:
$linea = "1.618,27, 3.142";
@linea = split /,\s*/, $linea;
print "@linea\n"; # imprime 1.618 27 3.142
|
Directivas de Control del flujo y Visibilidad
Hasta ahora no se ha descrito la forma en que podemos regular el
flujo de ejecución de un script orientado a la toma de decisiones que
implique un bloque de código, ni a la forma de efectuar operaciones
repetitivas y/o secuenciales y tampoco se ha tratado el tema de la visibilidad
o scoping de las variables. El objeto de ésta sección es el
de familiarizarnos con éstos tópicos desde un perspectiva Perl.
Condiciones
Con anterioridad se ha descrito el modo de empleo del operador condicional
trinario A ? B : C gracias a él podemos derivar el flujo de ejecución
de un script entre dos directivas alternativas B,C en función del cumplimiento
de la condición A. En caso de que las
alternativas B,C estén
formadas por un conjunto más o menos complejo de directivas
(i.e. bloque de código) es necesario recurrir a las directivas
if - else - unless:
if ( (-e $dir) && (-d $dir) ) {
chdir $dir;
} else {
unless (-e $dir) {
print STDERR "El directorio $dir no existe\n";
} else {
print STDERR "$dir no es un directorio\n";
}
}
|
Bucles
A continuación se recogen instrucciones relacionadas con el control
de flujo de operaciones
iterativas.
while - until
Se emplean para realizar operaciones iterativas siempre y cuando se cumpla
la condición explicitada en la directiva while; unless funciona de modo
similar pero en éste caso la iteración se produce hasta que la condición
expresada se cumple:
1 #!/usr/bin/perl
2 @malsonantes = qw(porras corcholis mecachis);
3 $malsonantes = join("|",@malsonantes);
4 while ( chomp( $texto = <STDIN>) ) {
5 ( @malsonantes = $texto =~ m/($malsonantes)/gi ) ?
6 print "Es feo decir \"@malsonantes\"\n" : ();
7 }
|
for
El bucle for como casi todo en Perl, puede emplearse de distintos modos,
por ejemplo, podemos iterar sobre los elementos de un array
de modo sucesivo mediante el operador rango ..:
for $letra (A .. Z) {
print "$letra ";
}
|
También podemos utilizarlo del modo en que tradicionalmente se escribe
en C:
@letra = (A .. Z);
for($letra=0;$letra<=$#letra;$letra++) {
print "$letra[$letra] ";
}
|
foreach
Dicho bucle nos permite iterar igualmente sobre un array del mismo modo que
procede la directiva de control for (de hecho son sinónimos para
el intérprete):
foreach (<*.pl>) {
system("copy $_ ${_}.bkup");
print "Backup de Script : $_ -> ${_}.bkup\n";
}
|
En el ejemplo se ha utilizado un atajo muy frecuente en Perl que consiste
en utilizar el operador <*.pl> para recuperar el array correspondiente a todos
los nombres de fichero presentes en el directorio de trabajo (*) que tengan la
extensión pl para realizar una operación de backup sencilla. Hay que notar
igualmente que al no intercalarse ningún nombre de variable entre foreach
y los paréntesis, debemos recuperar cada uno de los valores por los que el bucle
itera mediante la variable por defecto $_
Control de bucles
Perl proporcional un conjunto de directivas para el control de bucles
tales como next:
foreach (<*.pl>) {
next if ( -M $_ > 7.0 );
system("copy $_ ${_}.bkup");
print "Backup de Script : $_ -> ${_}.bkup\n";
}
|
Así pues, en el ejemplo, realizamos el backup anterior de todos los ficheros
cuya fecha de modificación sea inferior a siete días ... puesto que next
nos devuelve al inicio del bloque sin realizar ninguna operación en los casos
en los que la condición if sea cierta.
Visibilidad: my - our
Perl por defecto considera como visibles o accesibles globalmente
(en cualquier punto de un script) todas las variables
empleadas a menos que se le indique lo contrario restringiendo la visibilidad
de éstas mediante una directiva my, our o local.
El uso de la directiva my antepuesta al uso o asignación de una
variable, le proporciona unas características de visibilidad similar al
estilo de visibilidad por bloques en C de modo que:
$i = 54;
for my $i (0 .. 3) {
print "$i\t";
}
print "$i\n";
|
proporcionaría la siguiente salida por pantalla:
Puesto que la visibilidad de la variable $i declarada en el bloque
for queda restringida a dicho bloque y desde fuera de él accedemos
a la variable $i global declarada e inicializada a
54 en la primera
linea.
La directiva our sirve para hacer notar explícitamente que usamos
una variable global; de éste modo podríamos haber escrito en lugar del
ejemplo anterior:
our $i = 54;
for my $i (0 .. 3) {
print "$i\t";
}
print "$i\n";
|
Subrutinas
Las subrutinas son piezas de código de uso frecuente susceptibles de
ser llamadas en diversas ocasiones con la posibilidad de parametrizar
su ejecución mediante una serie de argumentos.
En Perl se pueden definir subrutinas mediante una sintaxis del tipo:
sub nombre_subrutina {
....
}
|
Cualquier argumento pasado a la subrutina en su llamada, podrá
ser accedido por las directivas de dicha subrutina
mediante el array canónico @_, dicho
array puede ser mencionado explícitamente dentro del cuerpo de
la subrutina o obviado pues pasa a ser un argumento implícito
de funciones y operadores de arrays. Una subrutina devuelve
un valor de retorno igual al de la última expresión evaluada en el
transcurso de su ejecución. Podemos utilizar igualmente
la directiva return para hacer explícita la acción de finalización
y retorno del valor correspondiente.
Un ejemplo de la utilización de subrutinas junto con el uso
de bucles anidados con directivas de control basadas en etiquetas
podría consistir en una búsqueda recursiva de los ficheros contenidos
en un árbol de carpetas que hayan sido modificados hace menos de 5 días:
1 #!/usr/bin/perl
2 print busqueda_recursiva(".",5.0)." ficheros < 5 dias\n";
3 @x = busqueda_recursiva(".",5.0);
4 print join("\n",@x);
5 print "\n";
6 sub busqueda_recursiva {
7 my $path = shift;
8 my $age = shift;
9 my @array = ();
10 DIRECTORIO: foreach my $dir ( <$path/*> ) {
11 next DIRECTORIO unless ( -d $dir ) ;
12 FICHERO : foreach my $file (<$dir/*>) {
13 next FICHERO unless ( -M $file < $age );
14 if ( -d $file ) {
15 push(@array,busqueda_recursiva($file,$age));
16 } else {
17 push(@array,$file);
18 }
19 }
20 }
21 @array;
22 }
|
En la segunda linea se produce una primera llamada a la subrutina
busqueda_recursiva pasándole como argumentos el directorio
sobre el que se realizará la búsqueda (en éste caso el
directorio actual ".") además del
número de días 5.
Cabe destacar que en ésta primera llamada, la rutina se ejecuta en un
contexto escalar y es por eso, que @array en la linea 21 dentro
del cuerpo de la rutina se evaluará del mismo modo, obteniendo el efecto
de recuperar el número de ficheros que satisfacen la condición requerida.
En la linea 3 la llamada se produce en un contexto distinto (contexto
de lista) y por tanto se devuelve el array con el conjunto de nombres
de ficheros.
El cuerpo de la subrutina se inicia con dos llamadas a la función shift
con el argumento implícito @_ para recuperar el valor de los dos
argumentos pasados (i.e. el directorio inicial de búsqueda y el número
de días), a continuación se inicializa un array vacío que contendrá
la lista de nombres de ficheros. Se inicia un bucle anidado en el que
se utilizan explícitamente unas etiquetas DIRECTORIO: y
FICHERO: que se utilizarán como delimitadores de las directivas
de control de bucles next. Dentro del bucle, existe una llamada
recursiva a la misma función en caso de que se necesite profundizar
en un subdirectorio y también una llamada a la
función push que va acumulando
cada uno de los nombres de ficheros que satisfacen las condiciones deseadas.
Referencias y estructuras de datos
Perl 5 incorporó la posibilidad de gestionar las llamadas referencias
con objeto de facilitar la gestión de estructuras de datos complejas tales
como hashes anidados o matrices multidimensionales. En ésta sección
se expondrán las claves de su uso y su aplicación en el contexto del
lenguaje.
Introducción
Las referencias son escalares que del mismo modo que los nombres
de variables como arrays o hashes pero a un nivel más interno se refieren
a
la totalidad de valores de un hash, un array o simplemente un único escalar.
Su incorporación a Perl se produjo en la versión
5 y se derivó de la frustración que suponía no poder construir
arrays de hashes, hashes de arrays y otras estructuras derivadas.
A continuación se expondrá la sintaxis básica de construcción y
uso de referencias.
Creación de referencias
Existen dos reglas para crear referencias:
Regla de creación No 1
Situando un backslash -> \ como prefijo de una
variable, obtenemos una referencia a dicha variable:
1 #!/usr/bin/perl
2 %datos = (autor => "William Shakespeare", titulo => "Hamlet");
3 $texto = " Ber. Who's there ? ...";
4 $referencia_escalar = $texto;
5 $referencia_hash = \%hash;
6 print "$referencia_escalar\n";
7 print "$referencia_hash\n";
|
La salida del ejemplo nos proporcionaría:
SCALAR(0x8060284)
HASH(0x8060308)
|
Tal y como se puede apreciar, la referencia es una variable escalar que por el
momento parece contener una cadena de texto indicativa del tipo de variable
al que se refiere junto con un índice interno (0x806...) que utilizará
el intérprete para recuperar el contenido de la variable referenciada en cuanto
lo requiramos.
Regla de creación No 2
Perl posibilita también la creación de referencias a arrays o hashes anónimos mediante
la siguiente sintaxis:
1 #!/usr/bin/perl
2 $datos = {autor => "William Shakespeare", titulo => "Hamlet"};
3 $texto = [" Ber. Who's there ? ...", "Fran. Nay, answer me."];
4 print "$datos\n";
5 print "$texto\n";
|
Así pues, mediante los delimitadores { } creamos un nuevo hash
que contiene información acerca de la obra y mediante los
corchetes [ ] un nuevo array con las dos primeras lineas del texto.
En el lado izquierdo de ambas asignaciones, se situa el nombre de una
variable que contendrá la referencia al hash y al array respectivamente.
Hay que destacar que tanto el hash como el array son elementos que
no tienen un nombre (son anónimos), en cambio, existe en ambos
casos una variable que alberga una referencia a sus contenidos a la cual
nos podemos referir para recuperarlos posteriormente.
Uso de referencias
Del mismo modo que para crear referencias, podemos utilizar dos
reglas distintas para recuperar los contenidos a los que se refieren:
Regla de uso No 1
Supongamos que $refa contiene la referencia a un array. Perl
interpretará {$refa} como el nombre de dicho array y a todos
los efectos será equivalente trabajar con el array derreferenciado o
con el original:
@a = qw(a b c d e f);
$aref = \@a;
push(@a,g);
push(@{$aref},h,i);
print "@a\n";
print @{$aref}."\n";
|
En muchas ocasiones, podemos utilizar referencias anónimas a valores
de retorno de funciones como split de un modo similar al expuesto
a continuación:
$texto = "Homer Marge Bart Lisa Maggie";
foreach $i ( 0..$#{[split /\s+/, $texto]} ) {
print "Elemento No. $i = ${[split /\s+/, $texto]}[$i]\n";
}
|
Regla de uso No 2
Dado que el uso de gran cantidad de corchetes para derreferenciar contenidos
en estructuras de datos complejas oscurece la sintaxis, Perl proporciona una
notación alternativa para la recuperación de elementos de hashes o arrays:
${$aref}[0] => $aref->[0]
${$href}{k} => $href->{k}
|
Así pues, existe un operador arrow -> que nos permite
recuperar los valores contenidos en una referencia.
Agunos ejemplos
Como un primer ejemplo del uso de referencias podríamos considerar
la siguiente asignación:
@a = ( [1, 2, 3], [4, 5, 6], [7, 8, 9]);
|
@a es un array con tres elementos, los tres son referencias a otros
tantos arrays anónimos. Para recuperar los valores, necesitaremos en
primer lugar utilizar cada una de las tres referencias $a[0],
$a[1] o $a[2] aplicándoles el operador flecha con
el índice del elemento requerido: por ejemplo $a[1]->[2] que contiene
el tercer valor del segundo array (i.e. 6). Llegados a éste punto,
hay que hacer notar que existe una regla de uso que nos permite prescindir
del operador flecha en caso de que se haga constar entre dos índices.
De éste modo:
$a[1]->[2] equivale a $a[1][2]
|
Notándolo de manera mucho más próxima a la de un array multidimensional.
Por último, consideremos un problema concreto como podría ser el tratamiento
de un fichero cuyo contenido fuera el siguiente:
Chicago, USA
Frankfurt, Germany
Berlin, Germany
Washington, USA
Helsinki, Finland
New York, USA
|
para obtener una salida en la que se listen por orden alfabético los paises
que constan en el fichero, además de la lista de ciudades pertenecientes
a cada uno con un formato del tipo:
Finland: Helsinki.
Germany: Berlin, Frankfurt.
USA: Chicago, New York, Washington.
|
El modo más natural de resolver el problema consistiría en la creación
de un hash de arrays mediante el uso de referencias y tal como se expone a
continuación:
1 while (<>) {
2 chomp;
3 my ($city, $country) = split /, /;
4 push @{$table{$country}}, $city;
5 }
6
7 foreach $country (sort keys %table) {
8 print "$country: ";
9 my @cities = @{$table{$country}};
10 print join ',',sort @cities;
11 print ".\n";
12 }
|
Éste último ejemplo ha sido extraído del tutorial de uso de referencias
perlreftut
y en él abundan las expresiones implícitas
de variables canónicas, además del ahorro de paréntesis en las
llamadas a funciones como join utilizándolas a modo de operadores.
El análisis detallado de cada una de las lineas del último ejemplo
y los anteriores puede proporcionarnos
bastante comprensión acerca de como trabaja Perl y a la vez nos ayude
a mejorar nuestro propio estilo de programación.
Bibliografía
-
Programming Perl, Larry Wall, Tom Christiansen and
Jon Orwant, 2000, O'Reilly
-
Mark's very short tutorial about references, Mark-Jason
Dominus, 1998, The Perl Journal
|