[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]

139. Perl: gestione dei file

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 verrà trascurato.

139.1 Organizzazione generale

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 farà riferimento attraverso il flusso di file.

Per una convenzione diffusa, i nomi attribuiti ai flussi di file sono sempre composti da lettere maiuscole, e questo 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.

139.1.1 Apertura

Quando è necessario aprire un file, cioè quando non si tratta dei flussi predefiniti, si utilizza la funzione open().

open <flusso>,<file>

La funzione utilizza quindi solo due argomenti: il nome del flusso di file e il nome effettivo del file, eventualmente con l'indicazione del percorso necessario a raggiungerlo. Per esempio,

open MIO_FILE, 'mio_file';

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. Questi 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/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.

open MIO_FILE, ">> /home/tizio/mio_file";
...
print MIO_FILE "ciao a tutti\n" );

Nello stesso modo in cui si possono gestire i file su disco, si può accedere a una pipeline, 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 della pipeline; se si trova alla fine, significa che si vuole leggere attingendo dati dallo standard output della pipeline.

open MIAPIPE, "| sort > /home/tizio/mio_file";

L'esempio appena mostrato apre una pipeline in scrittura. Ciò che verrà ricevuto dalla pipeline sarà ordinato e registrato nel file /home/tizio/mio_file.

open MIAPIPE, "ls -l |";

L'esempio precedente apre una pipeline in lettura in modo da poter elaborare il risultato del comando ls -l.

139.1.2 Chiusura

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, quindi non è strettamente necessario chiudere un file prima di riaprirlo.

139.2 Condivisione

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.

139.2.1 Blocco dei file

Il modo più semplice per impedire che un file possa essere modificato da un altro processo, è quello di bloccarlo, cioè eseguire un lock su di esso, 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:

use Fcntl ':flock';

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, e 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.

Esempi

use Fcntl ':flock';   # importa le costanti LOCK_...
...
open ( ELENCO, ">> /home/tizio/mioelenco" );
flock ( ELENCO, LOCK_EX );
...
flock ( ELENCO, LOCK_UN );

Vengono eseguite le operazioni seguenti:

use Fcntl ':flock';   # importa le costanti LOCK_...
...
open ( ELENCO, ">> /home/tizio/mioelenco" );
if ( flock ( ELENCO, (LOCK_EX)+(LOCK_NB) ) ) {
	...
	flock ( ELENCO, LOCK_UN );
} else {
	print STDOUT "Il file è impegnato.\n";
};

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. *1*

139.3 I/O con i file

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.

139.3.1 Lettura

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. Per esempio:

$riga = <MIOHANDLE>;

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.

chop $riga;

In alternativa,

@file = <MIOHANDLE>;

restituisce tutto il file suddiviso in righe terminanti con il codice di interruzione di riga. In pratica, l'array conterrà tanti elementi quante sono le righe del file. Anche in questo caso si può eseguire un chop() o un chomp(), che interverrà su ogni elemento dell'array.

chop( @file );

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.

while ( $riga = <MIOHANDLE> ) {
	...
};

139.3.2 Scrittura

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, e non da una 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.

Esempi

print MIOHANDLE "Ciao, come stai?\n";

Scrive nel flusso di file indicato, a partire dalla posizione attuale del puntatore, il messaggio indicato come argomento.

print { $elenco_file[$i] } "Bla bla bla\n";

Inserisce il messaggio nel file indicato da $elenco_file[$i].

use Fcntl ':flock';   # importa le costanti LOCK_...
...
open ( ELENCO, ">> /home/tizio/mioelenco" );
flock ( ELENCO, LOCK_EX );
print ELENCO $daelencare,"\n";
flock ( ELENCO, LOCK_UN );

Vengono eseguite le seguenti operazioni:

139.3.3 Spostamento del puntatore

Lo spostamento del puntatore interno a un flusso di file avviene generalmente in modo automatico, sia in lettura che 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 0, 1 o 2 in base al significato seguente:

Esempi

seek( MIO_FILE, 0, 2 );

Posiziona alla fine del file in modo da poter, successivamente, aggiungere qualcosa a questo.

seek( MIO_FILE, 0, 0 );

Posiziona all'inizio del file.

use Fcntl ':flock';   # importa le costanti LOCK_...
...
open ( ELENCO, ">> /home/tizio/mioelenco" );
flock ( ELENCO, LOCK_EX );
seek( ORDINI, 0, 2 );
print ELENCO $daelencare,"\n";
flock ( ELENCO, LOCK_UN );

Vengono eseguite le seguenti operazioni:

---------------------------

Appunti Linux 1999.09.21 --- Copyright © 1997-1999 Daniele Giacomini --  daniele @ pluto.linux.it


1.) Per qualche motivo oscuro, 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 2, mentre dovrebbe essere un 6!


[inizio] [indice generale] [precedente] [successivo] [indice analitico] [contributi]