cc31a Desarrollo de Software de Sistemas

Patricio Poblete (ppoblete@dcc.uchile.cl)

Perl

Perl es un lenguaje interpretado, que fue creado e implementado por Larry Wall. Perl combina características de un shell, de sed, de awk, de C, e incluso algo de BASIC.

Es un lenguaje muy útil para desarrollar aplicaciones en forma rápida, con manejo de archivos y acceso a las funciones del sistema. También ha resultado ser uno de los lenguajes preferidos para implementar aplicaciones en el web.

"¡Hola!" en Perl

hola


        #!/usr/bin/perl
        
        print "¡Hola!\n";

Ejemplo: Saludar por nombre

saluda


        #!/usr/bin/perl
        
        # Lee un nombre e imprime saludo
        print "Por favor, escribe tu nombre: ";
        $nombre = <STDIN>;
        chop($nombre);
        print "¡Hola $nombre!\n";

Notas:

Variables escalares

Las variables escalares almacenan números y strings, y tienen nombres que comienzan siempre por $.

Ejemplos:

Ejemplo:

cat


        #!/usr/bin/perl
        
        while(<STDIN>) # Lee y deja en $_
          {
            print;     # Imprime $_
          }

Valores escalares

Números:

Strings:

"..."
Se realizan expansiones de $var, y también se interpretan \n, etc.
'...'
No se expanden variables, las únicas secuencias de escape reconocidas son \' y \\

"Here documents"

Perl también tiene una notación para "here documents" al estilo del shell:

here


        #!/usr/bin/perl
        
        print <<'END-OF-TEXT';
        Este es un texto
        que se toma textualmente
        hasta que aparece la marca
        definida al comienzo.
        END-OF-TEXT
<<'...'
No se hacen sustituciones de variables dentro del texto.
<<"..."
Se hacen sustituciones de variables.

Instrucciones condicionales

if( EXPR ) BLOCK
if( EXPR ) BLOCK else BLOCK
if( EXPR ) BLOCK elsif( EXPR ) BLOCK ... else BLOCK
donde un BLOCK es de la forma
{ EXPR; ... ; EXPR; }
(el último ; es opcional).

Una forma alternativa es

EXPR if EXPR
EXPR unless EXPR
Ejemplo:
$max = $x unless $max<$x;

Instrucciones iterativas

LABEL while( EXPR ) BLOCK
LABEL while( EXPR ) BLOCK continue BLOCK
LABEL for( EXPR; EXPR; EXPR ) BLOCK
LABEL foreach VAR ( ARRAY ) BLOCK
LABEL BLOCK continue BLOCK
El LABEL es opcional, y permite identificar el ciclo para ser usado por las instrucciones siguientes:
last LABEL # Termina el ciclo
next LABEL # Va a la siguiente iteración
redo LABEL # Ejecuta de nuevo el cuerpo de la iteración
Si el LABEL se omite, la instrucción se aplica al ciclo más cercano.

Condiciones

Cualquier expresión funciona como condición. Si el valor es "", "0" ó 0, se interpreta como FALSO; en caso contrario, VERDADERO.

Si una variable es indefinida, al ser evaluada da el string vacío, el cual en un contexto booleano da FALSO. Es frecuente aplicar esto de la manera siguiente:

Ejemplo:

$lim=100 unless $lim; # indefinido => 100
aunque en rigor esto debería escribirse:
$lim=100 unless defined($lim); # indefinido => 100
También existen conectivos lógicos: !, && y ||. Estos operadores tienen alta prioridad. Existen también los sinónimos de baja prioridad not, and y or.

Comparaciones

Las variables escalares se pueden comparar en forma numérica usando los operadores:

<, >, <=, >=, ==, !=

También existe el operador <=>, que retorna -1, 0 ó +1.

Para comparar escalares como strings, se usan los operadores:

lt, gt, le, ge, eq, ne

En análogo para strings de <=> es cmp.

Ejemplo: Imprimir From: y Subject: de un mensaje de mail

headers


        #!/usr/bin/perl
        
        while(<>)
          {
            print if /^From:|^Subject:/;
            last if /^$/;  # Para con una línea en blanco
          }
Notas:

Arreglos

Un arreglo se denota por un nombre que comienza por @, y sus elementos se subindican de cero en adelante.

paco


        #!/usr/bin/perl
        
        @p = ("hugo", "paco", "luis");
        print $p[1], "\n";  # imprime "paco"
Notas:

"echo" en Perl

echo


        #!/usr/bin/perl
        
        for( $i=0; $i<=$#ARGV; ++$i )
          {
            print $ARGV[$i],
                  ($i<$#ARGV ? " " : "\n");
          }
Notas:

"There's more than one way to do it"

echo2


        #!/usr/bin/perl
        
        print join(" ", @ARGV),
              ($#ARGV>=0 ? "\n" : "" );
Nota:

split

Lo contrario de join es split. El siguiente programa calcula el número total de segundos correspondientes a una hora de la forma "hh:mm"ss":

hora1


        #!/usr/bin/perl
        
        print "Ingrese una hora en formato hh:mm:ss\n";
        chop($hms=<>); # Combina lectura y chop
        ($h, $m, $s) = split(/:/, $hms);
        print "Esto equivale a ", $h*3600+$m*60+$s, " segundos\n";
Notas:

Alternativa usando pattern matching

hora2


        #!/usr/bin/perl
        
        print "Ingrese una hora en formato hh:mm:ss\n";
        chop($hms=<>); # Combina lectura y chop
        $hms =~ /(..):(..):(..)/;
        print "Esto equivale a ", $1*3600+$2*60+$3, " segundos\n";
Notas:

push, pop, shift, unshift

Los arreglos crecen dinámicamente, a través de agregarle o quitarle elementos de sus dos extremos:
push(@a,$x)
Agrega $x al final de @a

$x = pop(@a)
Extrae el último elemento de @a

unshift(@a,$x)
Agrega $x al comienzo de @a

$x = shift(@a)
Extrae el primer elemento de @a

Arreglos Asociativos (Hashes)

Los arreglos se subindican con números. Los hashes se subindican con strings. El nombre de un hash comienza por %, y el subíndice se encierra entre { }.

Ejemplo: Resumir records de entrada de la forma "userid value".

resume


        #!/usr/bin/perl
        
        while(<>)
          {
            chop;  # opera sobre $_
            ($u, $v) = split;
            $total{$u} += $v;
          }
        foreach $u (sort keys %total)
          {
            printf "%-8s %4d\n", $u, $total{$u}
          }

Notas:

Enumeración de un hash

Ejemplo: Sustitución de diminutivos

dim


        #!/usr/bin/perl
        
        %dim = ( "José", "Pepe",
                 "Patricio", "Pato",
                 "Francisco", "Pancho" );
        while(<>)
          {
            chop;
            $_ = $dim{$_} if $dim{$_};
            print "¡Hola $_!\n";
          }
Notas:

Variables de ambiente

Las variables de ambiente se le entregan al programa a través de un hash llamado %ENV.

Ejemplo:

pwd


        #!/usr/bin/perl
        
        print "El directorio actual es $ENV{'PWD'}\n";

Manejo básico de archivos

Ejemplo: show archivo

show


        #!/usr/bin/perl
        
        die "Use: show archivo\n" unless $#ARGV==0;
        open(IN,$ARGV[0]) || die "No se puede abrir el archivo\n";
        while(<IN>)
          {
        	print;  # imprime $_
          }
        close(IN);
Notas:

Ejemplo: copy from-file to-file

copy


        #!/usr/bin/perl
        
        $#ARGV==1 || die "Use: copy from-file to-file\n";
        open(IN,"<$ARGV[0]") || die "No se puede abrir archivo de entrada\n";
        open(OUT,">$ARGV[1]") || die "No se puede abrir archivo de salida\n";
        
        while(<IN>)
          {
        	print OUT;
          }
Notas:

Forma general de open

La forma general del comando open es
open FILE, filename
Los argumentos también pueden ir entre paréntesis.

El filename puede ser de las formas siguientes:

"..."read
"<..."
">..."write
">>..."append
"+>..."read/write
"|..."pipe to command
"...|"pipe from command

Contexto escalar vs. de arreglo

Los operadores de Perl entregan resultados distintos dependiendo de si aparecen en contexto escalar o de arrgeglo.

Ejemplo:

$linea = <STDIN>; # lee una línea
@lineas = <STDIN>; # lee TODAS las líneas

El siguiente programa es una versión de cat de Unix que usa mucha memoria:

cat2


        #!/usr/bin/perl
        
        @lineas = <>;  # leer todas las líneas
        print @lineas; # e imprimirlas

Más aún, como print espera una lista de parámetros, el contexto a la derecha de print es de arreglo. Así, el siguiente programa es equivalente al anterior:

cat3


        #!/usr/bin/perl
        
        print <>;

De manera similar, muchos operadores se extienden a arreglos. Por ejemplo,

chop(@lineas);
quita los \n a cada uno de los elementos del arreglo.

Comandos grep y map

La operación grep(EXPR, ARRAY) aplica la EXPR a cada uno de los elementos del ARRAY, y retorna un arreglo conteniendo a los elementos que arrojaron valor verdadero.

La operación map (de Perl5) es similar, pero retorna todos los elementos.

Ejemplo:

titulos


        #!/usr/bin/perl
        
        @a = <>;
        
        print grep(/h3/,@a);      # Imprimir todos los títulos h3
        print "-" x 60, "\n";     # Imprime "-" 60 veces  
        print grep(s/h3/h2/g,@a); # Modificar e imprimir líneas con h3
        print "-" x 60, "\n";
        print map(s/h3/h2/g,@a);  # Modificar h3; imprimir todas las líneas

Expresiones regulares

Cada carácter calza consigo mismo, excepto los caracteres especiales: *, +, (, ),, etc., los cuales se escapan con \.

.Cualquier carácter excepto newline
(...)Agrupa y recuerda en $1, $2,... el substring detectado
^Comienzo de línea
$Fin de línea
[...]Clase de caracteres
[^...]Clase de caracteres negada
(...|...|...)Alternativas
*Repite lo anterior 0 o más veces
+Repite lo anterior 1 o más veces
{m,n}Repite mínimo m, máximo n veces

\wCarácter alfanumérico \WCarácter no alfanumérico
\sEspacio (blanco, tab) \SNo espacio
\dCarácter numérico \DCarácter no numérico
\bBorde de palabra \BNo borde de palabra

Ejemplo:

hora


        #!/usr/bin/perl
        
        open(DATE, "date|");
        $date = <DATE>;
        # Tue Aug 15 16:17:12 CST 1995
        ($h, $m) = ( $date =~ /\w+ \w+ \d+ (\d+):(\d+):\d+/ );
        print "Son las ", ($h<=12? $h : $h-12),
              ($m==0? "" : " con $m minutos"), "\n";
        # Son las 4 con 17 minutos

Funciones (subrutinas)

Declaración:
sub subname
  {
    statement1;
    statement2;
    . . .
  }

Ejemplo:

saluda2


        #!/usr/bin/perl
        
        sub saluda
          {
            print "¡Hola!\n";
          }
        
        # A continuación, la llamada:
        
        saluda;

Argumentos

Si al llamar a la subrutina se le entregan argumentos, éstos se le entregan en un arreglo @_.

Ejemplo:

saluda3


        #!/usr/bin/perl
        
        sub saluda
          {
            print "¡Hola $_[0]!\n";
          }
        
        saluda("Juan");
        saluda("María");

Retorno de valores

El valor retornado por la función es el último valor evaluado dentro de su bloque.

Ejemplo:

suma


        #!/usr/bin/perl
        
        sub suma
          {
            $_[0]+$_[1];
          }
        
        print suma(2,3), "\n";

Variables locales

Una variable declarada como my es local a la subrutina.

Es frecuente copiar los argumentos a variables locales. Con esto se consiguen dos efectos: darles nombres más significativos, y hacer que el traspaso sea por valor (el default es por referencia).

Ejemplo:

suma2


        #!/usr/bin/perl
        
        sub suma
          {
            my($a,$b) = @_;
        
            $a+$b;
          }
        
        print suma(2,3), "\n";

Una subrutina con un número variable de parámetros

suman


        #!/usr/bin/perl
        
        sub suma
          {
            my $s = 0;  # en realidad, no hace falta
                            # inicializar
            my $x;
        
            foreach $x( @_ )
              {
                $s += $x;
              }
            $s;
          }
        
        print suma(12,5,21,14), "\n";
        print suma(1..5), "\n";  # 1+2+...+5

Las subrutinas pueden ser recursivas

potencia


        #!/usr/bin/perl
        
        sub potencia
          {
            my($a,$n) = @_;
        
            if( $n == 0 )
              {
                1;
              }
            elsif( $n%2 == 1 )
              {
                $a*potencia($a,$n-1);
              }
            else
              {
                potencia($a*$a,$n/2);
              }
          }
        
        print potencia(2,13), "\n";

Operaciones predefinidas de Perl

El lenguaje Perl posee una gran cantidad de operadores que permiten realizar operaciones útiles. Algunos ejemplos son:
rand EXPR
Retorna un número al azar entre 0 y EXPR
time
Retorna el tiempo en segundos desde el 1/1/1970.
length EXPR
Retorna el largo en caracteres del string EXPR
-r FILE
Retorna verdadero si FILE es legible
rename OLDNAME, NEWNAME
Cambia el nombre de un archivo
sleep EXPR
Hace que el programa "duerma" durante EXPR segundos
Todos los servicios del sistema están accesibles al programa a través de este tipo de operadores.

Otras operaciones

Además de lo que hemos visto hasta el momento, Perl posee:

Programación Orientada a Objetos en Perl

En Perl, una clase se implementa mediante un package.

Empleado1.pm


        package Empleado1;
        
        sub new
          {
            my($pkg, $nombre, $rut, $sueldo) = @_;
        
            my $e = {
                      nombre => $nombre,
                      rut => $rut,
                      sueldo => $sueldo
                    };
            return bless $e, $pkg;
          }
        
        sub impr
          {
            my $e=shift;
        
            print "Nombre: ", $e->{rut}, "\n",
                  "RUT:    ", $e->{nombre}, "\n",
                  "Sueldo: ", $e->{sueldo}, "\n";
          }
        
        sub aumenta_sueldo
          {
            my($e, $delta) = @_;
        
            $e->{sueldo} += $delta;
          }
        
        sub sueldo # método de acceso
          {
            my $e=shift;
        
            return $e->{sueldo};
          }
        
        1;

Ejemplo:

empleado1


        #!/usr/bin/perl
        
        use Empleado1;
        
        $e1=new Empleado1("Juan Pérez", "3.141.159-2", 350000);
        $e1->impr;
        $e1->aumenta_sueldo(50000);
        print "\n";
        $e1->impr;

Existen también métodos de la clase. Por ejemplo, supongamos que se desea llevar un contador global de cuantos empleados existen:

Empleado2.pm


        package Empleado2;
         
        my $nemp=0;
        
        sub new
          {
            my($pkg, $nombre, $rut, $sueldo) = @_;
         
            my $e = {
                      nombre => $nombre,
                      rut => $rut,
                      sueldo => $sueldo
                    };
            ++$nemp;
            return bless $e, $pkg;
          }
        
        sub nemp # metodo de la clase
          {
            return $nemp;
          }
         
        sub impr
          {
            my $e=shift;
         
            print "Nombre: ", $e->{rut}, "\n",
                  "RUT:    ", $e->{nombre}, "\n",
                  "Sueldo: ", $e->{sueldo}, "\n";
          }
         
        sub aumenta_sueldo
          {
            my($e, $delta) = @_;
         
            $e->{sueldo} += $delta;
          }
         
        sub sueldo # método de acceso
          {
            my $e=shift;
         
            return $e->{sueldo};
          }
         
        1;

Ejemplo:

empleado2


        #!/usr/bin/perl
        
        use Empleado2;
        
        $e1=new Empleado2("Juan Pérez", "3.141.159-2", 350000);
        $e2=new Empleado2("Pedro González", "2.718.281-8", 390000);
        print "Hay ", Empleado2->nemp, " empleados\n";

Los objetos dejan de existir al dejar de referirnos a ellos. Si se provee un método DESTROY, se ejecuta automáticamente en ese momento:

Empleado3.pm


        package Empleado3;
         
        my $nemp=0;
        
        sub new
          {
            my($pkg, $nombre, $rut, $sueldo) = @_;
         
            my $e = {
                      nombre => $nombre,
                      rut => $rut,
                      sueldo => $sueldo
                    };
            ++$nemp;
            return bless $e, $pkg;
          }
        
        sub DESTROY
          {
            my $e=shift;
        
            print $e->{nombre}, " Q.E.P.D.\n";
            --$nemp;
          }
        
        sub nemp
          {
            return $nemp;
          }
         
        sub impr
          {
            my $e=shift;
         
            print "Nombre: ", $e->{rut}, "\n",
                  "RUT:    ", $e->{nombre}, "\n",
                  "Sueldo: ", $e->{sueldo}, "\n";
          }
         
        sub aumenta_sueldo
          {
            my($e, $delta) = @_;
         
            $e->{sueldo} += $delta;
          }
         
        sub sueldo # método de acceso
          {
            my $e=shift;
         
            return $e->{sueldo};
          }
         
        1;

Ejemplo:

empleado3


        #!/usr/bin/perl
        
        use Empleado3;
        
        $e1=new Empleado3("Juan Pérez", "3.141.159-2", 350000);
        $e2=new Empleado3("Pedro González", "2.718.281-8", 390000);
        print "Hay ", Empleado3->nemp, " empleados\n";
        $e1=0;
        print "Hay ", Empleado3->nemp, " empleados\n";

Un método incluso podría haber sido llamado como


        Clase->metodo

o bien


        $objeto->metodo

y el siguiente trozo de código permite distinguir entre ambos casos:


        sub metodo
          {
            my($r, ...) = @_;
        
            if(ref $r)
              {
                # fue llamado con un objeto (instancia)
              }
            else
              {
                # fue llamado para la clase
              }
          }

Herencia

Empleado.pm


        package Empleado;
        
        sub alloc
          {
            my($pkg, $nombre, $rut) = @_;
        
            my $e = {
                      nombre => $nombre,
                      rut => $rut
                    };
            return bless $e, $pkg;
          }
        
        sub nombre
          {
            my $e=shift;
        
            return $e->{nombre};
          }
        
        1;

EmpJC.pm


        package EmpJC;
        
        @ISA=("Empleado");
        
        sub new
          {
            my($pkg, $nombre, $rut, $sueldo) = @_;
        
            my $e=$pkg->alloc($nombre, $rut); # usamos al padre
        
            $e->{sueldo}=$sueldo;
        
            return $e;
          }
        
        sub sueldo
          {
            my $e=shift;
        
            return $e->{sueldo};
          }
        
        1;

EmpHoras.pm


        package EmpHoras;
        
        @ISA=("Empleado");
        
        sub new
          {
            my($pkg, $nombre, $rut, $valor_hora, $num_horas) = @_;
        
            my $e=$pkg->alloc($nombre, $rut);
        
            $e->{valor_hora}=$valor_hora;
            $e->{num_horas}=$num_horas;
          }
        
        sub sueldo
          {
            my $e=shift;
        
            return $e->{num_horas} * $e->{valor_hora};
          }
        
        sub pone_horas
          {
            my($e, $h) = @_;
        
            $e->{num_horas}=$h;
          }
        
        1;

Para accesar explícitamente un método del padre se usa


        $obj->SUPER::metodo;

Esto permitiria que alloc se llamara new y se usaria


        $pkg->SUPER::new( ... );

Herencia múltiple

Perl permite herencia múltiple, mediante:


        @ISA=("padre1", "padre2"); # O bien @ISA=qw(padre1 padre2);

UNIVERSAL

Todos las clases heredan de una clase llamada UNIVERSAL.

También existen los métodos isa y can:


        Rectangulo->isa("Figura")  # verdadero si Rectangulo hereda de Figura
                                   # directa o indirectamente
        
        Rectangulo->can("dibujar") # verdadero si Rectangulo o alguno de sus
                                   # ancestros tiene un metodo "dibujar"