Usare per queste operazioni elementari un linguaggio di programmazione compilato è un po' dispersivo, specialmente perché i linguaggi convenzionali non offrono dei servizi di livello sufficientemente alto, o almeno non così alto da rivaleggiare con il set di comandi che un utente Unix si trova ad usare nella pratica di tutti i giorni.
Per questo si sono sempre usati i cosiddetti shell script: dei file che contengono sequenze di quei comandi (o programmi se preferite) che si trovano in qualsiasi sistema Unix. Per alcuni scopi questi shell script sono sufficienti, ma se si vogliono eseguire dei compiti complessi, come l'elaborazione di file, a volta si fatica un po' troppo per quello che si vuole ottenere.
Esistono però altri modi di scrivere in fretta un programmino che analizzi certi file ed emetta un risultato. Entrano in scena allora sed (stream editor) che automatizza le operazioni che è possibile compiere con un editor su di un file ed awk, un linguaggio basato sul riconoscimento di pattern all'interno di un file di testo e sulla loro elaborazione.
Con l'aiuto di questi piccoli tool è stato possibile per la maggior parte degli utenti creare dei programmi di inusitata complessità, ma... C'è sempre un ma.
Usando i comandi degli shell script, il sed o l'AWK si può operare solo su file di testo e neanche tanto agevolmente, tanto è vero che spesso sed e AWK sono usati all'interno di shell script che comunque ne dirigono le operazioni. Oltre tutto i programmi ottenuti in questo modo sono spesso lenti e se vanno bene per elaborare piccoli file in modo semplice, mostrano di essere inadeguati per operazioni complesse o per operare su file molto grandi.
Il Perl è stato creato per venire incontro all'esigenza di avere un linguaggio di programmazione di uso generale, potente, veloce nell'esecuzione e nella scrittura dei programmi. Sebbene al momento della sua nascita fosse un linguaggio orientato all'elaborazione dei testi (da cui il suo nome: Practical Extraction Report Language), il Perl è in grado di trattare con altrettanta facilità ed assoluta trasparenza anche file binari.
Benché sia molto noto come linguaggio per lo sviluppo di CGI, Perl è stato creato inizialmente come ausilio ai sistemisti, come linguaggio di manipolazione di testo e file. Infatti è anche detto Practical Extraction and Report Language, ma questo è un acronimo assegnato dopo la creazione del nome o, in inglese, un "backronym". Pertanto secondo la documentazione stessa di Perl, non vi si dovrebbe mai riferire come "PERL", ma come "Perl" o "perl" a seconda che si intenda il linguaggio in quanto tale o una specifica implementazione rispettivamente. È anzi questa un'informazione utilizzata negli ambienti perlistici per distinguere "chi è del giro" da chi non lo è.
Si è evoluto nel tempo, anche grazie ad un potente sistema di moduli, in un linguaggio a carattere più generale, comprendente l'elaborazione di immagini, l'interrogazione di banche dati, i processi di comunicazione via rete, ed utilizzabile in tutti quegli ambiti in cui non siano strettamente necessarie le performance di un linguaggio compilato più a basso livello, offrendo al contempo tempi di sviluppo molto più rapidi. È quindi anche utilizzato per la prototipizzazione di programmi da implementarsi in altri linguaggi.
Il linguaggio è stato pensato per essere pratico (facile da usare, efficiente, completo) oltre che bello e "magico" (è questo un concetto tipicamente perlistico); esso non è mai stato pensato per essere compatto, elegante o minimale, infatti il suo motto più distintivo è riassunto nell'acronimo TMTOWTDI (There is More Than One Way To Do It), che in inglese molto indicativamente significa "c'è più di un modo per farlo". Tuttavia uno dei suoi maggior pregi è che grazie a tale ricchezza consente di risolvere con grande semplicità ed eleganza problemi che con altri linguaggi richiederebbero notevoli sforzi. Infatti Larry Wall ama ripetere che uno dei suoi obiettivi è "rendere le cose facili, facili e quelle difficili, possibili".
Perl supporta sia il paradigma procedurale che quello ad oggetti, ha potenti funzioni per l'elaborazione dei testi ed è dotato di una delle maggiori collezioni di moduli prodotte dalla sua vasta comunità di utenti.
Punti di forza del Perl (almeno all'epoca delle sue origini: con lo sviluppo esplosivo che ha subito ora ne ha molti altri) erano le regular expression, estremamente potenti ed articolate e il sistema di reporting che consente di disegnare il layout della pagina.
Ora il Perl viene usato per gli scopi più diversi: utility di sistema, accesso a database, networking, programmazione CGI e grafica.
Questo linguaggio è ora disponibile su moltissime piattaforme, da Unix (la sua piattaforma originaria) a Plan 9, VMS, QNX, OS/2, Amiga e Win32.
La caratteristica più peculiare di questo linguaggio è la libertà di espressione che consente al programmatore: è un linguaggio ``ridondante'' dal punto di vista delle strutture logiche e di flusso. Questo rende più facile per un programmatore accostarsi al linguaggio, qualunque siano state le sue esperienze passsate. Il motto del Perl è ``There's more than one way to do this'' (c'è più di un modo per farlo) e il suo autore tiene molto a questa forma di libertà che contrasta così tanto con la pratica comune di creare linguaggi parchi di istruzioni, con il minor numero possibile di strutture, seguendo una ricerca estetica di semplicità.
A cosa somiglia il Perl? Questa è una domanda che ha una risposta insolitamente lunga: ha dentro di sé caratteristiche prelevate da numerosi linguaggi. Chi ha programmato con gli shel script riconoscerà qualcosa della Bourne shell e della C shell, così come certe altre istruzioni ricordano il sed, l'AWK, il C, passando addirittura per alcuni dialetti del BASIC.
In realtà, come ho già detto, questo è un punto di forza, perché chi ha un minimo di esperienza di programmazione in qualsiasi linguaggio può diventare produttivo molto rapidamente.
Il Perl è un linguaggio interpretato: non necessita di dichiarazioni di variabili: a secondo dell'uso che se ne fa, le variabili vengono trasformate nel tipo necessario. Ad esempio si può assegnare un valore numerico ad una variabile, moltiplicarlo per due e concatenargli una stringa.
Questa caratteristica in realtà è quanto ci si possa già aspettare da un linguaggio interpretato. Ma il Perl è più di un linguaggio interpretato: in realtà quando si lancia un programma Perl, il testo del programma viene letto e compilato, viene quindi eseguito ad una velocità molto elevata.
Il Perl è molto ottimizzato (è il frutto di anni di lavoro da parte di hacker di tutto il mondo), ma per i fan del codice binario è disponibile in beta un compilatore ``reale'' e presto sarà disponibile anche un compilatore in grado di tradurre il Perl in byte code Java...
In sostanza la velocità del Perl non è un problema, perché il linguaggio dispone di una espressività di livello molto alto, che consente di pilotare facilmente routine interne all'interprete, come ad esempio il meccanismo di ricerca e sostituzione all'interno di una stringa.
Una caratteristica che rende rapida la scrittura ``fisica'' di un programma Perl è che praticamente tutte le funzioni hanno dei default, ad esempio lavorano sulla riga corrente del file di input o sul valore della variabile di default, quella che poi contiene la riga appena riga.
Negli ultimi anni sono state aggiunte al Perl delle caratteristiche sofisticate: le possibilità di immagazzinare in variabili dei riferimenti ad altre variabili o subroutine e la possibilità di usare ``moduli'', il termine con cui si designano in Perl le classi.
In realtà un modulo non è niente più di una raccolta di subroutine incapsulate in un package (l'analogo ante litteram dei namespace del C++, presente da anni nel linguaggio) e di una reference di tipo particolare che conserva al suo interno, oltre ai dati degli oggetti, l'indicazione della classe a cui appartiene e attraverso la quale si può accedere ai dati e funzioni membro del modulo.
L'object orientation nel Perl non è basata sull'inaccessibilità dei dati e delle funzioni private delle classi, ma su un patto democratico (diciamo una policy) tra il creatore del modulo e i suoi utenti. L'autore documenta i dati e un'interfaccia che l'utente farebbe meglio a rispettare, perché il resto delle funzioni interne non è detto che resti invariata tra una versione e l'altra o necessita di una comprensione troppo approfondita delle operazioni svolte dal modulo per poter essere usata in modo da non provocare malfunzionamenti.
Parlo di patto o accordo, perché spesso si tratta di un vero accordo bilaterale e non di un dictat da parte dell'autore. Questa affermazione introduce un discorso sul metodo con cui procede lo sviluppo del Perl e dei suoi moduli.
Questo metodo di sviluppo ricorda molto da vicino quello adottato per Linux: i sorgenti del linguaggio sono liberamente disponibili e sono coperti da una Artistic License, che è meno restrittiva della GPL della GNU.
Chiunque può partecipare allo sviluppo: gli autori sono frequentatori accaniti dei vari newsgroup dedicati al linguaggio ed è possibile dialogare con loro, porre quesiti, ma anche fare richieste, che, se ragionevoli, vengono prese in considerazione. In pratica la discussione sulla definizione del linguaggio è sempre stata pubblica e chiunque ne abbia voglia può parteciparvi.
Lo stesso succede per i moduli (ormai se ne contano a centinaia). Il luogo deputato alla discussione delle caratteristiche dei nuovi moduli è sempre un newsgroup. Non è raro assistere alla discussione pubblica su ciò che un modulo deve fare e su come sarebbe meglio scriverlo.
Insomma lo spirito di collaborazione, l'estrema dinamicità dello sviluppo e l'eccellenza dei risultati ricorda molto da vicino ciò che succede nella comunità Linux.
Questa situazione ha favorito il moltiplicarsi dei moduli, che coprono ora ogni aspetto dell'informatica, trasformando il Perl da un linguaggio di text processing in un linguaggio general purpose.
Seguono alcuni esempi di codice Perl particolarmente interessanti, utili o folkloristici, trovati nei newsgroup dedicati al Perl. (comp.lang.perl.*).
1: #!/usr/bin/perl -n00
2:
3: while(m{
4: <\s*A 5: \s+HREF 6: \s*=\s* 7: (["']) 8: (.*?) 9: \1 10: .*?>
11: }xsgi)
12: {
13: print "$2\n";
14: }
Questo programmino estrae tutti i link contenuti in un file HTML e li stampa sullo schermo. Nella prima riga vengono passate anche delle opzioni al perl: -n significa esegui il programma che segue per ogni riga letta e -00 significa leggi il file di input un paragrafo alla volta.
Così se questo programma viene chiamato ``estrai_link'', basta dare il comando:
$ estrai_link pagina.html
per avere come output tutti i link presenti nel file. Il programma è composto da un ciclo while che testa il risultato di un pattern matching: se la stringa è stata trovata nella riga (paragrafo) corrente, allora viene stampata. La regular expression m{...} identifica una espressione HTML del tipo:
Nelle righe dalla 4 alla 6 si cerca la parte del link. Vengono usate delle espressioni del tipo \s , che rappresentano la classe dei caratteri spazio (nel nostro caso sono lo spazio, il tab e l'andata a capo).
La riga 7 cerca un carattere appartenente all'insieme ["'] e viene conservata memoria del carattere trovato (le parentesi tonde individuano una match da ricordare). Questo perché la stringa che compone il link può essere circondata da singoli o doppi apici: se il primo apice trovato è singolo, lo sarà anche quello di chiusura e lo stesso succede per i doppi apici. In pratica in HTML sono possibili le due scritture:
e
La riga 8 fa il match del link e ne ricorda la stringa (è tra parentesi tonde). Il quantificatore, cioè i metacaratteri che indicano quante volte è ripetuto il carattere cercato è di tipo ``*?''. Questo vuol dire che nella ricerca della stringa individuata dalla regular expression bisogna sempre tenere presente il carattere successivo da trovare e ci si deve fermare quando lo si trova. In pratica raccogliendo i caratteri che formano il link mi devo fermare quando trovo l'apice successivo, segnalato nella riga 9 dalla variabile \1, che contiene il carattere letto nell'espressione contenuta nella prima coppia di parentesi tonde (["']).
La riga 10 dice che possono seguire una serie di caratteri, terminati comunque dal carattere '>'.
Nella riga 11 si dice che la regular expression è di tipo extended (x): gli spazi e le andate a capo, inserite per migliorarne la leggibilità devono essere tralasciati nel pattern matching. La regular expression deve essere inoltre case insensitive (i), deve poter individuare tutti i link presenti nella riga di input e non solo il primo (g) e deve poterli individuare in una stringa multilinea (s).
La riga 13 stampa la strina individuata dall'espressione contenuta nella seconda coppia di parentesi.
Questo programmino agisce sempre su un file HTML e ricerca e sostituisce un link con un altro.
#!/usr/bin/perl -pi.bak -00
s[
(
<\s*A \s+HREF \s*=\s* (["']) ) http://dominio\.it/dir/ ( (.*?) \2 .*?>
)
][${1}http://www.dominio.it/dir2/$3]xsgi;
Se siete riusciti a seguire la tortuosa spiegazione del primo programma, avete già capito gratis cosa fa questo. Unica nota: l'opzione -p serve per eseguire il programma su ogni riga (paragrafo anche in questo caso, grazie all'opzione -00) del file di input e stampa automaticamente la riga letta dopo aver applicato la ricerca e sostituzione, mentre l'opzione -i.bak serve per far eseguire il programma sul file di input, salvandone una copia con estensione .bak.
Se chiamiamo il programma cambia_link, dando il comando:
$ cambia_link file.html
ritroviamo alla fine un file file.html in cui sono stati modificati i link e un file file.html.bak che è il file originale.
Questo programmino usa il modulo UserAgent per leggere una pagina da un server HTTP e stamparla sullo schermo. Niente di eccezionale così com'è, ma le estensioni sono lasciate alla vostra fantasia.
#!/usr/bin/perl
use LWP::UserAgent;
$agent = new LWP::UserAgent;
$request = new HTTP::Request('GET', 'http://gretux/');
$response = $agent->request($request);
print $response->content;
Per dimostrare l'attitudine che ha il Perl di poter scendere a livello di sistema, questo programmino sintetizza un pacchetto TCP/IP Out of Band e lo spedisce alla porta 139 di un computer:
#!/usr/bin/perl
use IO::Socket;
IO::Socket::INET->new(PeerAddr => "$ARGV[0]:" .
(defined $ARGV[1] ? $ARGV[1] : "139"),
Proto => "tcp")->send("bye",MSG_OOB);
Se chiamiamo questo programma crash, dando il comando:
$ crash host.dominio.it
verrà spedito un singolo pacchetto TCP/IP al computer host.dominio.it, sulla porta 139 (default).
Aggiungendo un ulteriore parametro possiamo specificare la porta verso la quale spedire il nostro pacchetto.
Se ti è piaciuto l'articolo, iscriviti al feed per tenerti sempre aggiornato sui nuovi contenuti del blog: