PROGRAMMAZIONE CON PERL


INDICE DEGLI ARGOMENTI




7. Subroutine

In questo breve capitolo descriveremo tutte quelle tecniche che possono servire a rendere più modulare uno script, in modo da poterne riutilizzare in più contesti alcune sue parti o anche per poter integrare nello script strumenti software già esistenti e che sarebbe dispendioso riscrivere da capo.

7.1. Subroutine

Se una certa parte di codice deve essere richiamata più volte all'interno di uno script o semplicemente per motivi di leggibilità del codice stesso, può essere utile spezzare un lungo script in più subroutine, ossia piccoli pezzi di sottoprogramma con una loro propria identità logica e funzionale. In Perl chiameremo quindi subroutine ciò che in Pascal viene chiamato procedura ed in C funzione.

Una subroutine deve essere definita prima di poterla utilizzare; per far questo si usa il seguente costrutto:

sub nome_subroutine {
blocco di istruzioni della subroutine
return valore_di_ritorno;
}

È possibile passare dei parametri alla subroutine, che verranno memorizzati dall'interprete nell'array '@_'. La subroutine può a sua volta restituire un valore che deve essere specificato come argomento della funzione return.

Per richiamare una subroutine definita nello script si deve specificare il nome preceduto dal simbolo '&' e seguito dalle parentesi che possono eventualmente contenere i parametri (separati da virgole) passati alla subroutine.

Con la funzione local possono essere definite delle variabili locali che hanno validità solo all'interno della subroutine.

Nell'esempio seguente viene definita una subroutine 'media' che restituisce la media aritmetica dei numeri passati come argomento:

#!/usr/local/bin/perl
# media.pl
# legge dei valori in input e ne stampa la
# media aritmetica

sub media {
  local($n, $somma, $media);
  foreach $n (@_) {
    $somma += $n;
  }
  $media = $somma/($#_+1);
  return($media);
}

print "Inserisci i numeri:\n";
@numeri = <STDIN>;
chop(@numeri);
$media = &media(@numeri);
print "media = $media\n";

7.2. Ricorsione

In Perl possono anche essere definite delle funzioni ricosive, ossia delle funzioni che possono richiamare se stesse per produrre il risultato desiderato.

Ad esempio supponiamo di voler calcolare il fattoriale di un numero intero; il fattoriale di n è quella funzione che restituisce il prodotto dei numeri da 2 ad n. Possiamo definire due subroutine diverse per calcolare il fattoriale di $n$, una iterativa ed una ricorsiva:

#!/usr/local/bin/perl
# fattoriale.pl

sub fattoriale_1 {
  local($i, $n, $f);
  $n = shift(@_);
  $f = $n;
  for ($i=$n -1; $i>1; $i--) {
    $f = $f*$i;
  }
  return($f);
}

sub fattoriale_2 {
  local($n) = shift(@_);
  ($n == 1) && return(1);
  return($n * &fattoriale_2($n-1));
}

$n = <STDIN>;
$f1 = &fattoriale_1($n);
$f2 = &fattoriale_2($n);
print "fattoriale iterativo = $f1\n";
print "fattoriale ricorsivo = $f2\n";

7.3. Programmi esterni

Con il Perl esistono vari modi per integrare tra loro più programmi esterni, richiamandoli in modo opportuno da uno script in modo che cooperino per la soluzione del problema posto. Brian W. Kernighan nella prefazione di un suo famoso volume, nel sostenere che UNIX è un sistema operativo assai potente ed efficace, asserisce che il punto centrale è costituito dal concetto che la forza di un sistema nasce più dall'interazione tra i programmi che non dai programmi veri e propri. Se presi isolatamente--sostiene Kernighan--molti programmi UNIX compiono dei lavori banali, ma combinati con altri, diventano strumenti utilissimi e generalizzati. Il Perl consente di realizzare questa integrazione in modo assai versatile ed efficiente.

I modi usati più comunemente per interagire con programmi esterni attraverso uno script in Perl sono sostanzialmente tre:

Vediamo in estrema sintesi queste tre tecniche.

7.3.1. Chiamata mediante apici inversi

È il modo più semplice e diretto di richiamare un programma esterno. Sostanzialmente in questo modo si lancia un processo figlio che esegue il suo compito e restituisce l'output come valore di ritorno, senza visualizzare nulla sullo STDOUT.

Ad esempio per inserire la data corrente nella variabile $data si può utilizzare la seguente chiamata:

$data= `date +%d/%m/%y`;

Chiaramente si possono eseguire operazioni anche molto più complesse di questa, magari concatenando in un pipe più chimate. Ad esempio per leggere un file di input ($file) ordinandone le righe e selezionando solo quelle che contengono la stringa $s si può usare la seguente espressione:

@linee = `cat $file | grep $s | sort`

7.3.2. Le funzioni system ed exec

Queste due funzioni aprono un processo figlio e lo eseguono interamente (senza intercettarne l'output come per l'esecuzione di programmi mediante gli apici inversi).

Se l'esecuzione di un programma esterno viene effettuata mediante la funzione system, allora lo script aspetterà che il programma esterno sia completamente terminato prima di riprendere l'esecuzione dall'istruzione successiva alla system.

Con la chiamata exec invece lo script termina l'esecuzione e passa definitivamente il controllo al programma esterno richiamato.

7.3.3. Esecuzione mediante il canale di pipe

Aprendo un programma esterno come se fosse un file in modalità di 'piping', se ne possono sfruttare le funzionalità da uno script in Perl.

Ad esempio, supponiamo di voler visualizzare il contenuto di un array, impaginando l'output mediante il comando UNIX more. Il seguente frammento di script fornisce una possibile implementazione:

open (OUT, "| more") || die "Errore\n\n";
foreach $riga (@array) {
  print OUT $riga;
}
close(OUT);

Un esempio un po' più sofisticato è il seguente. Supponiamo di voler inviare per posta elettronica ad un certo indirizzo l'output del nostro script; il seguente frammento di codice suggerisce una possibile implementazione (in questo caso sfrutteremo il comando mail disponibile su numerosi sistemi UNIX):

$indirizzo = 'liverani@mat.uniroma1.it';
open (MAIL, "| /bin/mail $indirizzo") || die "Impossibile inviare
  il messaggio all'indirizzo $indirizzo.\n\n"; 
print MAIL <<"END";
Subject: Risultato dello script
Lo script ha prodotto il seguente risultato...
END
close(MAIL);

7.4. Librerie esterne

Un'altra tecnica per rendere modulare e riutilizzabile il codice delle nostre subroutine è quello di produrre una libreria di subroutine di uso comune e salvarle su uno o più file. Successivamente se avremo bisogno di utilizzare una subroutine già definita su un file esterno, potremo includere tale file nel nostro script utilizzando la funzione require.

Alla funzione require deve essere passato come parametro il nome del file che si vuole includere nello script. Il parametro deve quindi includere il path assoluto del file desiderato, a meno che il file non si trovi in una delle directory di default in cui l'interprete Perl va a cercare le librerie di subroutine da includere negli script. Questo path di ricerca è contenuto nell'array @INC; se si vuole inserire un'altra directory nell'array basterà aggiungerla alla lista con la funzione push. Il path di ricerca di default dipende dall'installazione dell'interprete Perl di cui si dispone sul proprio sistema (tipicamente su un sistema UNIX esiste una directory /usr/local/lib/perl).

7.5. Valutazione di espressioni

Concludendo questo capitolo è opportuno citare anche la funzione eval, che consente di valutare espressioni Perl. Questa funzione, che appare subito estremamente potente, può risultare spesso difficilmente utilizzabile, ma in alcuni casi può invece essere determinante per sbrogliare situazioni intricate e di difficile soluzione.

In alcuni contesti può risultare troppo gravoso scrivere a priori delle subroutine per svolgere determinati compiti, oppure una subroutine molto generale può risultare poi di fatto troppo lenta; in questi casi può invece essere utile produrre runtime (al momento dell'esecuzione dello script) delle subroutine sulla base del contesto in cui ci si trova e farle quindi eseguire all'interprete mediante la funzione eval.

Riportiamo un esempio elementare che può aiutare a chiarire il funzionamento di questa potente istruzione. Riprendiamo quindi lo script per il calcolo della media aritmetica su un insieme di numeri inseriti dall'utente e riscriviamolo sfruttando la funzione eval:

#!/usr/local/bin/perl
# media_bis.pl
# legge dei valori in input e ne stampa la media aritmetica

sub media {
  local($n, $somma, $media);
  $media = "(";
  $media .= join('+', @_);
  $media .= ")/($#_+1)";
  return(eval($media));
}

print "Inserisci i numeri:\n";
@numeri = <STDIN>;
chop(@numeri);
$media = &media(@numeri);
print "media = \$media/n";



Capitolo 8



(C) 2000 for spaRtan