[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [translators] [docinfo] [indice analitico] [volume] [parte]
La gestione dei file è uno dei punti di forza di Perl. Perl permette di gestire in modo molto semplice i file di testo e i file DBM. Sono presenti ugualmente gli strumenti per la gestione di file di qualunque altro tipo, attraverso l'accesso al singolo byte, ma questo aspetto passa in secondo piano rispetto al resto e qui viene trascurato.
Prima di poter accedere in qualunque modo a un file, occorre che questo sia stato aperto all'interno del programma, il quale, da quel punto in poi, vi fa riferimento attraverso il flusso di file.
Per una convenzione diffusa, i nomi attribuiti ai flussi di file sono sempre composti da lettere maiuscole, cosa che facilita il loro riconoscimento all'interno di un sorgente Perl.
Oltre ai file su disco, esistono tre file particolari: standard input, standard output e standard error. Questi risultano sempre già aperti e ai flussi di file corrispondenti si fa riferimento attraverso tre nomi predefiniti: STDIN, STDOUT e STDERR.
In condizioni normali, i file si intendono contenere una codifica a 8 bit; mentre è possibile specificare esplicitamente che questi utilizzano la codifica UTF-8.
Quando è necessario aprire un file, cioè quando non si tratta dei flussi predefiniti, si utilizza la funzione open().
open flusso,file |
open flusso,modalità,file |
La funzione utilizza quindi due o tre argomenti: il nome del flusso di file, il nome effettivo del file (che può contenere l'indicazione del percorso necessario a raggiungerlo) ed eventualmente la modalità di apertura. Nel primo caso, si intende che il file contiene, o deve contenere, una codifica a 8 bit, mentre nel secondo si può specificare in modo preciso la codifica.
L'esempio seguente apre il file mio_file
che si trova nella directory corrente e gli abbina il flusso di file MIO_FILE:
|
Con l'apertura del file si deve definire anche in che modo si intende accedervi. Fondamentalmente si distingue tra lettura e scrittura, ma in realtà si presentano anche altre sfumature. Per poter informare la funzione del modo in cui si intende aprire il file, la stringa che viene utilizzata per indicare il nome del file su disco può contenere dei simboli aggiuntivi che servono proprio per questo, oppure si usa l'argomento ulteriore che si colloca prima del nome del file. In presenza di soli due argomenti, tali simboli vanno posti quasi sempre di fronte al nome e possono essere spaziati da questo in modo da facilitarne la lettura:
|
A questa simbologia si può aggiungere il segno + in modo da permettere anche l'altro tipo di accesso non dichiarato, per cui:
|
In generale, un file aperto in lettura e scrittura attraverso il simbolo +< permette anche l'allungamento del file stesso. Il pezzo di codice seguente mostra l'apertura di un file in aggiunta e l'inserimento al suo interno di una riga contenente una frase di saluto.
|
L'esempio seguente è una variante in cui si dichiara espressamente l'utilizzo della codifica UTF-8 e si inseriscono alcune lettere greche, specificando i punti di codifica U+03B1, U+03B2, U+03B3:
|
Eventualmente, si può dichiarare che la codifica deve essere di un certo tipo, attraverso l'istruzione seguente:
use open ":codifica" |
Nello stesso modo in cui si possono gestire i file su disco, si può accedere a un condotto, cioè una sequenza di programmi che ricevono dati dal loro standard input e ne emettono attraverso lo standard output. Per ottenere questo, al posto di indicare un file su disco si mette una riga di comando che si vuole sia eseguita, preceduta o terminata con la consueta barra verticale: se si trova all'inizio, significa che si vuole scrivere inviando dati attraverso lo standard input del condotto; se si trova alla fine, significa che si vuole leggere attingendo dati dallo standard output del condotto.
|
L'esempio appena mostrato apre un condotto in scrittura. Ciò che viene ricevuto dal condotto viene ordinato e registrato nel file /home/tizio/mio_file
.
|
L'esempio precedente apre un condotto in lettura in modo da poter elaborare il risultato del comando ls -l.
In questi casi non si può dichiarare la codifica nell'istruzione open, pertanto qui conviene usare l'istruzione use open. Ecco gli stessi esempi appena presentati, in cui si dichiara la scrittura e la lettura secondo la codifica UTF-8:
|
|
Naturalmente, occorre considerare che l'istruzione use open rimane valida per tutti i file che vengono aperti successivamente, fino a quando se ne appare un'altra che ne cambia la modifica.
Dal momento che i flussi standard (standard input, standard output e standard error) risultano già aperti in modo predefinito, esiste la possibilità di dichiarare la codifica di file dopo che questi sono già stati aperti:
binmode flusso, ":codifica" |
Per esempio, per dichiarare che tutti i flussi standard usano la codifica UTF-8, bastano le istruzioni seguenti:
|
Un file aperto che non serve più deve essere chiuso. Ciò si ottiene attraverso la funzione close() indicando semplicemente il flusso di file da chiudere.
close flusso |
L'apertura di un file può essere fatta anche se questo risulta già aperto, per cui non è strettamente necessario chiudere un file prima di riaprirlo.
In presenza di un sistema operativo in multiprogrammazione, tanto più se anche multiutente, si pone il problema della gestione degli accessi simultanei ai file. In pratica occorre gestire un sistema di blocchi, o di semafori, che impediscano le operazioni di scrittura simultanea da parte di processi indipendenti.
Infatti, la lettura simultanea di un file da parte di più programmi non ha alcun effetto collaterale, mentre la modifica simultanea può tradursi anche in un danneggiamento dei dati. Per questo, quando un file deve essere modificato, è importante che venga impedito ad altri programmi di fare altrettanto, almeno per il tempo necessario a concludere l'operazione.
Il modo più semplice per impedire che un file possa essere modificato da un altro processo, è quello di bloccarlo (lock), per il tempo necessario a compiere le operazioni che si vogliono fare in modo esclusivo.
Teoricamente, il blocco potrebbe limitarsi solo a una porzione del file, ma questo implica un'organizzazione condivisa anche dagli altri processi, in modo che sia ben definita l'estensione di questo blocco. In pratica, ci si limita quasi sempre a eseguire un blocco totale del file, rilasciando il blocco subito dopo la modifica che si vuole effettuare.
Il blocco e lo sblocco del file si ottiene generalmente con la funzione flock() su un file già aperto. La funzione richiede l'indicazione del flusso di file e del tipo di operazione che si vuole compiere.
flock flusso,operazione |
Per la precisione, il tipo di operazione si esprime attraverso un numero il cui valore dipende dal sistema operativo utilizzato effettivamente. Per evitare di doversi accertare di quale valore sia corretto per il proprio sistema, è possibile acquisire alcune macro attraverso l'istruzione seguente:
|
In questo modo, l'operazione può poi essere indicata attraverso i nomi: LOCK_SH, LOCK_EX, LOCK_NB e LOCK_UN.
Il blocco del file può essere richiesto in modo da mettere in pausa il programma fino a quando si riesce a ottenere il blocco, oppure no. Nel secondo caso, il programma deve essere in grado di riconoscere il fallimento dell'operazione e di comportarsi di conseguenza. Il blocco con attesa deve essere utilizzato con prudenza, perché può generare una situazione di stallo generale: il processo A apre e blocca il file X, il processo B apre e blocca il file Y e successivamente tenta anche con il file X che però è occupato; a questo punto anche il processo A tenta di aprire il file Y senza avere rilasciato il file X; infine i due processi si sono bloccati a vicenda.
Il blocco esclusivo di un file si ottiene con il tipo di operazione LOCK_EX; se si vuole evitare l'attesa dello sblocco da parte di un altro processo si deve aggiungere il valore di LOCK_NB. Lo sblocco di un file si ottiene con il tipo di operazione LOCK_UN.
Segue la descrizione di alcuni esempi.
|
Vengono eseguite le operazioni seguenti:
si caricano le costanti di definizione dei tipi di blocco attraverso l'istruzione use Fcntl ':flock';;
si apre il file /home/tizio/mioelenco
in aggiunta;
si blocca il file in modo esclusivo;
si compiono alcune operazioni che non sono indicate;
si rilascia il blocco.
|
Si tratta di una variante dell'esempio precedente, in cui si richiede un blocco esclusivo senza attesa. Se il blocco ha successo, si procede, altrimenti viene segnalata la presenza del blocco da parte di un altro processo.
Per qualche motivo, se si vuole sommare il valore della macro LOCK_EX assieme a quello di qualche altra, è necessario racchiuderla tra parentesi, come si vede nell'esempio. Probabilmente questo dipende dal modo in cui il valore viene generato. Per uniformità, nell'esempio si mostra racchiusa tra parentesi anche la macro LOCK_NB. Volendo verificare questa anomalia, basta provare ad assegnare a una variabile la somma di queste o di altre macro, visualizzando poi il risultato; se si prova una cosa del tipo $pippo = LOCK_EX+LOCK_NB;, senza parentesi, e poi si visualizza il contenuto di $pippo, si ottiene solo il valore due, mentre dovrebbe essere un sei! |
Le operazioni di I/O con i file richiedono la conoscenza del modo in cui si esegue la lettura, la scrittura e lo spostamento, del puntatore interno a un flusso di file. Fortunatamente, Perl gestisce tutto in modo piuttosto trasparente, soprattutto per ciò che riguarda la lettura. È il caso di ricordare che queste operazioni si compiono su file già aperti, di conseguenza si fa riferimento a loro tramite il flusso corrispondente.
La lettura di un flusso di file riferito a un file di testo è un'operazione molto semplice, basta utilizzare le parentesi angolari per ottenere la valutazione dello stesso che si traduce nella restituzione di una riga, nel caso di contesto scalare, o di tutto il file, nel caso di un contesto lista. L'esempio seguente restituisce una riga, a partire dalla posizione del puntatore del file fino al codice di interruzione di riga incluso, spostando in avanti il puntatore del file:
|
Per questo, dopo un'operazione di questo tipo, si esegue un chop() o un chomp(), in modo da eliminare il codice di interruzione di riga finale.
|
In alternativa, l'istruzione seguente restituisce tutto il file suddiviso in righe terminanti con il codice di interruzione di riga:
|
In pratica, l'array viene popolato con tanti elementi quante sono le righe del file. Anche in questo caso si può eseguire un chop() o un chomp(), che intervenga su ogni elemento dell'array:
|
La valutazione di un flusso di file in questo modo, quando il puntatore del file ha superato la fine del file, restituisce un valore indefinito che può essere utilizzato per controllare un ciclo di lettura. L'esempio seguente mostra in modo molto semplice come un ciclo while possa controllare la lettura di un flusso di file terminando quando questo ha raggiunto la conclusione.
|
La scrittura di un file avviene generalmente attraverso la funzione print() che inizia a scrivere a partire dalla posizione attuale del puntatore del file stesso.
print flusso lista |
print lista |
Se non viene specificato un flusso di file, tutto viene emesso attraverso lo standard output, oppure attraverso quanto specificato con la funzione select().
È il caso di osservare che l'argomento che specifica il flusso è separato dalla lista di stringhe da emettere solo attraverso uno o più spazi (non si usa la virgola). Per lo stesso motivo, se il flusso di file è contenuto in un elemento di un array, oppure è il risultato di un'espressione, ciò deve essere indicato in un blocco.
Segue la descrizione di alcuni esempi.
|
Scrive nel flusso di file indicato, a partire dalla posizione attuale del puntatore, il messaggio indicato come argomento.
|
Inserisce il messaggio nel file indicato da $elenco_file[$i].
|
Vengono eseguite le operazioni seguenti:
si caricano le costanti di definizione dei tipi di blocco attraverso l'istruzione use Fcntl ':flock';;
si apre il file /home/tizio/mioelenco
in aggiunta;
si blocca il file in modo esclusivo;
si inserisce una riga nel file;
si rilascia il blocco.
Lo spostamento del puntatore interno a un flusso di file avviene generalmente in modo automatico, sia in lettura, sia in scrittura. Si possono porre dei problemi, o dei dubbi, quando si accede simultaneamente a un file sia in lettura che in scrittura. Lo spostamento del puntatore può essere fatto attraverso la funzione seek().
seek flusso,posizione,partenza |
La posizione effettiva nel file dipende dal valore del secondo e del terzo argomento. Precisamente, il terzo argomento può essere zero, uno o due, in base al significato seguente:
|
Segue la descrizione di alcuni esempi.
|
Posiziona alla fine del file in modo da poter, successivamente, aggiungere qualcosa a questo.
|
Posiziona all'inizio del file.
|
Vengono eseguite le operazioni seguenti:
si caricano le costanti di definizione dei tipi di blocco attraverso l'istruzione use Fcntl ':flock';;
si apre il file /home/tizio/mioelenco
in aggiunta;
si blocca il file in modo esclusivo;
per sicurezza si posiziona il puntatore alla fine del file;
si inserisce una riga nel file;
si rilascia il blocco.
Nel momento in cui si apre un file, si deve attribuire il nome del flusso relativo. Fino a questo punto è stato visto l'uso di nomi dichiarati nell'istante dell'apertura, come nell'esempio seguente:
|
Da quel punto, il simbolo MIO_FLUSSO diviene ciò che identifica il flusso. È già stato mostrato anche il modo in cui è possibile trasferire il riferimento a questi simboli, come nell'esempio seguente:
|
Successivamente è possibile fare riferimento in modo indifferente al simbolo originale o alla variabile che vi punta:
|
In realtà, il simbolo che rappresenta un flusso, può anche essere una variabile, contenente una stringa qualunque: è il contenuto della variabile che identifica effettivamente il flusso. Si osservi l'esempio seguente:
|
Si vede la variabile $a che inizialmente riceve la stringa tizio e in questa situazione viene usata per aprire in lettura il file prova_1
. Subito dopo, la stessa variabile riceve la stringa caio e in questo modo viene usata per aprire in scrittura il file prova_2
. I due flussi sono identificati rispettivamente dalle stringhe tizio e caio; non ha importanza se queste stringhe sono contenute in una variabile o se sono usate direttamente come sono.
Più avanti, si può vedere che, quando $a contiene la stringa tizio, scrivere
|
oppure
|
dà lo stesso risultato: la lettura del flusso abbinato al file prova_1
. Ugualmente si può fare per il flusso in scrittura, con la differenza che non si può usare la stringa in modo delimitato:
|
Questa possibilità di gestire i flussi identificandoli subito attraverso delle variabili, facilita il trasferimento dell'indicazione dei flussi nelle chiamate di funzione, senza più il bisogno di creare dei riferimenti.
Si noti che non basta dichiarare un flusso indicando semplicemente una variabile, perché questa variabile deve essere inizializzata in qualche modo. Utilizzando una variabile non inizializzata sarebbe come volere identificare il flusso con la stringa nulla. |
Appunti di informatica libera 2006.07.01 --- Copyright © 2000-2006 Daniele Giacomini -- <daniele (ad) swlibero·org>
Dovrebbe essere possibile fare riferimento a questa pagina anche con il nome perl_gestione_dei_file.htm
[successivo] [precedente] [inizio] [fine] [indice generale] [indice ridotto] [translators] [docinfo] [indice analitico]