Iniziamo allora a vedere, riga per riga,
il nostro programma.
1 #!/usr/bin/perl
Iniziamo ovviamente con la dichiarazione iniziale che
questo è uno script in
Perl. Se questo script ha i permessi di esecuzione, il
sistema userà il compilatore presente in /usr/bin/perl per compilare ed eseguire
lo script. Questa prima riga viene, in gergo, detta she-bang, dal nome dei primi
due caratteri che la compongono (contrazione di sharp-bang).
Le righe 2..4 caricano tre librerie nel programma.
2 use strict;
Questa, benchè richiamata sintatticamente come un modulo, è in realtà una direttiva del Perl che,
attivando alcuni controlli in fase di compilazione, costringe il
programmatore ad essere più attento nella scrittura del codice. Per esempio, per
poter utilizzare una variabile è obbligatorio prima dichiararla. Questo aiuta, per esempio,
a evitare errori di battitura nello scrivere le variabili. Questi, di solito,
non producono errori in fase di compilazione ma in fase di esecuzione
e non sono di
immediata soluzione.
Per esempio, senza questa direttiva il codice seguente
viene correttamente compilato ed eseguito ma il risultato non e' quello che ci
si aspetta
$pippo = 5;
print $pipo;
Aggiungendo la direttiva strict il programma produce
un errore nella riga di inizializzazione della variabile $pippo, in quanto essa non è precedentemente definita. Riscrivendo il
programma in questo modo:
use strict;
my $pippo;
$pippo = 5;
print $pipo;
immediatamente ci si accorge dell'errore di digitazione
nell'ultima riga in quanto lì il programma produce un errore essendo la
variabile $pipo non precedentemente dichiarata.
3 use Net::IRC;
Questo modulo implementa la classe che incapsulerà la
sintassi del protocollo IRC permettendoci di instaurare un dialogo con un server
IRC e eventualmente con i canali e i client della rete senza sapere come, in realtà, funziona il protocollo
IRC. Vedremo nelle righe 20..24 come si inizializza un oggetto di questa classe.
4 use Data::Dumper;
Cos'è questo modulo che siamo andati a caricare? Il modulo
Data::Dumper è uno dei più utili del
Perl. Questo modulo prende
una variabile scalare e restituisce il contenuto della variabile. Dato che in Perl una variabile scalare è
praticamente tutto, da un numero, ad una stringa, ad un riferimento ad un array,
ad un riferimento ad un hash, ad una classe, questo modulo restituisce una versione
"visiva" dei dati della variabile.
Facciamo un esempio e prendiamo uno scalare contenente un
riferimento ad un array cosi fatto
$integers = [1,2,3,4,5,6,7];
Se usiamo il modulo Data::Dumper
e ne stampiamo il risultato esso sarà
$VAR1 = [1,2,3,4,5,6,7];
che, a parte il nome della variabile, è proprio il
contenuto della stessa.
Va da se che questo modulo è utilissimo in fase di debug
in quanto ci permette di vedere, in qualsiasi momento, il contenuto di una
qualsiasi struttura e quindi i valori in essa contenuti.
Ma, la cosa bella è che questo modulo può essere
tranquillamente usato per serializzare una variabile ossia scrivere questa
variabile in un qualche posto, tipo un file, per riutilizzarla successivamente
inalterata, magari in un altro programma. Questo è possibile perchè quello che
il Data::Dumper stampa altro non è che il codice
Perlnecessario a inizializzare una
variabile che ha gli stessi valori della variabile sorgente e questo è proprio
la definizione di serializzazione di una variabile. Mentre altri linguaggi usano
varie forme, per esempio binarie, per la serializzazione, il
Perl (come ad esempio anche il Lisp) può serializzare le variabili generando
il codice necessario a definirle e
quindi la funzione di deserializzazione in Perl,
altri non è se non la funzione eval()
che elabora una stringa di caratteri come se fosse codice
Perl.
Se facciamo
eval(Data::Dumper::Dumper($integers))
non avremo fatto altro che definire una nuova variabile
$VAR1 che è, come valori e tipo, identica a
$integers. Vedremo nella funzione
output_log alla riga 64 e successive il caso reale di utilizzo di questo
modulo per la serializzazione dei dati applicato al nostro esempio.
5 use vars qw($find_user $output $status);
Questo ulteriore modulo è, in realtà, un'altra direttiva
che definisce una o più variabili con
visibilità globale ossia delle
variabili definite in tutte le parti del programma, sia nel package
main che nelle sue
subroutine. In parole più semplici, la variabile $find_user, che identificherà il nickname da trovare, è definita in ogni
punto del programma ed avrà sempre lo stesso valore.
La seconda variabile $output definirà il percorso del file dove andremo a serializzare
lo stato del nick.
Queste prime due variabili in realtà, avrebbero potuto
avere validità locale nell'ambito delle singole funzioni in cui sono coinvolte.
Per esempio, $output serve solo nella funzione output_log e avrebbe potuto essere definita solo in quel contesto. Si è
scelto una tipo globale in modo da definire queste variabili diciamo di
"configurazione" tutte nello stesso punto all'inizio del programma (linee
10 e 11)
L'ultima variabile globale $status rappresenterà lo stato del nostro nick e sarà quella che
dovrà esssere serializzata. Questa è una "vera" variabile globale in quanto sara
inizializzata nel main (linea 12) ma ulteriori
informazioni verranno aggiunte alla variabile durante le varie chiamate su IRC
(linee 40, 42 e 59) e infine essa verrà serializzata in 67.
E qui si vede l'utilità di una variabile globale. Il fatto
che in 67 troviamo la variabile con i dati che sono stati valorizzati nel corso
dell'esecuzione del programma è dovuto solo ed esclusivamente al fatto che
questa sia stata definita come globale.
6 my $irc_server = 'irc.azzurra.org';
7 my $irc_port = 6667;
8 my $irc_nick = 'emi_finder';
9 my $irc_name = 'Emi, r u there?';
10 $find_user = 'emi';
11 $output = '/www/ebruni.it/www/irc_status.txt';
In queste righe definiamo e inizializziamo alcune
variabili di configurazione. L'utente che utilizzerà il nostro script dovrà solo
modificare questa sezione per adattarla alla propria situazione. Il resto del
programma non dovrebbe essere toccato.
Queste variabili, di ovvio significato, definiscono
rispettivamente il server IRC della nostra rete preferita, la porta del server,
il nickname utilizzato da questo script quando si collegherà alla rete e la sua
descrizione.
Si valorizzano quindi le due variabili globali del
nickname da monitorare e il file per la serializzazione.
12 $status = {timestamp => scalar(localtime)};
Qui iniziamo a valorizzare la variabile che conterrà, alla
fine, i dati sullo stato del nostro nick. La variabile è inizializzata come un riferimento
ad un hash '{}' di elementi chiave => valore. Come primo, aggiungiamo un
elemento chiamato timestamp con l'orario in cui lo script
è stato eseguito. Questo ci servirà, eventualmente, per sapere in che momento è
stato lanciato questo script l'ultima volta e quindi in che momento è stata
fatta la rilevazione del nostro nick e quindi a che momento risalgono i
dati rilevati.
13 my %event_lookup = (
14 'endofmotd' => 376,
15 'ison' => 303,
16 'whoischannels' => 319,
17 'list' => 322,
18 );
Qui c'è l'unica parte relativa al protocollo IRC. Ogni
messaggio server-to-client è definito da un numero che identifica il tipo di
messaggio. Qui identifico una variabile di ricerca che mi permetterà di usare
dei nomi invece che dei numeri.
Per esempio,
la fine del MOTD (message
of the day) è identificato dal numero 376 ma da ora posso ora anche usare la
variabile $event_lookup{'endofmotd'} al posto del
numero.
19 my $irc = new Net::IRC;
20 my $conn = $irc->newconn(
21 Nick => $irc_nick,
22 Server => $irc_server,
23 Port => $irc_port,
24 Ircname => $irc_name);
Inizializzo il client IRC. Queste righe corrispondono a
lanciare un normale client IRC con GUI tipo Xchat e configurare i suoi parametri
per entrare in IRC.
25 $conn->add_global_handler($event_lookup{endofmotd}, \&on_connect);
26 $conn->add_handler($event_lookup{ison},\&on_ison_reply);
27 $conn->add_handler($event_lookup{whoischannels},\&on_whois_reply);
28 $conn->add_handler($event_lookup{list},\&on_list_reply);
Dato che a rispondere ai messaggi di IRC non ci sarà un
uomo ma una macchina, devo istruire il programma su cosa dire in certi
determinati eventi. In particolare ci interesserà gestire risposte agli eventi
di tipo endofmotd, ison, whoischannels e list.
Vedremo a breve perchè proprio questi ma ora ci preme
osservare come, a fronte dell'esecuzione di un dato evento, sia stata associata
l'esecuzione di una data function che dovrà gestire l'evento.
29 $irc->start;
Ci colleghiamo a IRC.
30 sub on_connect {
31 my $self = shift;
32 $self->ison($find_user);
33 }
Quando ci colleghiamo ad IRC con un client tipo XChat,
vediamo che, prima di poter entrare in un canale, il server ci manda tutta una
serie di informazioni. Una delle ultime informazioni è il
MOTD che viene
terminato dall'END
OF MOTD.
Come definito nella riga 25, al
ricevimento da parte del nostro client dell'END
OF MOTD deve essere eseguita la funzione on_connect.
A questa funzione viene passato un parametro che contiene un istanza del nostro
client IRC che incapsula i metodi per comunicare con il server IRC.
Dato che, se siamo dentro a questa funzione, ci possiamo
tranquillamente considerare connessi al server IRC, passiamo allo step 2 del
flusso del nostro programma definito sopra e
andiamo ad eseguire il comando
ISON
(linea 32 ).
34 sub on_ison_reply {
35 my $self = shift;
36 my $event = shift;
37 if ($event->{args}->[1] eq $find_user . " ") {
38 # user on line
39 $self->whois($find_user);
40 $status->{online} = 1;
41 } else {
42 $status->{online} = 0;
43 $self->quit;
44 }
45 }
Una volta che il server
ha risposto al nostro
comando ISON, siamo dentro alla funzione on_ison_reply. Cosa ci restituisce il server
a fronte di una richiesta ISON?
Come potete vedere il
risultato è il nick stesso richiesto se l'utente con quel nick esiste o una
risposta 303 senza nick nel caso esso non sia presente nella rete.
La risposta del server, lato script, la troviamo nel
secondo parametro passato alla funzione che abbiamo salvato nella variabile
$event. Un Data::Dumper (vedete l'utilità :-), di questa variabile risultata
essere:
$VAR1 = bless( {
'nick' => 'caltanet.azzurra.org',
'host' => '',
'user' => '',
'from' => 'caltanet.azzurra.org',
'userhost' => '@',
'format' => 'server',
'args' => [
'emi_finder',
'emi '
],
'type' => 'ison',
'to' => [
''
]
}, 'Net::IRC::Event' );
Come potete vedere questa variabile è un po' complessa:
proviamo ad analizzarla ugualmente. Essa risulta essere un oggetto: bless
assegna infatti un hash di proprietà ad una interfaccia che, in questo caso,
scopriamo essere di tipo Net::IRC::Event. Leggiamo anche le proprietà
valorizzate per questo oggetto: quello che ci interessa ora è la chiave
args, che osserviamo essere un riferimento ad un array di elementi che non sono
altro che uno split della riga di risposta del server (confrontatela con
la risposta "raw" del
server)
Se l'utente non esiste, ossia se l'elemento con indice
1 della chiave args dell'oggetto $event è vuoto, impostiamo allora la chiave
"online" della variabile di stato a 0 e usciamo (linea 42
e 43), altrimenti impostiamo questa chiave a 1 e
chiediamo al server di ottendere l'WHOIS
del nickname (linea 39 e 40).
46 sub on_whois_reply {
47 my $self = shift;
48 my $event = shift;
In risposta alla nostra richiesta
di WHOIS
il server ci risponde con tutta
una serie di informazioni sul nick. Tra tutte queste risposte, l'unica che
ci interessa è la risposta 319 che contiene l'elenco dei canali su cui l'utente
è presente.
Come il nostro client riceve questa riga di risposta, in
accordo con la nostra richiesta alla riga 27, viene
eseguita la funzione on_whois_reply.
49 my @visible_channels = split(/ /,$event->{args}->[2]);
Anche in questo caso con un Data::Dumper stavolta della
variabile $event->{args}
$VAR1 = [
'emi_finder',
'emi',
'@#controguerra @#areanetworking @#geeks @#webgui @#pescaralug @#telug '
];
si scopre che la lista che ci interessa si trova
nell'indice 2 dell'array per cui separandola in base agli spazi (split) si
otterrà un array con il nome di un canale in ogni elemento.
50 foreach (@visible_channels) {
Per ogni canale
51 s/^@//;
rimuoviamo l'eventuale "@" presente su quei canali in cui
l'utente è operatore ma che, per i nostri scopi, non è una informazione
interessante
52 $self->list($_);
53 }
e, per avere informazioni sul canale, ne richiediamo il
LIST.
54 $self->quit;
55 }
Una volta eseguita questa serie di
LISTsu
tutti i canali, usciamo.
56 sub on_list_reply {
57 my $self = shift;
58 my $event = shift;
A fronte di ogni comando
LIST
eseguito sopra il server ci
rispondera ripetendo il
nome del canale e il suo topic in un messaggio con codice 322 che, in accordo
con quanto da noi specificato, verrà processato dalla
funzione on_list_reply.
59 $status->{channels}->{$event->{args}->[1]} = $event->{args}->[3];
60 }
Non perdiamoci proprio ora. Questa è la riga più
complicata di tutto il programma ma solo perchè su di una unica riga sono state
eseguite più operazioni. E' questo uno dei punti a favore del
Perl.
Vediamo di scrivere questa riga in forma esplicita:
my $channels = $status->{channels};
my $channel_name = $event{args}->[1];
my $channel_topic = $event{args}->[3];
$channels->{$channel_name} = $channel_topic;
Quindi, prendiamo l'elemento con chiave channels della variabile di stato,
recuperiamo l'elemento 1 e 3 dell'array di risposta del server e creiamo una
chiave su $channels con chiave uguale al nome del canale e valore il topic.
Osservate che, alla prima chiamata della sub on_list_reply
nell'hash $status non esiste alcuna chiave channels quindi
$channels non è
definita. Mancherebbe quindi una riga in cui si definisce che $channels è un
riferimento ad un hash
$channels = {}
ma questa cosa viene implicitamente fatta dalla associazione del topic con il
nome del canale. Ovviamente, una volta che $channels, ossia $status->{channels},
è stata definita, se si tenta di valorizzare qualcosa che non sia una chiave
di un hash si ottiene un errore. Per esempio, se dopo la definizione
implicita di $channels come hashref si tenta di fare qualcosa del genere
$channels->[0] = ...
si otterrebbe un errore.
61 sub END {
62 &output_log;
63 }
Alla riga 43 o 54 il programma, chiusa la connessione, uscirebbe. Ma ci manca
ancora di scrivere la variabile di stato da qualche parte per poterla utilizzare
successivamente.
A questo pensa la funzione END.
Questa viene chiamata automaticamente dal Perl prima della "morte" del programma
e prima che le variabili escano dall'ambito di validità. Qui eseguiamo la
funzione che avrà il compito di scrivere il nostro report.
64 sub output_log {
65 $Data::Dumper::Varname = 'irc_status';
Il parametro $Varname presente nel namespace
Data::Dumper definisce il nome della
variabile che verrà stampata come variabile a cui viene associato il dump.
Vedete, negli esempi del Data::Dumper sopra che di default il suo valore e'
'VAR'. Noi gli diamo ora un valore più significativo.
66 open(OUTPUT,">$output");
Apriamo un handle di nome OUTPUT che punta, in scrittura (>) al file definito
all'inizio nella variabile $output.
67 print OUTPUT Data::Dumper::Dumper($status);
Stampiamo dentro a $output il dump della variabile
$status
68 close(OUTPUT);
69 }
Chiudiamo il file e questo è tutto.
Per i più curiosi, ecco un print della variabile $status durante una mia
sessione IRC
$irc_status1 = {
'channels' => {
'#telug' => 'La chat del Teramo Linux Users Group.....',
'#geeks' => 'Un canale pieno di buchi (geeks?!) ......',
'#areanetworking' => ' Benvenuti su #areanetworking ! .',
'#controguerra' => 'Gli abitanti di Controguerra, .....',
'#webgui' => '',
'#pescaralug' => 'PescaraLUG ....'
},
'online' => 1,
'timestamp' => 'Sun Mar 27 15:01:06 2005'
};
ecco lo stesso print mentre non ero presente su IRC.
$irc_status1 = {
'online' => 0,
'timestamp' => 'Sun Mar 27 15:05:28 2005'
};
Nel prossimo articolo descriverò uno script basato sul
modulo CGI che, utilizzando i dati presenti nel file
$output, produce una
pagina web che indica se siamo o non siamo online. ↑ |