[Per chi suona la campana] [About] [Copertina] [Emacs]

Articoli



NO NEWS IS A GOOD NEWS.

Nessuna notizia e' una buona notizia, recita un famoso detto inglese.
Spesso per noi che dipendiamo dalle News per avere la nostra dose quotidiana di informazioni è difficile anche renderci conto che non c'è proprio niente di interessante da leggere!
La quantità di notizie che ci arrivano tramite Internet è spesso così elevata da scoraggiare anche il più inpavido lettore.
Anche i filtri che i News readers ci mettono a disposizione sono spesso troppo grossolani e corriamo il rischio di perdere qualche informazione interessante o di averne così tante da non riuscire a trovare quella giusta per noi.
La soluzione, da bravi Linux-men, è quella di fabbricarci i nostri filtri su misura, interfacciandoci direttamente alle News, tramite il NNTP (Net News Transfer Protocol), il protocollo utilizzato per il trasferimento degli articoli tra news servers.

In questo articolo userò il linguaggio Perl e prenderò in esame un modulo aggiuntivo, che mette a disposizione del programmatore una classe che rende estremamente facile interfacciarsi al protocollo NNTP.

Supponendo che sulla vostra macchina sia installato il Perl, in una versione abbastanza recente (l'ultima versione al momento della scrittura di questo articolo è la 5.003), il componente che dovrete procurarvi è libnet-1.00.tar.gz .

L'installazione di questo modulo è molto semplice, come del resto quella di tutti i moduli aggiuntivi del Perl: basta scompattare il file, entrare nella directory creata e dare i seguenti comandi:

$ perl Makefile.PL
$ make
$ su -c 'make install'

A questo punto i moduli aggiuntivi e i relativi manuali saranno installati nelle opportune directory (che il Perl desume dai dati registrati nella sua installazione corrente).
Per leggere la pagina di manuale riguardante il modulo di interfaccia al protocollo NNTP basta dare il comando:

$ man Net::NNTP

Ovviamente dovrete avere nel vostro $MANPATH o nel vostro /etc/manpath.config anche la directory dei manuali del Perl, che di solito è /usr/lib/perl5/man.

PRIMI PASSI: LA LISTA DEI NEWSGROUPS.

Chiunque abbia scritto un programma che si interfacci ad un protocollo di rete ha sicuramente presente la quantità di istruzioni necessarie per l'apertura di un socket su di una porta TCP/IP.
Tutto questo lavoro è ora incapsulato in una singola chiamata, esattamente nella chiamata usata per l'inizializzazione di un oggetto Net::NNTP.

Praticamente basta scrivere:

$c = new Net::NNTP('mio.news.server');

per aprire la connessione con la macchina specificata dalla stringa 'mio.news.server'.

Il primo programmino che possiamo scrivere ci può servire per leggere la lista dei newsgroups disponibili sul nostro News Server:

#!/usr/bin/perl

use Net::NNTP;

$ARGV[0] = 'localhost' if !defined $ARGV[0];
$c = new Net::NNTP($ARGV[0]);
$lista = $c->list();
foreach $newsgroup (keys %$lista){
	print "$newsgroup\n";
}

Esaminiamolo riga per riga. La prima riga:

use Net::NNTP;

richiede al Perl di caricare il modulo che utilizzaremo. La riga:

$ARGV[0] = 'localhost' if !defined $ARGV[0];

Specifica un default ragionevole: se non sono presenti argomenti sulla riga di comando (if !defined $ARGV[0]), allora assegna al primo elemento dell'array in cui il Perl immagazzina la riga di comando, il nome del server di default, in questo caso 'localhost': il nostro server locale.
La riga:

$c = new Net::NNTP($ARGV[0]);

crea esplicitamente un oggetto della classe Net::NNTP. Dietro questa riga, come abbiamo detto, si nasconde tutto il meccanismo di connessione tramite socket alla porta NNTP del TCP/IP.
A questo punto non ci resta che inoltrare la richiesta della lista dei newsgroups attivi sul server e stampare il risultato.
Tutto il colloquio tra il Server e il nostro client è sintetizzato nella riga:

$lista = print $c->list();

che attiva la funzione membro 'list' dell'oggetto $c della classe Net::NNTP.
La chiamata a questo metodo ritorna una reference (puntatore per i fans del C) ad un array associativo, le cui chiavi sono i nomi dei newsgroups presenti.
Le righe seguenti:

foreach $newsgroup (keys %$lista){
	print "$newsgroup\n";
}

stampano i nomi dei newsgroups.
Il costrutto %$lista serve a dereferenziare l'intero l'array associativo puntato dalla variabile $lista.

Se chiamiamo questo programma 'lista' e lo rendiamo eseguibile (chmod u+x lista), possiamo lanciarlo con il comando:

$ lista

e otterremo su standard output l'elenco dei newsgroup attivi sulla nostra macchina.

QUANTI ARTICOLI CI SONO NEL NEWSGROUP

Fin qui niente di particolarmente interessante, ma andiamo avanti. Scriviamo ora un programmino (si parla sempre di programmini: questo modulo ci consente di scrivere programmi complessi in pochissime righe!) per rilevare il numero del primo e dell'ultimo articolo presente in un newsgroup sul server specificato:

#!/usr/bin/perl

use Net::NNTP;

if(!defined $ARGV[0]){
	print "Uso: $0 nome_gruppo [server]\n";
	exit;
}
$ARGV[1] = 'localhost' if !defined $ARGV[1];
$c = new Net::NNTP($ARGV[1]);
print "(", join(",", $c->group($ARGV[0])), ")\n";

Esaminiamo anche questo programma riga per riga. Le prime righe sono simili a quello del programma 'lista', poi le quattro righe:

if(!defined $ARGV[0]){
	print "Uso: $0 nome_gruppo [server]\n";
	exit;
}

verificano che al programma sia stato passato almeno un parametro sulla riga di comando: il nome del gruppo che vogliamo esaminare. In caso contrario il programma esce dopo aver stampato la sintassi con cui dovrebbe essere richiamato.
Le due righe successive:

$ARGV[1] = 'localhost' if !defined $ARGV[1];
$c = new Net::NNTP($ARGV[1]);

come abbiamo già visto definiscono un default per il nome del server da contattare e stabiliscono la connessione NNTP.

Vi sarete certamente resi conto che se l'utente per errore passa al programma il nome dell'host e dimentica il nome del newsgroup è probabile che si verifichi un errore, ma passatemi lo scarso controllo sui parametri, del resto questo è solo un programmino dimostrativo!

A questo punto, in un solo colpo, abbiamo la risposta:

print "(", join(",", $c->group($ARGV[0])), ")\n";

La funzione membro 'group' ritorna una lista di quattro elementi: il numero di articoli presenti nel newsgroup, il numero del primo e dell'ultimo articolo presenti e il nome nel newsgroup specificato. La funzione join lega gli elementi della lista in una unica stringa, separandoli con delle virgole.
Reso eseguibile questo programma e lanciato con il comando:

$ estremi_gruppo it.comp.linux

ottengo dal mio server una risposta del tipo:

(217,3511,3727,it.comp.linux)

GLI HEADERS DEGLI ARTICOLI.

Il passo successivo nella nostra esplorazione dei newsgroups è rappresentato da questo programma:

#!/usr/bin/perl

use Net::NNTP;
use Getopt::Std;
getopt('pu');

if(!defined $ARGV[0]){
	print "Uso: $0 [-pprimo] [-uultimo] nome_gruppo [server]\n";
	exit;
}
$ARGV[1] = 'localhost' if !defined $ARGV[1];
($c = new Net::NNTP($ARGV[1])) || die $!; # die on timeout

(($num_arts, $primo, $ultimo, $nome) = ($c->group($ARGV[0]))) || die $!;
$primo = $opt_p if defined $opt_p;
$ultimo = $opt_u if defined $opt_u;
$heads = $c->xhdr("Subject", $primo, $ultimo);

foreach $head (keys %$heads){
	print "$head $heads->{$head}\n";
}

che serve per ottenere un elenco dei soli soggetti degli articoli presenti in un newsgroup specificato. Esaminiamolo.
La prima righa:

use Net::NNTP;

come al solito serve per caricare il modulo che useremo. Le righe successive:

use Getopt::Std;
getopt('pu');

if(!defined $ARGV[0]){
	print "Uso: $0 [-pprimo] [-uultimo] nome_gruppo [server]\n";
	exit;
}

servono per caricare e usare una libreria compresa nella distribuzione standard del Perl, che serve per esaminare la riga di comando alla ricerca di opzioni.
Le opzioni che cerchiamo sono '-p' e '-u', entrambe seguite da un valore numerico, il cui scopo è definire il numero del primo e dell'ultimo articolo da esaminare.

Se la linea di comando è vuota, usciamo dal programma, proponendo un messaggio chiarificatore sull'uso.

La riga successiva:

$ARGV[1] = 'localhost' if !defined $ARGV[1]; 

definisce un default ragionevole per il nome del server da contattare.
La riga:

($c = new Net::NNTP($ARGV[1])) || die $!; # die on timeout

avvia la connessione con il server, questa volta controllando anche il risultato dell'operazione e uscendo in caso di errore o timeout.
Le righe successive:

(($num_arts, $primo, $ultimo, $nome) = ($c->group($ARGV[0]))) || die $!;
$primo = $opt_p if defined $opt_p;
$ultimo = $opt_u if defined $opt_u;

leggono il numero del primo e dell'ultimo articolo presenti nel newsgroup specificato, per utilizzarli come defaults, in caso l'utente non abbia specificato il numero del primo e dell'ultimo articolo da esaminare.
Alla fine le righe:

$heads = $c->xhdr("Subject", $primo, $ultimo);

foreach $head (keys %$heads){
	print "$head $heads->{$head}\n";
}

stampano tutti i soggetti degli articoli specificati.
La funzione membro xdhr() torna una reference ad un array associativo, le cui chiavi sono i numeri degli articoli e i cui valori sono l'header richiesto per l'articolo.
Il costrutto $heads->{$head} serve per dereferenziare un elemento dell'array a partire dal suo reference.

Reso eseguibile questo programma e lanciatolo con il comando:

$ headers_articoli -p34000 -u34010 comp.databases.oracle

ottengo dal mio server la risposta:

34000 Cursor: does the job but doesn't come back
34001 Re: Database writing architecture
34002 Help w/ D-2000 connection
34003 Re: Cursor: does the job but doesn't come back
34004 Re: Oracle 7.3 features
34005 Applications Programmers-Twin Cities-RTP NC
34006 Re: Java-Oracle are there any classes available?
34007 Re: PO7 upgrade to Win95.
34008 US-IL-ORACLE/ORACLE FINANCIALS DEVELOPERS & DBA'S
34009 Excellent Opportunities-RTP NCWash DCMinneapolis
34010 BIND variables?

UNA RICERCA!

Ma tutto ciò può essere fatto da un qualsiasi news reader!
Le cose cominciano a diventare utili se modifichiamo una sola riga del nostro programma in modo che stampi il numero di tutti gli articoli il cui soggetto contiene una certa stringa:

#!/usr/bin/perl

use Getopt::Std;
getopt('pu');
use Net::NNTP;

if(!defined $ARGV[1]){
	print "Uso: $0 [-pprimo] [-uultimo] nome_gruppo stringa [server]\n";
	exit;
}
$ARGV[2] = 'localhost' if !defined $ARGV[2];
($c = new Net::NNTP($ARGV[2])) || die $!; # die on timeout

(($num_arts, $primo, $ultimo, $nome) = ($c->group($ARGV[0]))) || die $!;
$primo = $opt_p if defined $opt_p;
$ultimo = $opt_u if defined $opt_u;
$heads = $c->xpat("Subject", $ARGV[1], $primo, $ultimo);

foreach $head (keys %$heads){
	print "$head $heads->{$head}\n";
}

Il programma cerca_articoli ora, lanciato così:

$ cerca_articoli comp.lang.perl.misc '*UNIX*'

genera sulla mia macchina questo output:

18263 Simple UNIX program scheduling...
18264 UNIX Symlinks and -i.bak
18266 Re: Simple UNIX program scheduling...
18273 Re: Simple UNIX program scheduling...

Ci sono due cose da notare: la prima è che il pattern viene interpretato dal server NNTP, quindi si usano dei metacaratteri diversi da quelli delle regular expression del Perl, più simili ai metacaratteri della shell.
La seconda cosa è che il pattern deve matchare tutta la stringa cercata: nel caso si compia una ricerca sul "Subject" dell'articolo, il pattern deve corrispondere all'intero soggetto, non ad una parte di esso.
Questo spiega gli asterischi prima e dopo la stringa da cercare.

Ovviamente usando lo stesso metodo posso cercare una stringa all'interno di qualsiasi altra riga dell'header, specificandone il nome al posto del "Subject", così, per cercare gli articoli scritti dal signor Rossi:

$heads = $c->xpat("From", "*Rossi*", $primo, $ultimo);

A questo punto abbiamo già il modo di selezionare un po' gli articoli prima della lettura, ma vogliamo raffinare ancora il nostro programmino. Facciamo in modo che la nostra creatura possa leggere anche il testo dell'articolo e che possa "decidere" cosa ci interessa.

Qualche altra riga di codice e otteniamo:

#!/usr/bin/perl

use Getopt::Std;
getopt('pu');
use Net::NNTP;

if(!defined $ARGV[1]){
	print "Uso: $0 [-pprimo] [-uultimo] nome_gruppo stringa_header stringa_body [server]\n";
	exit;
}
$ARGV[3] = 'localhost' if !defined $ARGV[3];
($c = new Net::NNTP($ARGV[3])) || die $!; # die on timeout

(($num_arts, $primo, $ultimo, $nome) = ($c->group($ARGV[0]))) || die $!;
$primo = $opt_p if defined $opt_p;
$ultimo = $opt_u if defined $opt_u;
$heads = $c->xpat("Subject", $ARGV[1], $primo, $ultimo);

foreach $numero (keys %$heads){
	$art = $c->article($numero);
	$testo = join "", @$art;
	if($testo =~ /$ARGV[2]/o){
		print "$testo\n\n";
	}
}

Qualche commento: il numero dei parametri richiesti dal programma è cresciuto: oltre alla stringa da cercare nell'header ora ci aspettiamo dall'utente anche una stringa da cercare all'interno del testo dell'articolo.
Dopo la chiamata alla funzione membro xpath() scorriamo tutte le righe dell'array, leggendo il testo dell'articolo mediante la funzione membro article().
Questa funzione torna una reference ad un array che contiene tutte le righe dell'articolo (variabile $art).
Uniamo tutte le righe dell'array con una join per formare un'unica variabile, all'interno della quale cercheremo poi il secondo testo specificato sulla riga di comando.

Invocando il programma in questo modo:

$ cerca -p18254 -u18280 comp.lang.perl.misc '*UNIX*' 'crontab'

ottengo questo l'output:

Path: nanux!comune.bologna.it!sirio.cineca.it!serra.unipi.it!swidir.switch.ch!in2p3.fr!oleane!tank.news.pipex.net!pipex!news.mathworks.com!newsfeed.internetmci.com!info.ucla.edu!ihnp4.ucsd.edu!munnari.OZ.AU!mel.dit.csiro.au!carlton.acci.COM.AU!gavin
 From: gavin@acci.COM.AU (Gavin Cameron)
 Newsgroups: comp.lang.perl.misc,comp.unix.programmer
 Subject: Re: Simple UNIX program scheduling...
 Date: 11 Apr 96 00:35:28 GMT
 Organization: Australian Computing and Communications Institute
 Lines: 23
 Message-ID: <gavin.829182928@carlton.acci.COM.AU>
 References: <4khh0i$fv2@solaris.cc.vt.edu>
 NNTP-Posting-Host: tawonga.acci.com.au
 X-Newsreader: NN version 6.5.0 #6 (NOV)
 Xref: nanux comp.lang.perl.misc:18273 comp.unix.programmer:5376
 
 Use crontab, see the crontab(5) man page for all the details you'll ever
 need to know.
 
 Gavin
 
 
 sms@magenta.com (SMS/Christian Fowler) writes:
 
 >I have a simple program, written with PERL, I want to schedule execution
 >for. What is the simplest way to do this? Should I have the program 
 >reschedule itself, every time it runs?
 
 >I simply want it to run, say every monday morning at 7 am.
 
 >Thanks for any info...
 
 
 >--
 >  =-=
 >=-=+=-=  Sound Machine Sound - The Music Makers Net Directory
 >=-=%=-=     Christian Fowler - sHAPE FACTOR MOMENt
 >=-=+=-=    sms@magenta.com  http://magenta.com/~sms/
 >  =-=

Purtroppo questa volta le stringhe che esprimono i pattern da cercare seguono due standard diversi: la prima stringa viene interpretata dal server NNTP e segue uno standard pseudo-shell, mentre la seconda stringa, interpretata dal Perl può essere una regular expression.
Spiacente per la confusione, ma a servizi diversi corrispondono formalismi diversi... fino a quando qualcuno non scriverà un server NNTP in Perl (oops!).

È comunque un buon risultato per un programma di venticinque righe, comprese righe vuote e commenti! C'è da dire che il programmino non ha molte pretese, che può essere migliorato, ma è sufficiente a dimostrare cosa si può fare con questo modulo e spero che possa fornirvi degli spunti per creare qualcosa di più complesso... interfacce News-Web? risponditori intelligenti?
Si potrebbe usare il modulo che si interfaccia al protocollo SMTP per spedire mails di biasimo automatiche a chi scrive sciocchezze... ma questa è un'altra storia e va raccontata un'altra volta.

Nel frattempo vi auguro felice hacking sulla vostra Linux box e se realizzate qualcosa di interessante non mancate di farmelo sapere e magari scrivete un'articolo per il Pluto Journal.

A rileggerci alla prossima. Nel frattempo per dubbi, congratulazioni, correzioni, insulti & altro scrivete a Nando Santagata.


APPENDICE

Il modulo citato si può trovare in uno dei siti del CPAN (Comprehensive Perl Archive Network) di cui vi elenco i siti in Europa:

Austria
	ftp://ftp.tuwien.ac.at/pub/languages/perl/CPAN/
Belgium
	ftp://ftp.kulnet.kuleuven.ac.be/pub/mirror/CPAN/
Czech Republic
	ftp://sunsite.mff.cuni.cz/MIRRORS/ftp.funet.fi/pub/languages/perl/CPAN/
Denmark
	ftp://sunsite.auc.dk/pub/languages/perl/CPAN/
Finland
	ftp://ftp.funet.fi/pub/languages/perl/CPAN/
France
	ftp://ftp.ibp.fr/pub/perl/CPAN/
	ftp://ftp.pasteur.fr/pub/computing/unix/perl/CPAN/
Germany
	ftp://ftp.leo.org/pub/comp/programming/languages/perl/CPAN/
	ftp://ftp.rz.ruhr-uni-bochum.de/pub/CPAN/
Greece
	ftp://ftp.ntua.gr/pub/lang/perl/
Hungary
	ftp://ftp.kfki.hu/pub/packages/perl/CPAN/
Poland
	ftp://ftp.pk.edu.pl/pub/lang/perl/CPAN/
	ftp://sunsite.icm.edu.pl/pub/CPAN/
Portugal
	ftp://ftp.ci.uminho.pt/pub/lang/perl/
Slovenia
	ftp://ftp.arnes.si/software/perl/CPAN/
Spain
	ftp://ftp.etse.urv.es/pub/mirror/perl/
	ftp://ftp.rediris.es/mirror/CPAN/
Sweden
	ftp://ftp.sunet.se/pub/lang/perl/CPAN/
Switzerland
	ftp://ftp.switch.ch/mirror/CPAN/
the Netherlands
	ftp://ftp.cs.ruu.nl/pub/PERL/CPAN/
UK
	ftp://ftp.demon.co.uk/pub/mirrors/perl/CPAN/
	ftp://sunsite.doc.ic.ac.uk/packages/CPAN/
	ftp://unix.hensa.ac.uk/mirrors/perl-CPAN/

L'Italia non ha un sito CPAN (qualche volontario?).

Il modulo in questione si trova, a partire dalla directory del CPAN indicata per ogni server, in:

modules/by-module/Net

di Nando Santagata


[Per chi suona la campana] [About] [Copertina] [Emacs]