Sei sulla pagina 1di 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html

Non riscrivere i PDF - riusali!


Di recente mi sono lasciato sedurre dal libro Getting things done: The Art of Stress-Free Productivity di
David Allen. Di che si tratta? Di un sistema di accorgimenti utili per non dimenticare di fare le cose e ridurre
lo stress creato dalle stesse, che tendono a perseguitarti finch non le hai completate. La soluzione, senza temere di
rovinare niente del libro, consiste semplicemente nello scriversele. Geniale, no?
Nella mia strada verso la redenzione dalla smemoratezza ho avuto anche la fortuna di incontrare qualche buon
samaritano. Un ottimo esempio costituito dal sito dedicato al D*I*Y Planner (http://www.diyplanner.com/), una vera
e propria agenda/planner che potete stamparvi da soli, includendo i moduli che volete. Non sto qui a tediarvi sul come
e sul cosa, ma io sono rimasto particolarmente colpito dalla versione hipster PDA del planner suddetto, che viene
fornito in varie forme, la pi interessante quella in PDF con quattro pagine per foglio (trovate il tutto qui:
http://www.diyplanner.com/templates/official/hpda).
Questo bel documentino PDF ha 29 facciate, ognuna fondamentalmente dedicata ad un differente aspetto
dell'organizzazione; ognuno pu sceglierne le parti che ritiene pi utili, tralasciando le altre. A questo punto sono sorti i
miei problemi:
isolare solamente le pagine che io ritenevo utili;
raddoppiare alcune delle pagine suddette, per fare in modo da avere schedine stampate su tutti e due i lati e
risparmiare spazio (posso portare la met delle schede, no?).
E qui, finalmente, entra in ballo Perl insieme al suo modulo PDF::Reuse.

Un po' di contesto
Il modulo in questione lo potete trovare su CPAN, consiglio una ricerca tipo PDF::Reuse. La descrizione
particolarmente illuminante, mi permetto di tradurla:
Questo modulo pu essere utilizzato quando volete produrre, in massa, documenti PDF simili ma non
identici fra loro e riutilizzare modelli, script JavaScript e qualche altra componente. progettato per essere
veloce, e per dare ai vostri programmi la capacit di produrre molte pagine al secondo e documenti PDF
molto grandi, se necessario.
Magari non abbiamo bisogno di andare tanto veloci, per l'idea del riutilizzo stuzzicante!

Getting (this) thing done!


Andiamo ad utilizzare il modulo per i nostri biechi scopi, dunque. Il nostro obiettivo scrivere uno script che ci
consenta di selezionare le pagine desiderate dal file di partenza, in un ordine dato da noi e con ripetizioni (per poter
stampare fronte-retro senza troppo lavoro di stampante). Tanto per perdere un po' di tempo, faremo in modo da
implementare la seguente sintassi sulla riga di comando:
specificare un numero di pagina;
Perl Mongers Italia. Tutti i diritti riservati.

1 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
specificare una pagina ripetuta con PPxNN (PP il numero di pagina, NN il numero di ripetizioni);
specificare un intervallo di pagine con PI-PF (PI il numero di pagina iniziale, PF il numero di pagina finale)
impostare un fattore di ripetizione con xNN (NN un numero);
poter separare ciascun comando con spazi o virgole
Mi sembra abbastanza, non indugiamo oltre:
1
2
3
4

#!/usr/bin/perl
use strict;
use warnings;
use PDF::Reuse;

5
6

my $ifile = shift;
my $reps = 1;

7
8
9

# Apro il documento PDF di uscita - viene inviato su STDOUT


# perche' non diamo alcun argomento a prFile()
prFile();

10
11
12
13

# Itero sui vari comandi in ingresso, che possono essere stati


# separati in chiamata (suddivisi dunque in @ARGV), oppure possono
# essere intervallati da spazi o virgole (da cui la map/split).
foreach my $cmd (map { split /[ ,]/, $_ } @ARGV) {

14
15
16
17
18

# Per default, il comando e` semplicemente un numero di pagina,


# per cui $start e $end sono inizializzate con essa. $itreps
# memorizza il numero di ripetizioni per questa particolare
# iterazione, e per default e` uguale all'impostazione globale
my ($start, $end, $itreps) = ($cmd, $cmd, $reps);

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# Analizza se il comando non specifica semplicemente un numero


# di pagina
if ($cmd =~ /^x(\d+)$/) {
# xNN
$reps = $1;
# Imposta ripetizioni
next;
# e prosegui con il prossimo comando
}
elsif ($cmd =~ /^(\d+)x(\d+)$/) {
# PPxNN
$start = $1;
# Inizio e fine coincidono con $1,
$end
= $1;
# la prima parte di PPxNN
$itreps *= $2;
# Le ripetizioni per questa iterazione
}
elsif ($cmd =~ /^(\d+)-(\d+)$/) {
# PI-PF
$start = $1;
$end
= $2;
}

34
35
36
37

# Posso determinare quali sono le pagine da inserire in questa


# iterazione. Ciascuna pagina nell'intervallo $start .. $end
# viene ripetuta per un numero $itreps di volte
my @pages = map { ($_) x $itreps } $start .. $end;

38
39
40
41
42
43
44
45
46

# Itero sulle pagine determinate e le estraggo dal documento


# di input. Per ciascuna di esse stampo anche un feedback su
# STDERR
foreach my $page (@pages) {
prDoc($ifile, $page, $page);
print STDERR "$page ";
}
} ## end foreach my $cmd (map { split...
print STDERR "\n";

47

# Chiudo il documento PDF di uscita

Perl Mongers Italia. Tutti i diritti riservati.

2 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
48

prEnd();

Il primo blocco (righe 1-4) roba gi vista, vero? Si imposta l'interprete di default per Unix, si utilizzano strict e
warnings tanto per stare sicuri, e si utilizza PDF::Reuse perch.... altrimenti non andremo molto lontano.
Il primo argomento a linea di comando deve essere il nome del file PDF di partenza, per cui lo mettiamo in $ifile e
lo togliamo da @ARGV (il tutto alla riga 5), che a questo punto conterr solamente i vari comandi del tipo elencato in
precedenza. La variabile $reps mantiene il numero di ripetizioni globalmente impostato, e viene fatto partire da 1
(riga 6).
La generazione di un file PDF prevede l'apertura e la chiusura del file stesso, che pu essere mandato su STDOUT.
L'apertura viene effettuata utilizzando la funzione prFile (riga 9), la relativa chiusura - avete indovinato? - si ha con
prEnd (riga 48). In mezzo... scorre il fiume delle pagine!
La riga 13 suddivide i vari comandi impostati. Poich abbiamo detto che possiamo separare i campi con spazi e virgole,
filtriamo @ARGV utilizzando map, suddividendo ciascun argomento singolo in sotto-campi, considerando spazi e virgole
come separatori. In uscita dalla map ci ritroveremo dunque ciascun singolo comando, pronto per l'analisi.
Per generalit, lavoreremo sempre come se stessimo trattando un intervallo di pagine, anche se la prima e l'ultima
possono coincidere. In queste ipotesi, per default assumiamo che il comando sia un semplice numero di pagina, ed
impostiamo $start e $end al valore di $cmd stesso (riga 18). La variabile $itreps contiene il numero di ripetizioni
valido per questo comando (sempre riga 18), sar pi chiaro in seguito. Per default, tale numero di ripetizioni coincide
con quello globale.
Successivamente, andiamo a verificare se, per caso, l'utente ha specificato in realt altri tipi di comandi:
impostazione del fattore di ripetizione (controllo alla riga 21): in questo caso estraiamo il numero di ripetizioni, lo
salviamo in $reps e passiamo direttamente al comando successivo (next alla riga 23);
pagina ripetuta un determinato numero di volte (controllo alla riga 25): di nuovo, sia $start che $end
coincidono, ma viene impostato il valore di ripetizione per questa iterazione pari al prodotto fra il valore attuale e
quanto richiesto nel comando (riga 28);
range di pagine (controllo alla riga 30): in questo caso, estraggo i valori e li imposto in $start e $end (righe 31
e 32).
A questo punto sono pronto per generare pagine, ma faccio l'operazione in due tempi per maggiore leggibilit..
L'intervallo $start .. $end viene espanso (riga 37) mediante la map, che applica eventuali ripetizioni impostate per
il ciclo particolare (l'impostazione memorizzata in $itreps); il risultato di questa espansione viene impostato in
@pages, che conterr dunque la lista delle pagine da estrarre per il dato comando, opportunamente moltiplicate.
Successivamente si itera (riga 41) su tale intervallo, per poter estrarre la pagina dal file iniziale con la funzione prDoc
e stampare un feedback su STDERR (righe 42 e 43). Abbiamo finito - baster concludere la stampa del feedback,
chiudere il file PDF di uscita e terminare.

Prendi e manda!
Perl Mongers Italia. Tutti i diritti riservati.

3 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
Mi capitato di dover automatizzare dei processi di questo tipo:
prendi una determinata risorsa disponibile in una certa rete;
impacchettala in un file .zip;
spediscila via e-mail ad uno o pi indirizzi.
Si, esatto: era qualcosa che mi stato richiesto per due giorni di seguito, il che ha fatto scattare il principio della
lazyness: lavorare subito (di programmazione) per non lavorare pi (fa tutto lo script). Il piano d'azione il seguente:

prendi: quale miglior candidato di LWP?


impacchetta: anche qui CPAN generoso, mettendo a disposizione Archive::Zip;
spedisci: del gran numero di moduli a disposizione, mi caduto l'occhio su Mail::Sender, che devo dire fa bene il
suo lavoro.
Il tutto senza dover passare per file temporanei e simili! Vediamo come...
1
2
3
4
5
6

#!/usr/bin/perl
use strict;
use warnings;
use LWP::Simple;
use Mail::Sender;
use Archive::Zip;

7
8
9

# Un po' di configurazione
my $resource
= "http://www.example.com/resource.pdf";
my $smtp_server = '10.20.30.40';

10
11
12
13

# Prendi il documento
print "Prendo $resource\n";
my $content = LWP::Simple::get($resource)
or die "niente da scaricare, riprovare piu` tardi";

14
15
16
17
18
19
20
21

# Impacchetta
print "Impacchetto\n";
my $zip = Archive::Zip->new();
$zip->addString($content, "risorsa.pdf");
my $zipped;
{
open my $fh, ">", \$zipped;
die "impossibile impacchettare!"

22
23

24
25
26
27
28
29
30
31
32
33
34

# Spedisci via email


print "Spedisco\n";
my $sender = Mail::Sender->new(
{
from
=> '"Pinco Pallino- Automatic" <pinco@example.com>',
to
=> '"Tizio Caio" <tizio@example.com>',
subject => "Risorsa trovata!",
smtp
=> $smtp_server,
}
)
or die "niente da fare!";

35
36
37

# Vogliamo avere un messaggio ed un allegato sul quale vogliamo


# mantere pieno controllo, per cui utilizziamo OpenMultipart(),
# Body() e Part() per costruire il messaggio, e Close() per

unless $zip->writeToFileHandle($fh) == Archive::Zip::AZ_OK;

Perl Mongers Italia. Tutti i diritti riservati.

4 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
38
39
40
41
42
43
44
45
46
47

# concluderlo.
$sender->OpenMultipart()
or die "impossibile chiamare OpenMultipart()";
$sender->Body({msg => "Beh, che si dice?\n"});
$sender->Part(
{
description => "Rassegna stampa del $date",
ctype
=> 'application/x-zip-encoded',
encoding
=> 'Base64',
disposition =>

48
49
50
51
52

qq{attachment; filename="$date.zip"; type="ZIP Archive"},


msg => $zipped,
}
);
$sender->Close() or die "impossibile chiamare Close()";

53
54

# Finito!
print "Fatto\n";

Il primo blocco (righe 1-6) dichiara le nostre intenzioni: fare le cose pulite (strict e warnings), ed utilizzare tanti bei
moduli disponibili. Dopo una prima fase di configurazione (righe 7-9), in particolare riguardante la risorsa da prendere
e il server SMTP da utilizzare per inviare l'e-mail, si entra nel vivo dello script.
La parte prendi si risolve in poche righe (dalla 10 alla 13): si utilizza LWP::Simple, che mette a disposizione
un'interfaccia minimale ma efficace per scaricare una risorsa su web. Se il download non va a buon fine (nel qual caso
la funzione LWP::Simple::get restituisce undef) si esce subito dallo script, con un messaggio di errore. Altrimenti... si
prosegue.
La parte impacchetta si avvale dei servigi di Archive::Zip. Tale modulo in grado di lavorare direttamente sui dati in
memoria, senza bisogno dunque di generare file temporanei. In questo caso abbiamo a che fare con un'interfaccia
orientata agli oggetti, per cui ci facciamo generare un oggetto (riga 16) e chiamiamo il metodo di aggiunta di un
elemento in memoria all'archivio Zip in costruzione mediante il metodo addString (riga 17), impostando anche il nome
che tale sequenza deve avere come "file" nell'archivio ("risorsa.pdf" nel nostro caso).
Il blocco di codice successivo (righe 18-23) realizza un piccolo inganno per Archive::Zip, che abitutato a scrivere su
file gli archivi compressi. Si apre dunque un file con open (riga 20), ma invece di un vero file nel filesystem si apre, in
realt,, una variabile scalare utilizzata come destinazione, ossia $zipped. Tale possibilit si ha solamente dalla versione
5.8 di Perl, quindi attenzione! Il resto della "scrittura su file" procede come di consueto. Anche qui, se qualcosa va
storto si esce immediatamente con un messaggio di errore.
A questo punto la variabile $zipped contiene il file compresso, come se l'avessimo appena letto da un file vero e
proprio. Siamo pronti alla spedizione, che si avvale - l'avreste mai detto? - di un'interfaccia ad oggetti. Questa volta il
costruttore richiede un po' pi di parametri (righe 26-34); niente di trascendentale come si pu vedere, con la solita
interruzione se qualcosa va male.
Per spedire un'e-mail con un messaggio ed un allegato potremmo utilizzare il metodo MailFile, che ci consente di fare
tutto in un colpo solo; in questo caso, per, vogliamo avere un controllo pi stretto sull'header relativo al file da
spedire, per cui scegliamo la strada pi lunga costruendo il messaggio pezzo per pezzo.
Utilizziamo allora OpenMultipart (riga 39), che imposta appunto il messaggio come composto da pi parti. Il corpo del
messaggio impostato alla riga successiva con il metodo Body (ok, in questo caso ce lo potevamo evitare, visto il
Perl Mongers Italia. Tutti i diritti riservati.

5 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
messaggio!), mentre l'allegato viene impostato con il metodo Part (righe 42-51), che pu essere chiamato anche pi
volte per aggiungere ulteriori allegati. Anche in questo caso i parametri passati sono abbastanza ovvi.
La chiusura del messaggio con il metodo Close (riga 52), infine, ha anche l'effetto di far partire l'invio vero e proprio
dell'e-mail. Il che termina anche la lista di quello che dovevamo fare!

Che fanno al cinema?


Premessa: l'esempio dato si avvale di informazioni liberamente disponibili su Internet. L'autore dell'esempio non ha
trovato nessun tipo di indicazione avversa al trattamento automatico dei dati disponibili sul sito Virgilio.it, nemmeno un
semplice robots.txt.
Se vi piace il cinema, sicuramente utilizzerete qualche servizio on-line per vedere dove, e quando, fanno il film che vi
interessa tanto vedere. Personalmente utilizzo il servizio messo a disposizione da Virgilio.it (http://www.virgilio.it/), che
consente di restringere la ricerca alla propria citt, e di ordinare i risultati sia per sala che per titolo. Come al solito,
per, a me non basta; prima di tutto perch mi piace lavorare a riga di comando (ad esempio per utilizzare
l'onnipresente grep), secondo poi perch a volte ho voglia di restringere la ricerca a determinate fasce orarie. Cosa
meglio di uno script Perl, allora?
I film per le sale di Roma sono reperibili all'indirizzo http://film.spettacolo.virgilio.it/cinema/insala.php
/citta=10661. Diamo un'occhiata alla parte interessante di una pagina di esempio:
<</span><span style="font-weight: bold; font-size: 10pt; font-family:
'Courier New'">table</span><span style="font-size: 10pt; font-family:
'Courier New'"> cellpadding=5 cellspacing=1 border=0 width=100%
class=listaElenco>
<tr>
<td width=15%><a href="/cinema/insala.php?citta=10661">Roma</a></td>
<td width=30%><a href=#
onclick="PopUpWin('popsala.php?id=469','Sale','scrollbars=yes,width=300,height=350')">Alhambra</a>
- Sala 2<br>
Via Pier delle Vigne, 4 <br>
<i>Tel.</i> 0666012154<br>
<i>Orari:</i> 16:00 18:15 20:15 22:40</td>
<td width=55%><a
href="http://film.spettacolo.virgilio.it/cinema/scheda.php?film=30689">Bambole
russe</a> (Francia/Gran Bretagna, 2005)<br>
<i>con</i> R. Duris, C. De France, A. Tautou<br>
<i>genere</i> commedia-sentimentale</td>
</tr>

Quello che segue si avvale di tutta la potenza di LWP::Simple (per scaricare la pagina degli spettacoli), e di
HTML::TableExtract per analizzare il file scaricato senza doversi destreggiare troppo nell'infida terra del parsing HTML.
1
2
3
4
5

#!/usr/bin/perl
use strict;
use warnings;
use HTML::TableExtract;
use LWP::Simple;

Perl Mongers Italia. Tutti i diritti riservati.

6 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
6
7

# L'URL di base per Virgilio Cinema


my $baseurl = 'http://film.spettacolo.virgilio.it/cinema/insala.php';

8
9
10
11
12

# La citta` di roma e` all'indice 10661


my $url = "$baseurl?citta=10661";
print STDERR "scarico da [$url]... ";
my $html = get($url) or die "\ncould not get the page";
print STDERR "fatto\n";

13
14
15
16
17
18
19

# Effettua il parsing del file, isolando le tabelle con classe


# "listaElenco" - ce ne dovrebbe essere una sola
my $te = HTML::TableExtract->new(
attribs
=> {class => 'listaElenco'},
keep_html => 1
);
$te->parse($html);

20
21
22
23
24
25
26

# Estrai i dati dalle tabelle trovate


my %data;
foreach my $table ($te->tables) {
print STDERR "Trovata tabella\n";
foreach my $row ($table->rows) {
my $title = film_title($row->[2]);
my $place = film_place($row->[1]);

27
28
29
30
31
32

# $data{$title}{$place} contiene/deve contenere un riferimento


# ad un array contenente gli orari degli spettacoli
push @{$data{$title}{$place}},
split(/\s+/, film_schedule($row->[1]));
} ## end foreach my $row ($table->rows)
} ## end foreach my $table ($te->tables)

33
34
35
36
37
38
39

# Stampe finali, ordinate per titolo, sala e orario


foreach my $title (sort keys %data) {
foreach my $place (sort keys %{$data{$title}}) {
my @schedules = sort @{$data{$title}{$place}};
print "[$title] [$place] [@schedules]\n";
}
} ## end foreach my $title (sort keys...

40
41
42
43
44
45

# Estrazioni di titolo, sala ed orari


sub film_title {
return ($_[0] =~ /<(?:a|b).*?>(.*?)<\/(?:a|b)>/i ? $1 : '');
}
sub film_place
{ return ($_[0] =~ /<a.*?>(.*?)<\/a>/i ? $1 : '') }
sub film_schedule { return ($_[0] =~ /\s+([\d :]+)$/i
? $1 : '') }

L'inizio il solito (righe 1-5): utilizziamo strict e warnings perch siamo coscienziosi, ed includiamo anche i due moduli
che ci permetteranno di scrivere lo script senza troppe ansie. Alla riga 7 viene impostata l'URL di base del servizio di
Virgilio, che viene poi personalizzata alla riga 9, dove viene anche impostata la citt da analizzare. Il download viene
effettuato mediante il metodo get (riga 11), importato automaticamente da LWP::Simple; questo restituisce undef se
qualche cosa va storto, nel qual caso lo script terminer con un messaggio di errore.
A questo punto la pagina scaricata si trova nella variabile $html. Il modulo HTML::TableExtract mette a disposizione
un'interfaccia ad oggetti, per cui prima di tutto dobbiamo procurarcene uno (riga 15), per poi seguire i passi necessari
all'analisi della tabella che ci interessa. Si noti che, nel costruttore dell'oggetto, vengono impostate le condizioni di
filtraggio: avendo notato che la tabella che ci interessa contiene l'attributo di classe 'listaElenco', indichiamo tale
restrizione in modo da eliminare le altre tabelle (possibilmente) presenti nella pagina.

Perl Mongers Italia. Tutti i diritti riservati.

7 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
Dopo aver effettuato il parsing della pagina scaricata (riga 19), iteriamo sulle varie tabelle individuate (riga 22). Questa
iterazione in realt non necessaria, visto che la tabella dovrebbe essere unica, ma meglio andare sul sicuro.
Nell'iterazione, la variabile $table itera sulla lista delle tabelle restituita dal metodo tables: nel nostro caso, come detto,
tale lista conterr solo un elemento.
La tabella $table viene analizzata riga per riga (linea 24); il metodo rows consente infatti di estrarre una lista di tutte le
righe disponibili. Il titolo e la sala vengono identificati dai relativi campi nella riga della tabella (righe 25 e 26),
utilizzando delle funzioni ad-hoc implementate in fondo allo script (righe 41-44). Queste due informazioni consentono
di accedere ad una lista di orari contenuta nella struttura multilivello rappresentata dall'hash %data. La prima chiave
costituita dal titolo, la seconda dalla sala e l'elemento puntato un riferimento anonimo ad un array di orari, che viene
riempito con gli orari desunti dalla riga sotto analisi (righe 29-30).
Quando l'analisi della pagina terminata, la struttura %data contiene tutto quello che ci serve per sbizzarrirci: inserire
i dati in un database, mettere a disposizione un'interfaccia di ricerca... Nel nostro caso siamo molto pi prosaici:
facciamo una semplice stampa dei dati. Poich la struttura multilivello, annidiamo un ciclo per ciascuno di essi
(tranquilli, sono solo due): al livello esterno troviamo i titoli (riga 34), che iteriamo in forma ordinata, mentre al livello
interno troviamo la sala (riga 35), anche qui iterata in ordine alfabetico. Per comodit, estraiamo l'array degli orari (riga
36) e procediamo con la stampa (riga 37).
Si noti che la stampa di un array fra virgolette doppie fa s che gli elementi siano separati fra loro dalla stringa
contenuta nella variabile speciale $", che per default corisponde ad uno spazio. Una cosa in meno a cui pensare!

Vietato ai minori
C' un modulo su CPAN che andrebbe vietato ai minori. Prima che vi prendiate a gomitate per raggiungere il browser
pi vicino e dare fondo alla vostra fantasia per cercare roba tipo Free::Nude su CPAN fatemi andare avanti, potreste
perdere il vostro tempo!
Ricorderete che a scuola, specialmente alle elementari ed alle medie, la parola calcolatrice era stata bandita dal
vocabolario di un qualsiasi insegnante di matematica. La ragione semplice: se devi imparare a fare i conti, ossia se
devi imparare come si fanno i conti e soprattutto perch si fanno in quel modo, avere la pappa pronta in una
macchinetta che sa sempre la risposta giusta non aiuta molto. Quando hai capito, invece, puoi benissimo permetterti di
dimenticare tutto ed usarla!
Con la scrittura credo debba essere uguale: dare delle scorciatoie prima che certi concetti siano ben radicati pu essere
molto, molto pericoloso. E qui veniamo al modulo proibito, quello che mette ordine nel testo al posto nostro:
Text::Beautify. I minori sono gentilmente pregati di tornare alle grammatiche ed alle antologie, grazie.
Text::Beautify consente di eliminare quelle noiosissime violazioni delle regole di buona scrittura che rendono alcuni
testi tanto noiosi da leggere. Se aprite un qualsiasi libro stampato (o quasi), potete infatti notare che vengono
rispettate queste regole semplicissime:
i periodi iniziano con la lettera maiuscola (chi ha voglia di premere quel dannato tasto?);
prima di un carattere di interpunzione (s, la punteggiatura) non va mai messo uno spazio...
... ma dopo si (stiamo scherzando? Non ho tempo da perdere!!!);
Perl Mongers Italia. Tutti i diritti riservati.

8 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
se c' un carattere di interpunzione, normalmente non ne serve un altro subito dopo (Tot docet: punto, anzi no:
due punti!);
le parole sono separate da un solo spazio;
gli spazi all'inizio ed alla fine normalmente non servono.
Sono regole semplici, ma pur sempre regole - che vengono regolarmente infrante in moltissimi testi scritti "di fretta",
come ad esempio nelle e-mail o nei commenti che vengono lasciati in giro su Internet.
Text::Beautify viene allora incontro alla pigrizia imperante: dategli un testo e tirer fuori una versione pi umana e
gradevole da leggere. Vediamo un esempio:
1
2
3
4

#!/usr/bin/perl
use strict;
use warnings;
use Text::Beautify qw( beautify );

5
6
7
8

my $text = "
qualcosa che non
print "Originale:
'$text'\n";
$text = beautify($text);
print "Trasformato: '$text'\n";

va bene ,direi ! ";

Lo script di esempio di una banalit sconvolgente. Da notare che, in fase di utilizzo del modulo (riga 4), viene
specificato il parametro beautify, che ha l'effetto di importare la funzione e renderla direttamente visibile alla riga 7.
Se non avessimo specificato il parametro, visto che l'autore del modulo (Jos Alves de Castro, anche noto come cog) fa
le cose per bene, non avremmo avuto modifiche sul nostro spazio dei nomi ed avremmo dovuto cambiare la riga 7 in:
7bis

$text = Text::Beautify::beautify($text);

Niente di sconvolgente, ma in uno script cos semplice possiamo permetterci di importare la funzione senza temere di
incappare in collisioni.
L'uscita semplicemente:
Originale:

'

qualcosa

che non

va bene ,direi ! '

Trasformato: 'Qualcosa che non va bene, direi!'

Comodo vero? Ma che non sia una scusa per scrivere male!

E se...
... volessi mettere a posto la punteggiatura, ma lasciare le lettere di inizio periodo cos come sono? O tenere le
punteggiature multiple?
Non sono richieste cos campate in aria, mi rendo conto; fortunatamente se n' reso conto anche cog, che ha messo a
disposizione la possibilit di disabilitare le caratteristiche che non interessano. Una rapida occhiata al manuale (che,
ricordo, accessibile con il comando perldoc Text::Beautify dopo che si installato il modulo) ci indica subito
la strada: utilizzare la funzione disable_feature().
1
2
3

#!/usr/bin/perl
use strict;
use warnings;

Perl Mongers Italia. Tutti i diritti riservati.

9 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
4

use Text::Beautify qw( beautify disable_feature );

5
6

my $brutto = " cog ,sapete,,


print "Brutto: '$brutto'\n";

7
8

my $errato = beautify($brutto);
print "Errato: '$errato'\n";

9
10
11

va scritto sempre

minuscolo ! ";

disable_feature('uppercase_first');
my $bello = beautify($brutto);
print "Bello : '$bello'\n";

La riga 4 non ci sorprende: poich abbiamo bisogno di un'altra funzione da quel modulo, indichiamo che vogliamo
importarla nel nostro spazio dei nomi.
Alla riga 7 utilizziamo beautify() esattamente come prima, ma il risultato che otteniamo errato, perch la parola

cog viene trasformata con l'iniziale maiuscola. qui che si innesta la funzione disable_feature(): alla riga 9 si disabilita
la trasformazione in maiuscola della prima lettera del periodo, con buona pace di cog. Il risultato finale :
Brutto: ' cog ,sapete,, va scritto sempre
minuscolo ! '
Errato: 'Cog, sapete, va scritto sempre minuscolo!'
Bello : 'cog, sapete, va scritto sempre minuscolo!'

Attenzione agli oggetti


Il modulo mette a disposizione un'interfaccia orientata agli oggetti: mi sembrata una cosa piuttosto golosa vista la
possibilit di abilitare o disabilitare determinate caratteristiche di trasformazione del testo. Purtroppo, per, nella
versione 0.08 (quella disponibile al momento) il supporto per l'interfaccia ad oggetti sembra limitarsi alla funzione
beautify() e non si estende all'impostazione delle feature di interesse. Tutto sommato, quindi, consiglierei di evitare
questa interfaccia per il momento, ed utilizzare quella puramente funzionale.

Upload, che passione!


Mi capitato di recente di dover spedire dei file molto grandi ad un paio di colleghi; con molto grandi intendo maggiori
di una ventina di megabyte, che gi abbastanza al di l della dimensione massima di un allegato che non mi
infastidisce ricevere. Visto che ho un server a disposizione, mi sembrato naturale mettere su un piccolo "servizio"
personale per l'upload dei file. Ecco cosa venuto fuori:
1
2
3
4
5
6

#!/usr/bin/perl -T
use strict;
use warnings;
use CGI;
use File::Basename qw( basename );
use Readonly;

7
8
9
10

# Configuration
Readonly my $fieldname
=> 'uploaded_file';
Readonly my $base_directory => "/percorso/per/upload";
Readonly my $base_url
=> "http://mio.server.it/upload";

11

my $q = CGI->new();

12
13

my $msg;
$msg = gestisci_upload_entrante() if $q->param('uploaded_file');

Perl Mongers Italia. Tutti i diritti riservati.

10 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
14
15
16
17
18
19
20
21
22

print $q->header(),
$q->start_html('Upload semplice semplice');
print $q->h1($msg), $q->hr() if $msg;
print $q->h1("Upload"),
$q->start_multipart_form(),
'File: ', $q->filefield(-name => $fieldname), $q->br(),
$q->submit(-name => 'Invia'),
$q->end_form(),
$q->end_html();

23
24

sub gestisci_upload_entrante {
chdir $base_directory or return "chdir(): $!";

25
26
27
28
29
30
31
32
33

my $fn = basename($q->param($fieldname))
or return "nessun nome di file";
$fn =~ s/[^\w\d .-]//g;
($fn) = $fn =~ /([\w\d .-]+)/;
return "Errore: nessun nome di file valido" unless $fn;
my @alphabeth = ('a' .. 'z');
while (-e $fn) {
$fn = $alphabeth[rand @alphabeth] . $fn;
}

34
35
36
37
38
39

my $fh = $q->upload($fieldname)
or return "Problemi nell'upload di '$fn'";
binmode $fh;
open my $out, '>', $fn or return "open(): $!";
binmode $out;
while (read $fh, my $data, 8192) { print {$out} $data }

40
41

return "Upload ok: $base_url/$fn";


}

L'inizio (righe 1..6) pi o meno il solito: usiamo strict, warnings ed i moduli CGI, visto che stiamo realizzando
uno script CGI, e File::Basename, che ci sar utile per estrarre il nome del file da un path completo.
Avete fatto caso all'opzione -T sulla prima riga? Serve ad attivare il cosiddetto taint mode, ossia una modalit un po'
paranoica che considera tutto quello che viene dall'esterno dello script come potenzialmente pericoloso, e quindi
impedisce che questi dati siano utilizzati in determinate funzioni (come, per fare un esempio, open() nella riga 37).
Questa modalit non vi impedisce di fare stupidaggini, ovviamente, ma vi costringe a prestare una particolare
attenzione ai dati che provengono dal browser, impedendovi di abbassare la guardia.
Le righe 7..10 riportano alcune configurazioni: in particolare, il campo nel form CGI che servir per l'upload verr
chiamato uploaded_file, i file verranno messi tutti dentro la directory /percorso/per/upload e questi saranno
poi visibili attraverso la URL base http://mio.server.it/upload.
Per evitare di spargere queste stringhe in giro per lo script, correndo il rischio di sbagliare qualcosa, ho deciso di
utilizzare delle variabili. L'approccio migliore, in questi casi, consiste nell'utilizzare un sistema che forzi l'utilizzo di
queste variabili come costanti; usiamo qui il modulo Readonly, che fa s che le tre variabili $fieldname,
$base_directory e $base_url non possano essere (involontariamente o meno) modificate.
La riga 11 dichiara e definisce $q, la variabile che verr utilizzata in tutto lo script per la gestione dell'interfaccia CGI.
La riga 13 chiama la funzione gestisci_upload_entrante() nel caso sia definito il parametro CGI in ingresso
uploaded_file; in questo modo, saremo in grado di utilizzare questo script sia per presentare una pagina per
Perl Mongers Italia. Tutti i diritti riservati.

11 of 12

Flavio Poletti

Cinque cose che gi sapevate di poter fare in


Perl
http://www.perl.it/documenti/articoli/2006/10/cinque-cose-che-1.html
l'upload che per gestire l'upload stesso.
La funzione gestisci_upload_entrante(), che vedremo fra poco, restituisce un messaggio che pu essere
utilizzato come riscontro per l'utente; questo messaggio viene raccolto nella variabile $msg.
Le righe 14..22 si occupano di generare la risposta da inviare al browser, utilizzando le funzioni standard del modulo
CGI. Se la variabile $msg non vuota, il messaggio contenuto viene incluso all'interno della pagina (riga 16). Le righe
18..21 includono nella pagina il form per l'upload: poich questo si deve occupare di inviare dei file, occorre prestare
attenzione ad utilizzare il metodo start_multipart_form() invece del semplice start_form(), altrimenti non
verr impostato il Content-Type corretto e non riceveremo nulla! Il resto del form banale: un campo filefield per
il file, ed un tasto per l'invio.
Le righe 23..41 contengono l'implementazione della funzione di gestione dell'upload, ossia
gestisci_upload_entrante(). Per prima cosa ci si sposta nella directory obiettivo $base_directory, con il
consueto controllo dell'errore (riga 24).
Le righe 25..33 si occupano di determinare il nome del file da utilizzare per salvare l'upload entrante, con alcuni
controlli paranoici. In particolare, attraverso la funzione basename() ci si assicura che non si cerchi di mettere il file in
una directory differente da quella in cui ci siamo spostati alla riga 24; la sostituzione alla riga 27 elimina tutto quello
che non mi piace in un nome di file (di base vengono mantenute solamente lettere, cifre e poco pi)
La riga 28 merita un commento. Abbiamo detto che stiamo operando in modalit taint, a causa dell'opzione -T
impostata nella prima riga, e che questa modalit fa s che qualsiasi dato "esterno" sia considerato non sicuro da
utilizzare con certe funzioni, come ad esempio open() alla riga 36. Questo crea un problema: come facciamo a
liberare $fn da questo marchio infamante? Semplice: dobbiamo estrarre del testo utilizzando un'espressione regolare,
perch questo l'unico modo a disposizione per prendere dati da una variabile marcata senza che il risultato sia
anch'esso marcato. Si effettua allora un semplice match di tutti gli elementi consentiti (che sono anche gli unici rimasti
nella stringa a questo punto, ma la prudenza non mai troppa) all'interno di una parentesi di cattura, ed il risultato
viene re-immesso dentro $fn. Le parentesi intorno al nome della variabile prima dell'assegnazione servono a forzare
un contesto lista sul match, in modo che questo restituisca la lista ($1, $2, ...), che proprio quel che ci serve.
Se non avanza proprio niente, alla riga 29 si esce. Le righe 30..33 servono a trovare un nome di file che sia differente
da quello dei file gi presenti, in modo da evitare sovrascritture. Questo sistema, ovviamente, non mette al riparo da
upload contemporanei, ma sinceramente non era un mio requisito quando ho fatto lo script!
La riga 34 raccoglie un filehandle da cui possibile leggere il file inviato con l'upload; ci assicuriamo di effettuare una
lettura binaria senza conversioni implicite (riga 36), apriamo il file di uscita (riga 37), lo impostiamo in modalit binaria
(riga 38) e poi siamo pronti a fare la copia. Potevamo utilizzare altri sistemi, lo so, ma questo risolve!
Perl Mongers Italia. Tutti i diritti riservati.

12 of 12

Potrebbero piacerti anche