Michele Beltrame
Corso di Perl - Altre funzioni e moduli

Continuiamo la carrellata di funzioni analizzandone ancora qualcuna tra quelle più spesso utilizzate dai programmatori. La seconda parte dell'articolo è invece dedicata ad un breve introduzione ai moduli. In essa si cercherà più che altro di indicare al lettore come servirsi delle migliaia di librerie di cui la comunità Perl dispone.

Data di pubblicazione: Questo articolo è stato pubblicato su Dev n85 e in questo sito per autorizzazione espressa del "Gruppo Editoriale Infomedia" (http://www.infomedia.it).

© 2007 Michele Beltrame. Michele Beltrame vive a Maniago, PN, è programmatore Perl e convinto sostenitore del movimento Open Source. Può essere raggiunto via e-mail all'indirizzo mb@italpro.net

© Perl Mongers Italia. Tutti i diritti riservati.


Introduzione

Ricominciamo da dove ci eravamo fermati la scorsa lezione. Si erano viste alcune funzioni relative alla manipolazione di stringhe, numeri e date. Al di fuori di queste tre principali categorie, le funzioni offerte da Perl sono molteplici, e qui vedremo solo alcune delle più utili, peraltro in maniera molto sintetica. Rimandiamo il lettore a dare una rapida occhiata alla lista di esse reperibile nella documentazione in linea oppure in una qualsiasi guida di riferimento al linguaggio.


Warn, exit, die!

Non si tratta dell'ultimo urlo minaccioso di qualche cantante rap-metal, bensì di una triade di funzioni che permettono di controllare l'esecuzione del proprio programma. Iniziamo dalla tenebrosa die(): essa generalmente causa l'immediata terminazione del programma in esecuzione; esistono tuttavia altri casi, che qui non vedremo, in cui essa può avere altro significato. Un tipico esempio del suo utilizzo è:

open (NOMI, "<nomi.txt") or die "Errore!";

die() stampa sullo standard error (STDERR, buffer di output dedicato agli errori, che solitamente è la console stessa) tutti i parametri passati, in questo caso la sola stringa Errore!, e causa la fine dell'esecuzione del programma ed il ritorno dei controllo al processo chiamante. Essa va quindi usata per gestire gli errori dopo i quali è impossibile continuare. E se invece l'errore non è poi così grave ed è desiderabile solo visualizzare un messaggio sullo standard error per poi continuare regolarmente? In questo caso è possibile servirsi di warn(): essa è uguale a die() nel senso che stampa su STDERR tutti i suoi parametri, ma non causa alcuna terminazione del programma. Per garantire un'uscita "pulita" dal programma, senza che venga stampato alcun messaggio, va invece utilizzata exit(). Essa accetta anche un parametro numerico che indica lo stato di uscita (livello di errore): se questo è omesso, lo stato di uscita è 0 (nessun errore).


Gestione di file e directory

Per semplificare e rendere più rapida la gestione dei file Perl mette a disposizione alcune funzioni di base. mkdir() ad esempio crea una directory:

mkdir "/u/mp3/SkidRow";
mkdir "/u/mp3/SkidRow", 0700;

Come si vede, è possibile passare anche un secondo parametro, che indica i permessi del file. Questi sono validi solo in ambiente Unix, e chi utilizza uno di questi sistemi operativi sa bene cosa sono. Chi invece usa Windows oppure altri sistemi operativi dove i permessi non sono supportati non ha bisogno di preoccuparsene. Una directory può naturalmente anche essere rimossa, purché vuota, con:

rmdir "/u/mp3/SkidRow";

Per leggere i contenuti di una directory vanno invece usate ben tre funzioni, in quanto il meccanismo è simile a quello relativo alla lettura dei file:

opendir (MPEG, "/u/mp3");
@files = readdir (MPEG);
closedir (MPEG);

La prima riga associa una directory ad un handle e la apre, la seconda ne legge il contenuto in un array ponendo un nome di file per ogni elemento, ed infine la terza chiude la directory e distrugge l'handle. Dalle directory spostiamoci ora ai file, e vediamo come rinominarli e cancellarli. La prima operazione si effettua con rename():

rename "November_Rein.mp3", "November_Rain.mp3";

Il primo parametro è il nome attuale, mentre il secondo è quello che si vuole assegnare. Rimuovere un file è altrettanto facile:

unlink "Wasted_Time.mp3";

È anche possibile passare ad unlink() una lista di file:

unlink "Wasted_Time.mp3", "Eileen.mp3";

Si possono persino utilizzare le wildcard con:

unlink glob("*.mp3");

La funzione glob() infatti espande l'argomento passato ricavandole una lista di file, che poi viene passata come parametro ad unlink().


Grep e map

Esistono due importanti funzioni che permettono di manipolare degli array: la prima di esse è grep() ed essa restituisce una lista contente solo gli elementi di quella originale che rispondono ad un determinato pattern; la seconda, map(), esegue invece un'istruzione o una funzione per ogni elemento di un array. Partiamo da grep(), che prende il nome da un programma Unix, il GNU Regular Expression Parser, che fa più o meno la stessa cosa. Ricordando le regular expression viste qualche lezione fa, vediamo un esempio:

@nomi = 'Alice', 'anna', 'Camilla', 'carlotta';
@a =grep /^[aA]/, @nomi;

In $a sono contenuti solo i primi due elementi di @nomi, e cioè quelli che iniziano per a oppure per A e per i quali il matching ha quindi esito positivo. Questa funzione è utilissima ad esempio per isolare i file a cui si è interessati a partire da una lista ottenuta con readdir(). Per quanto riguarda map(), si tratta di una delle funzioni più potenti in assoluto di Perl: a partire da un'espressione o da un blocco di istruzioni, essa processa tutti gli elementi di una lista. Ad esempio:

@a = (2, 4, -4, 5, 0, -1);
@b = map { $_+1 } @a;

crea un array @b contenente tutti gli elementi di @a incrementati di una unità. Tra le parentesi graffe si può anche collocare un complesso blocco di istruzioni, ricordandosi che ogni elemento dell'array viene memorizzato, quando viene processato, in $_. Questa variabile, che avevamo già visto in precedenza, è particolare nel senso che molte funzioni la considerano come parametro di default se non ne vengono passati altri. Non è detto che l'array di destinazione debba avere lo stesso numero di elementi di quello di partenza, infatti:

@parole = map { split ' ' } @righe;

divide in parole ogni stringa contenuta in @righe e memorizza tutte queste parole separate in @parole. La funzione map() può anche essere utilizzata per svolgere il lavoro di grep(), infatti:

@a = grep /^[aA]/, @nomi;

è equivalente a:

@a = map { /^[aA]/ ? $_ : () } @nomi;

Per comprenderne il funzionamento è necessario ripensare alla struttura condizionale vista nella lontana quarta lezione: viene valutata l'espressione a sinistra del punto di domanda e, se il risultato logico è vero, viene tornato l'elemento stesso dell'array ($_), altrimenti una lista vuota.


Utilizzo di moduli esterni

I moduli sono delle librerie tramite le quali vengono estese le possibilità offerte da Perl, ad esempio tramite l'aggiunta di nuove funzioni. Chiunque può creare un modulo e renderlo disponibile a tutti tramite il Comprehensive Perl Archive Network (http://search.cpan.org): il sito ne contiene migliaia, molti dei quali sono tra l'altro già inclusi nella distribuzione standard dell'interprete. Mentre creare un modulo richiede qualche conoscenza un po' più avanzata di Perl, il loro utilizzo è piuttosto semplice. Molti di essi a dire il vero sono oggetti, e quindi richiedono delle conoscenze minime di OOP (object oriented programming). In questa sede verranno fornite solo le formazioni indispensabili al loro uso, rinviando chiunque fosse interessato ad approfondire la conoscenza della programmazione OOP alla relativa documentazione. I moduli distribuiti con l'interprete Perl si distinguono in due principali categorie: quelli pragmatici e tutti gli altri.


Moduli pragmatici

I moduli pragmatici sono dei moduli speciali, in quanto essi forniscono delle direttive all'interprete in modo che esso analizzi il codice in un determinato modo. Vediamo ad esempio l'effetto del modulo pragmatico integer:

use integer;
$a =  11/3;

Anzitutto si nota l'utilizzo di use, la direttiva che indica appunto l'inclusione di un modulo, il cui nome è passato come parametro. L'interprete verifica anzitutto se tale parametro indica un modulo pragmatico, e si comporta di conseguenza. Nel caso così non fosse, il modulo viene cercato nei path standard previsti dall'interprete. Se si desiderasse specificarne altri si può inserire, come prima istruzione use:

use lib '/usr/lib/mymodules';

In questo specifico caso i moduli verranno cercati anche in /usr/lib/mymodules. Tra l'altro anche lib è un modulo pragmatico. Tornando ad integer, esso fa sì che tutte le operazioni matematiche vengano compiute su numeri interi, quindi $a assume qui valore 3 anziché 3.66666666667. È importante notare che questo modulo non ha nessun influsso sull'assegnazione dei valori alle variabili, ma solo sulle operazioni su di esse, quindi:

$a = 4.75;

assegna effettivamente 4.75 a $a. I moduli pragmatici non sono molti, ma alcuni di essi tornano utili in specifici casi. Ad esempio warnings permette di abilitare o disabilitare la stampa sullo standard error dei warning, cioè gli errori non fatali, controllando anche quali tipi di warning vadano effettivamente visualizzati. I moduli pragmatici possono anche essere utilizzati solo su parti del proprio programma, e precisamente solo all'interno dei blocchi di codice nei quali si vuole abbiano effetto. Ad esempio:

$a = 11/3;
{
  use integer;
  $b = 11/3;
  print "$b\n";
}
print "$a\n";

Il primo valore stampato ($b) è intero, mentre il secondo è un numero in virgola mobile. Una direttiva interessante è quella che permette di specificare la versione di Perl minima richiesta. Ad esempio:

use 5.8.0;

posto all'inizio del proprio programma fa sì che esso non venga eseguito in versioni dell'interprete antecedenti alla 5.8.0.


Moduli non pragmatici

Più che moduli non pragmatici, questi andrebbero chiamati semplicemente moduli. Essi si distinguono in quelli standard e quelli non standard. La differenza tecnica è inesistente: la categorizzazione è dovuta al semplice fatto che i primi si trovano assieme all'interprete, mentre i secondi vanno cercati nel CPAN oppure in giro per Internet. Vediamo qualcuno di quelli standard, partendo dal vasto ed affascinante mondo della matematica:

use Math::Trig;
$ch = cosh($a);

Come si nota il modulo permette di chiamare la funzione cosh(), che calcola il coseno iperbolico. Allo stesso modo sono fornite funzioni per il calcolo di tangente, arcoseno, cosecante e via dicendo. Poniamo ora di voler utilizzare interi molto grandi, cioè al di sopra dell'intervallo che il nostro sistema operativo mette a disposizione (solitamente da -2147483647 a +2147483647). Ci viene incontro Math::BigInt:

use Math::BigInt;
$a = Math::BigInt->new("14598271981201015819041");
print ($a*3)."\n";

Questo codice crea, chiamando il costruttore dell'oggetto Math::BigInt, un numero intero grande (la grandezza è indefinita, il modulo non pone limiti) e lo memorizza in $a. Il costruttore è new(): in maniera semplicistica potremmo definirlo una funzione che, a partire da una stringa, crea un'entità adatta a rappresentare un numero intero grande. Una volta creato, il numero (che in realtà è un oggetto, non una variabile scalare) può essere utilizzato come un intero qualsiasi, e quindi su di esso sono applicabili tutti gli operatori matematici. Lasciamo ora stare i numeri e passiamo a File::Copy, modulo non OOP che aggiunge a Perl le funzioni copy() e move(), le quali permettono appunto di copiare e spostare file:

use File::Copy;
# Copia Anna_Lee.mp3 della directory /mp3/DT
copy ("/mp3/Anna_Lee.mp3" "/mp3/DT/Anna_Lee.mp3";
# Copia Eileen.mp3 della directory /mp3/SkidRow
move ("/mp3/Eileen.mp3" "/mp3/SkidRow/Eileen.mp3";

Questo, per operazioni di copia o spostamento semplici, cioè che non sfruttino ricorsione o altre opzioni più avanzate, evita di dover chiamare un comando esterno con system() o con l'apostrofo inverso (`), detto anche backtick). È anche possibile confrontare due file con File::Compare:

use File::Compare;
if (compare ("Eileen.mp3", "SkidRow-Eileen.mp3") )
{
  print "Sono uguali!";
} else {
  print "Sono diversi!";
}

Anche in questo caso il modulo mette semplicemente a disposizione una nuova funzione, senza che vengano sfruttati gli oggetti. Analizziamo ora il comodo Text::Wrap, che formatta automaticamente un testo in modo che rientri in un certo numero di colonne. Poniamo di avere il paragrafo riportato nella prima parte di Riquadro 1 in $a e di processarlo con il seguente codice:

use Text::Wrap;
$Text::Wrap::columns = 30;
print wrap (" " x 5, " ", $a);

La seconda riga è di un certo interesse: essa assegna un valore ad una variabile interna Text::Wrap, ed in particolare a quella che determina il numero massimo di colonne per ogni riga. L'ultima linea di codice è quella che fa il lavoro vero e proprio, chiamando la funzione wrap(). I parametri sono, nell'ordine: margine sinistro della prima riga, margine sinistro di tutte le altre righe e variabile contente il testo originale. Il risultato è visibile nella seconda parte di Riquadro 1.

La citazione kantiana prima della formattazione:

Due cose riempiono l'animo di ammirazione e riverenza sempre nuova e 
crescente: il cielo stellato sopra di me la legge morale dentro di me.

... e la stessa dopo essere stata trattata con Text::Wrap...

        Due cose riempiono
 l'animo di ammirazione e
 riverenza sempre nuova e
 crescente: il cielo stellato
 sopra di me la legge
 morale dentro di me.

Inclusi nella distribuzione, soprattutto se la versione è piuttosto recente, si possono trovare moltissimi altri moduli, sufficienti per tutte le operazioni più comuni.


Conclusioni

Con l'analisi di alcune funzioni e di alcuni dei principali moduli si chiude questo piccolo corso di Perl. Gli argomenti da affrontare sarebbero ancora molti, a partire dalla programmazione object oriented di cui si parlava prima per finire alla creazione di moduli propri. Le informazioni date in queste dieci lezioni sono comunque sufficienti, o almeno mi auguro lo siano, a permettere a chi prima non sapeva nulla di Perl di svolgere le operazioni fondamentali. Gli argomenti più avanzati si possono apprendere da un buon libro, come quello indicato in bibliografia, oppure direttamente dalla completissima documentazione in linea. Con "in linea" intendo sia il materiale reperibile su Internet che quello incluso direttamente con l'interprete. La mia speranza è inoltre che questi articoli siano serviti a convincere qualcuno a provare Perl, ad incuriosirlo su questo linguaggio potente e moderno, ad indurlo a scoprirne le infinite sfaccettature, a considerarlo come un'alternativa, un modo di programmare e di pensare libero in un panorama dominato dalle soluzioni proprietarie.

  1. Larry Wall, Tom Christiansen, Jon Orwant - "Programming Perl (3rd edition)", O'Reilly & Associates, Luglio 2000