-+  Documenti
 |-  Bibliografia
 |-  Articoli
 |-  Perlfunc
 |-  F.A.Q.
 |-  F.A.Q. iclp
-+  Eventi
-+  Contatti
-+  Blog
-+  Link



 

Versione stampabile.


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 arthas@perl.it

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

© 2007 Michele Beltrame.

1. Introduzione 

2. Apertura di un file 

3. Lettura e scrittura 

4. Funzioni utili 

5. Pipe 

6. Comandi esterni 

7. Conclusioni 

Introduzione

Un programma ha quasi sempre necessità di accedere a dei dati. Se ad esempio ne scriviamo uno che converte un'immagine dal formato GIF allo JPEG, esso avrà bisogno di aprire l'immagine sorgente in lettura e poi memorizzarne da qualche parte l'equivalente convertito. Sono quindi necessarie delle funzioni che permettano la gestione dei file sui dischi locali: come ogni linguaggio di programmazione esistente, anche Perl ne mette a disposizione alcune. Si tratta come al solito di funzioni piuttosto potenti che permettono accessi in lettura, scrittura, simultanei o in pipe. Quest'ultimo modo permette di invocare un comando esterno gestendolo come un file per quanto riguarda l'invio dei dati di input ed il prelievo di quelli in output.

 Apertura di un file

La funzione fondamentale per l'apertura di un file Perl è open(), che presenta la seguente sintassi:

open HANDLE, MODO, NOMEFILE;

L'handle è ciò che permette di utilizzare il file una volta aperto, mentre il secondo parametro indica il modo di accesso: sola lettura, sola scrittura, append (cioè aggiunta di dati in coda) e via dicendo. Il terzo ed ultimo argomento rappresenta, abbastanza intuitivamente, il nome del file. Vediamo subito un esempio:

open IMG, "<", "cover.gif";

L'handle è solitamente una sequenza di caratteri maiuscoli: non è necessario che esso sia definito in precedenza, nemmeno se si tratta di un filehandle locale: anzi, in questo specifico caso esso non va definito, a meno che non si utilizzi una variabile al suo posto:

sub apri {
  my $img;
  open $img, "<", "cover.gif"; 
  # Altro codice
}

Questa sintassi, poco usata, al prezzo di una riga in più di codice per aprire il file, ha un vantaggio: esso viene chiuso automaticamente allorché il flusso giunge alla parentesi graffa di chiusura del blocco. Nel caso generale la chiusura deve invece essere compiuta esplicitamente servendosi della funzione close():

close IMG;

Torniamo per un istante all'analisi dei parametri: l'utilizzo del filehandle per leggere e scrivere dati verrà illustrato in seguito; il modo di accesso specificato in questo caso è <, che indica che il file deve essere aperto in sola lettura. In Tabella 1 sono riportati tutti i modi di accesso disponibili.

Modo Lettura Scrittura Solo append Creazione Cancellazione
<
SINONONONO
>
NOSINOSISI
>>
NOSISISINO
+<
SISINONONO
+>
SISINOSISI
+>>
SISISISINO
| COMANDO
NOSINon disp.Non disp.Non disp.
COMANDO |
SINONon disp.Non disp.Non disp.

Le parentesi angolari di apertura e chiusura indicano, rispettivamente, sola lettura e sola scrittura. In quest'ultimo caso il file viene creato, con preventiva cancellazione nel caso esista già. Il modo di accesso >> apre invece un file in append: si tratta di una modalità di sola scrittura in cui però un eventuale file precedente non viene cancellato, ed i nuovi dati vengono scritti in coda ad esso; nel caso il file non esistesse viene creato, esattamente come accade con la modalità >. Vi sono poi i modi di accesso in lettura/scrittura: +<< (il file deve già esistere, in quanto non viene automaticamente creato o cancellato), +> (il file viene creato, ed eventualmente prima cancellato) e +>> (il file viene aperto in append, solo che è anche possibile leggervi i contenuti). Di fatto la prima modalità di lettura/scrittura è l'unica ampiamente utilizzata, mentre la seconda lo è solo di rado in quanto tutti i dati preesistenti vengono cancellati prima che sia effettivamente possibile leggerli. Essa è quindi adatta solo nel caso si voglia rileggere ciò che è appena stato scritto. È piuttosto diffusa l'usanza di unire in un'unica stringa la modalità di accesso ed il nome del file, ad esempio:

open IMG, ">cover.jpg";

Nel caso il modo venisse del tutto omesso, come in:

open IMG, "cover.gif";

il file viene aperto in sola lettura, cioè con modo <. La funzione open() ritorna un valore vero se il file viene aperto con successo e undef in caso contrario. Ciò rende possibile la gestione degli errori in vie come le seguenti:

# ### Tramite if ###
if (open IMG, "<cover.gif") {
  # Codice
  close IMG;
} else {
   die "Impossibile aprire il file: errore $!";
}

# ### Tramite or ###
open IMG, "

In quest'ultimo caso è importante ricordarsi di utilizzare l'operatore a bassa priorità or e non ||. Se si desidera servirsi invece di quest'ultimo è necessario mettere qualche parentesi in più:

open (IMG, "<cover.gif") || die ("Impossibile aprire il file: errore $!");

 Lettura e scrittura

Leggere e scrivere su un file non è molto diverso da leggere e scrivere sul terminale. Per leggere una riga da tastiera, o comunque dallo standard input, utilizziamo:

$a  = <STDIN>;

Nel caso di un file la procedura è uguale, in quanto viene sostituito l'handle del file a STDIN:

$a = <IMG>;

Se volessimo leggere tutte le righe in un colpo solo potremmo utilizzare lo stesso handle in un contesto di lista:

@righe = <IMG>;

Questi metodi di lettura sono tuttavia comodi più che altro quando si ha a che fare con file di testo. Come fare invece per leggere un determinato numero di byte senza che venga tenuto conto della suddivisione in righe del file? La funzione read() ci viene incontro. Di seguito è proposto un esempio del suo utilizzo:

read(IMG, $b, 10000);

Questo comando legge 10000 byte dal file corrispondente all'handle IMG e li memorizza in $b. Per memorizzare l'intero file in $b si può scrivere come segue:

read(IMG, $b, (stat(IMG))[7]);

A prima vista può sembrare magia nera; in realtà è un classico esempio di utilizzo di stat(), funzione che verrà descritta tra breve. Vediamo invece ora com'è possibile scrivere su un file che sia stato preventivamente aperto in scrittura. La funzione da utilizzarsi è la solita print(), per qualsiasi tipo di dato si desideri scrivere:

print IMG $b;

È quindi sufficiente indicare come primo parametro il filehandle per far sì che l'output venga rediretto ad esso. print() va bene sia nel caso di singole righe che di buffer letti con read(): non fatevi trarre in inganno dal nome della funzione, e quindi non utilizzate write(), che serve a tutt'altro.

 Funzioni utili

Perl incorpora svariate funzioni che semplificano la gestione di input ed output da file. Esse servono a spostarsi all'interno del file aperto e ad ottenere varie informazioni su di esso. Anzitutto tell() ritorna la posizione corrente, cioè il byte al quale il puntatore è attualmente collocato: se iniziassimo a scrivere o a leggere, l'operazione avrebbe inizio da tale posizione. L'utilizzo è molto semplice:

$pos = tell IMG;

In $pos si trova la posizione, che assume valore zero se il puntatore è collocato all'inizio del file. È naturalmente possibile anche modificare tale posizione tramite seek(), ad esempio:

seek IMG, 400, 0;

Il primo parametro è il classico handle, il secondo è l'offset (cioè la lunghezza dello spostamento in byte), mentre il terzo argomento è denominato whence ed indica l'origine dello spostamento. Questo può assumere tre valori: 0 (lo spostamento, in questo caso "lungo" 400 byte, avviene a partire dall'inizio del file), 1 (lo spostamento avviene a partire dalla posizione corrente), oppure 2 (i 400 byte vengono calcolati a partire dalla fine del file). Il potersi posizionare all'interno di un file, benché non di uso comunissimo, è fondamentale in quanto consente di leggere in maniera non sequenziale diverse parti del file, nonché di sovrascrivere i dati presenti nei vari punti di esso senza dover per forza leggerne l'intero contenuto, effettuare le modifiche, e scrivere un nuovo file. Un'altra funzione è eof(), che torna un valore vero se ci si trova alla fine del file. Essa è ad esempio utile se si effettuano continue letture e si vuole sapere quando smettere poiché non ci sono più dati da leggere. Ad esempio:

while(! eof (IMG))  {
  # Istruzioni read, ?
}

Una delle funzioni di uso in assoluto più comune Perl allorché diventi necessario gestire file esterni è stat(), già vista brevemente in precedenza con la promessa di spiegarla in seguito. Eccoci dunque a scoprirne il funzionamento. Il numero di informazioni fornito da stat() è notevole, tanto che viene tornato un array di ben tredici elementi. La sintassi è, nel caso del nostro file IMG:

@a = stat(IMG);

Siccome stat(), vista la natura di alcune delle informazioni che ritorna, è ampiamente utilizzata anche per file non aperti, essa accetta anche un nome di file anziché un handle come unico parametro:

@a = stat("anna.txt");

Ogni elemento dell'array contiene un'informazione sul file. Ad esempio l'elemento di indice sette ne indica la dimensione. Per ricavare dalla funzione solo questo dato si può scrivere, come abbiamo appunto fatto in precedenza sempre in questo articolo:

$size = (stat(IMG))[7];

La lista di tutti gli elementi dell'array ed il loro significato è riportata in Tabella 2.

IndiceContenuto
0
Numero di device del filesystem
1
Numero di inode
2
Tipo e permessi del file
3
Numero di link (non simbolici) al file
4
UID numerico del proprietario del file
5
GID numerico del gruppo a cui appartiene il file
6
Identificativo del device (applicabile solo ai file speciali)
7
Dimensione in byte del file
8
Timestamp contenente data ed ora dell'ultimo accesso al file
9
Timestamp contenente data ed ora dell'ultima modifica al file
10
Timestamp contenente data ed ora dell'ultimo cambiamento all'inode
11
Dimensione del blocco preferibile per l'input/output
12
Numero di blocchi allocati per il file

Alcuni di essi probabilmente saranno incomprensibili per la maggior parte dei lettori, ma non è scopo di questo corso spiegarli. Un buon libro sui sistemi operativi Unix svelerà ogni mistero. Inoltre, per chi usa Windows, molti di essi non sono utilizzabili in quanto propri di filesystem Unix. Per quanto riguarda la dimensione di un file nello specifico è più conveniente in realtà utilizzare un operatore unario di testing, -s:

$size = -s "anna.txt";

Questi operatori permettono di accedere direttamente ad un'informazione, solitamente booleana (che può cioè assumere esclusivamente valore vero oppure falso) su un file. Una lista di quelli di uso più comune è disponibile in Tabella 3.

OperatoreDescrizione
-e
Vero se il file esiste
-r
Vero se il file è accessibile in lettura
-w
Vero se il file è accessibile in scrittura
-d
Vero se il file è una directory
-f
Vero se il file non è una directory o un file speciale
-B
Vero se il file è un file binario
-T
Vero se il file contiene testo
-M
Tempo passato dall'ultima modifica al file (timestamp)
-A
Tempo passato dall'ultimo accesso al file (timestamp)
-s
Il file non è vuoto (ha dimensione maggiore a 0 byte)
-z
Il file è vuoto (ha dimensione di 0 byte)

 Pipe

Le pipe (da pronunciarsi all'inglese, quindi come ad esempio line) sono, come da traduzione letterale dei tubi che incanalano l'output di un programma verso un altro, facendolo diventare l'input di quest'ultimo. Chi usa un sistema operativo Unix-like sa bene che ad esempio il seguente comando dato da shell:

cat anna.txt | lpr

fa sì che il file anna.txt, anziché essere visualizzato in console, venga passato come input ad un altro comando, lpr, che si preoccupa a sua volta di inviarlo alla stampante predefinita. La barra verticale è il carattere che, in una shell Unix, permette di ottenere questo comportamento. In Perl il simbolo da usarsi è esattamente lo stesso, e va indicato come modo ad open(). Poniamo di voler scrivere un programmino che abbia la stessa funzione della coppia di comandi appena vista. Potremmo porre:

open TESTO, "<anna.txt";
open STAMPA, "|lpr";
while ($riga = <TESTO>) {
  print STAMPA $riga;
}
close TESTO;
close STAMPA;

Questo codice apre in lettura anna.txt ed in pipe di input il comando lpr: questo è ottenuto per mezzo della barra verticale posta prima del nome del file, esattamente come viene fatto nel caso di qualsiasi altro carattere che specifica il modo, come può essere la parentesi angolare. Un normale ciclo while legge tutte le righe del file TESTO e le scrive nel file STAMPA. In realtà l'interprete Perl non va, ovviamente, a sovrascrivere il comando lpr con il contenuto di anna.txt, ma lo esegue passando tali contenuti al suo standard input. Le ultime due righe si occupano semplicemente di chiudere i file precedentemente aperti. Perl permette di aprire anche delle pipe in input anziché in output semplicemente ponendo la barra verticale alla fine del nome del file:

open ETC, "ls -1 /etc|";
while ($file = <ETC>) {
  print $file;
}
close ETC;

In questo specifico caso viene eseguito il comando ls -1 /etc, il quale di norma visualizza il contenuto della directory specificata, mostrando un file per ogni riga. L'output è accessibile come un normale file aperto in input.

 Comandi esterni

Oltre alle pipe Perl mette a disposizione anche un paio di metodi diretti per invocare i comandi esterni: essi sono la funzione system() e l'operatore apostrofo inverso. Partiamo dal secondo metodo, che è quello di uso più comune, e vediamone subito un esempio:

$testo = `cat anna.txt`;

Questo operatore provoca l'esecuzione del comando specificato all'interno degli apici inversi e la memorizzazione del suo output in $testo. Analogamente è possibile servirsi della quoted syntax:

$testo = qx/cat anna.txt/;

A volte al posto dell'output può interessare catturare l'exit status di un programma. In questo caso ci viene incontro la funzione system():

$status = system("mkdir", "mieitesti");

Comando e parametri vanno passati non in una stringa unica ma come una lista, quindi devono essere separati. In questo caso il programma tornerà vero se la creazione della directory è avvenuta correttamente, e falso in caso contrario. Come piccola nota, la creazione di una directory in Perl non richiede l'utilizzo di un comando esterno, in quanto è presente una funzione mkdir() di cui è raccomandabile l'uso. Esiste inoltre una funzione exec() che ha la stessa sintassi di system(): quest'ultima tuttavia causa la terminazione dell'esecuzione del programma corrente ed il passaggio del controllo al comando invocato, e quindi non torna mai.

 Conclusioni

Abbiamo visto come accedere ai file ed eseguire comandi esterni. Entrambe le operazioni sono di fondamentale importanza allorché si voglia che il proprio script sia in grado di trattare dei dati presenti su disco oppure di scrivere da qualche parte o inviare ad altro programma il proprio output. Come si nota, in Perl la gestione dei file, pur garantendo una notevole potenza e flessibilità, è alquanto semplice, anche se sono disponibili funzioni come sysread() e syswrite() che garantiscono maggior controllo ma allo stesso tempo richiedono maggiore attenzione nell'uso. Una regola generale di Perl è infatti quella di rendere facili le operazioni facili, senza rendere impossibili quelle difficili.

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


Ti è piaciuto questo articolo? Iscriviti al feed!











Devo ricordare i dati personali?






D:
Annunci Google