perlfaq5 - File e Formati ($Revision: 1.25 $, $Date: 2008-03-31 21:14:13 $)
Questa sezione tratta l'I/O e tutto ciò che inizia per ``f'': filehandle,
flushing, formati, e footer.
Il Perl non supporta esattamente l'output privo di buffer (salvo nel caso
in cui possiate eseguire syswrite(OUT, $char, 1) ) benché supporti
il ``command buffering'' [utilizzo del buffer a comando, NdT], nel quale una
scrittura fisica viene eseguita dopo ogni comando di output.
La libreria standard di I/O del C (stdio) di solito utilizza il buffer per i
caratteri inviati alle periferiche cosicché non ci sia una chiamata
di sistema per ogni byte. In molte implementazioni di stdio, il tipo di
output con buffer e la dimensione del buffer variano a seconda del tipo
di periferica. Le funzioni Perl print() e write() di solito hanno l'output
con buffer, mentre syswrite() evita del tutto l'uso del buffer.
Se volete che il vostro output venga immediatamente inviato quando eseguite
print() o write() (per esempio, per alcuni protocolli di rete), dovete
settare il flag di terminazione automatica delle operazioni di I/O in corso
dell'handle. Questo flag è costituito dalla variabile Perl $|
e quando viene impostata ad un valore vero, il Perl svuoterà il buffer
dell'handle dopo ogni print() o write(). Impostare $| influenza l'uso del buffer
solo per il filehandle di default attualmente selezionato. Scegliete questo
handle con la chiamata select() ad un argomento (vedete perlvar/$
e perlfunc/select).
Usate select() per scegliere l'handle desiderato, poi impostate le sue
variabili per filehandle.
$vecchio_fh = select(OUTPUT_HANDLE);
$| = 1;
select($vecchio_fh);
Alcune espressioni idiomatiche possono gestirlo in una singola istruzione:
select((select(OUTPUT_HANDLE), $| = 1)[0]);
$| = 1, select $_ for select OUTPUT_HANDLE;
Alcuni moduli offrono un accesso orientato agli oggetti agli handle e alle
loro variabili, sebbene possa essere eccessivo se questa è l'unica
cosa per cui li usate. Potete usare IO::Handle:
use IO::Handle;
open(DEV, ">/dev/printer"); # ma E<egrave> questa?
DEV->autoflush(1);
oppure IO::Socket:
use IO::Socket; # questa E<egrave> tipo una pipe?
my $sock = IO::Socket::INET->new( 'www.esempio.com:80' );
$sock->autoflush();
Usate il modulo Tie::File, che è incluso nella distribuzione
standard fin dal Perl 5.8.0.
Un modo efficente ed elegante è quello di contare i caratteri di
ritorno a capo nel file. Il seguente programma usa una caratteristica di
tr///, come viene documentata in perlop. Se le righe del vostro file di
testo non terminano con il ritorno a capo, il file non è
propriamente un file di testo, quindi il programma potrebbe restiruire
meno righe di quello che vi aspettereste.
$linee = 0;
open(FILE, $nomefile) or die "Non posso aprire '$nomefile': $!";
while(sysread FILE, $buffer, 4096) {
$linee += ($buffer =~ tr/\n//);
}
close FILE;
Questo presuppone che non si facciano giochetti con l'uso di tr sul ritorno
a capo.
-i imposta il valore della variabile Perl $^I , che a sua volta influisce
sul comportamento di <> ; consultate perlrun per maggiori dettagli.
Modificando direttamente le variabili appropriate, potete ottenere lo stesso comportamento
all'interno di un grosso programma. Per esempio:
# ...
{
local($^I, @ARGV) = ('.orig', glob("*.c"));
while (<>) {
if ($. == 1) {
print "Questa linea dovrebbe apparire in cima ad ogni file\n";
}
s/\b(p)earl\b/${1}erl/i; # Corretto un errore di battitura, mantiene le maiuscole/minuscole
print;
close ARGV if eof; # Reimposta $.
}
}
# $^I e @ARGV qua ritornano ai loro vecchi valori
Questo blocco modifica tutti i file .c nella directory corrente,
lasciando una copia del dato originale da qualunque file in un nuovo
file .c.orig .
(contributo di brian d foy)
Usate il modulo File::Copy. È disponibile con il Perl e può
fare una copia effettiva attraverso i filesystem e fa la sua magia in maniera
portabile.
use File::Copy;
copy( $originale, $nuova_copia ) or die "Copia fallita: $!";
Se non potete usare File::Copy, dovrete farlo voi stessi: aprire
il file originale, aprire il file di destinazione, poi scrivere
sul file di destinazione quello che avete letto nell'originale.
Se non vi serve conoscere il nome del file, potete usare open() con
undef al posto del nome del file. La funzione open() crea un file
temporaneo anonimo.
open my $tmp, '+>', undef or die $!;
Altrimenti, potete usare il modulo File::Temp.
use File::Temp qw/ filetemp dirtemp /;
$dir = dirtemp( CLEANUP => 1 );
($fh, $nomefile) = filetemp( DIR => $dir );
# oppure, se non avete bisogno del nome del file
$fh = filetemp( DIR => $dir );
File::Temp è un modulo standard fin dal Perl 5.6.1. Se avete
installato un Perl abbastanza recente, usate il metodo di classe
new_tempfile dal modulo IO::File per ottenere un file aperto in
lettura e scrittura. Usate quanto segue se non avete la necessità
di sapere il nome del file:
use IO::File;
$fh = IO::File->new_tmpfile()
or die "Impossibile creare un nuovo file temporaneo: $!";
Se siete costretti a creare un file temporaneo a mano, usate l'ID
del processo e/o il valore dell'ora corrente. Se avete bisogno di
più file temporanei in un unico processo, usate un contatore:
BEGIN {
use Fcntl;
my $dir_temp = -d '/tmp' ? '/tmp' : $ENV{TMPDIR} || $ENV{TEMP};
my $nome_base = sprintf("%s/%d-%d-0000", $dir_temp, $$, time());
sub file_temp {
local *FH;
my $conta = 0;
until (defined(fileno(FH)) || $conta ++ > 100) {
$nome_base =~ s/-(\d+)$/"-" . (1 + $1)/e;
# O_EXCL e` richiesto per ragioni di sicurezza.
sysopen(FH, $nome_base, O_WRONLY|O_EXCL|O_CREAT);
}
if (defined(fileno(FH))) {
return (*FH, $nome_base);
} else {
return ();
}
}
}
Il modo più efficiente è usare pack() e unpack(). È
più veloce rispetto all'uso di substr() quando si ricevono molte,
molte stringhe. È più lento se se ne ricevono poche.
Ecco un pezzo di codice d'esempio per dividere in pezzi e poi rimettere
insieme alcune linee di input in formato fisso, in questo caso dall'output
di un normale ps stile Berkeley:
# esempio di linea in input
# 15158 p5 T 0:00 perl /home/larsen/script/
my $PS_T = 'A6 A4 A7 A5 A*';
open my $ps, '-|', 'ps';
print scalar <$ps>;
my @campi = qw( pid tt stat time command );
while (<$ps>) {
my %processo;
@processo{@campi} = unpack($PS_T, $_);
for my $campo ( @campi ) {
print "$campo: <$processo{$campo}>\n";
}
print 'line=', pack($PS_T, @processo{@campi} ), "\n";
}
Abbiamo usato uno hash slice in maniera da manipolare facilmente i campi di ogni
riga. Immagazzinare le chiavi in un array significa che è facile operarvi
come gruppo oppure interarvi con un for. Evita anche di inquinare il programma con
variabili globali e l'utilizzo di riferimenti simbolici.
Con la versione 5.6 di perl, open() crea automaticamente handle per file e
directory sotto forma di riferimenti se gli viene passata una variabile
scalare non inizializzata. Potete poi passare tali riferimenti come qualunque
altro scalare, e usarli al posto degli handle dotati di nome.
open my $fh, $nome_file;
open local $fh, $nome_file;
print $fh "Salve Mondo!\n";
processa_file( $fh );
Prima della versione 5.6, dovevate avere a che fare con vari idiomi che
usavano i typeglob e che potete vedere nel codice meno recente.
open FILE, "> $nomefile";
processa_typeglob( *FILE );
processa_riferimento( \*FILE );
sub processa_typeglob { local *FH = shift; print FH "Typeglob!" }
sub processa_riferimento { local $fh = shift; print $fh "Riferimento!" }
Se volete creare molti handle anonimi, dovreste controllare i moduli Symbol
e IO::Handle.
Utilizzare un filehandle in maniera indiretta significa usare qualcosa di
diverso da un simbolo in un luogo dove è richiesto un filehandle
Di seguito sono riportati alcuni modi per ottenere un filehandle indiretto:
$fh = UN_FH; # parola senza virgolette; e` ostile a strict subs
$fh = "UN_FH"; # e` ostile a strict-refs; solo nello stesso package
$fh = *UN_FH; # typeglob
$fh = \*UN_FH; # reference ad un typeglob (bless-abile)
$fh = *UN_FH{IO}; # IO::Handle blessed dal typeglob *UN_FH
Oppure potete servirvi del metodo new da uno dei moduli IO::* per creare
un filehandle anonimo, memorizzarlo in una variabile scalare, ed utilizzarlo
come se fosse un normale filehandle.
use IO::Handle; # 5.004 o superiore
$fh = IO::Handle->new();
Usate poi uno di quelli come fareste con un normale filehandle. Ovunque
Perl si aspetti un filehandle, al suo posto può essere utilizzato
un filehanle indiretto. Un filehandle indiretto è semplicemente
una variabile scalare che contiene un filehandle. Funzioni quali print ,
open , seek , o l'operatore <FH> accetteranno sia un filehandle
vero e proprio che una variabile scalare che ne contenga uno:
($ifh, $ofh, $efh) = (*STDIN, *STDOUT, *STDERR);
print $ofh "Scrivilo: ";
$ottenuto = <$ifh>;
print $efh "Cos'era quello: $ottenuto";
Se state passando un filehandle ad una funzione, potete scrivere tale
funzione in due modi:
sub accetta_fh {
my $fh = shift;
print $fh "Sto inviando ad un filehandle indiretto\n";
}
Oppure potete localizzare un typeglob ed utilizzare direttamente il filehandle:
sub accetta_fh {
local *FH = shift;
print FH "Sto inviando ad un filehandle localizzato\n";
}
Entrambi gli stili funzionano sia con oggetti che con typeglob che con
filehandle reali. (Potrebbero anche funzionare con stringhe in alcune
circostanze, ma la cosa è rischiosa.)
accept_fh(*STDOUT);
accept_fh($handle);
Negli esempi sopra riportati, abbiamo assegnato il filehandle ad una variabile
scalare prima di utilizzarlo. Ciò accade perché solo le
semplici variabili scalari, e non espressioni o elementi di hash oppure
array, possono essere usati con le funzioni integrate come print e
printf , o con l'operatore diamante [<FH>, NdT]. L'utilizzo di qualcosa
di diverso da una semplice variabile scalare come filehandle non è
consentito, ed il programma non compilerà nemmeno:
@fd = (*STDIN, *STDOUT, *STDERR);
print $fd[1] "Scrivilo: "; # ERRATO
$ottenuto = <$fd[0]>; # ERRATO
print $fd[2] "Cos'era quello: $ottenuto"; # ERRATO
Con print e printf potete aggirare ciò servendovi di un blocco
ed un'espressione al posto del filehandle:
print { $fd[1] } "cose carine\n";
printf { $fd[1] } "Peccato per la povera %x.\n", 3_735_928_559;
# Peccato per la povera deadbeef. [convenzionalmente, uno schema esadecimale utilizzato per riempire parole di memoria, NdT]
Questo blocco è un blocco valido come qualsiasi altro, quindi potete
inserire codice più complesso al suo interno. Il codice di seguito
riportato invia il messaggio in uno dei due posti:
$ok = -x "/bin/cat";
print { $ok ? $fd[1] : $fd[2] } "cat stat $ok\n";
print { $fd[ 1+ ($ok || 0) ] } "cat stat $ok\n";
Questo approccio, consistente nel trattare print e printf come chiamate
a metodi di un oggetto, non funziona con l'operatore diamante. Ciò
accade perché esso è un vero operatore, non solo una funzione
con argomenti non separati da virgola. Ponendo che abbiate memorizzato i
tyeglob nella vostra struttura come indicato in precedenza, potete utilizzare
la funzione integrata readline per leggere un record allo stesso modo
in cui fa <> . Posta l'inizializzazione indicata prima per @fd,
ciò dovrebbe funzionare, ma solo perché readline() richiede
un typeglob. Non funziona con oggetti o stringhe, il che potrebbe essere un
bug che non abbiamo ancora corretto.
$ottenuto = readline($fd[0]);
Va notato che la debolezza dei filehandle indiretti non è collegata
al fatto che essi siano stringhe, typeglob, oggetti, o qualsiasi altra cosa.
è la sintassi degli operatori fondamentali. Il gioco degli oggetti
non vi è di alcun aiuto qui.
Non c'è un modo predefinito per fare ciò, ma perlform
contiene un paio di tecniche che rendono la cosa possibile all'hacker
intrepido.
Per una funzione swrite(), consultate perlform/``Accessing Formatting Internals''.
(contributo di brian d foy e Benjamin Goldberg)
[a differenza dell'Italia, nei paesi anglosassoni, il separatore delle
migliaia è la virgola mentre quello dei decimali è il
punto, NdT]
Potete utilizzare the Number::Format manpage per separare il numero in porzioni più piccole.
Gestisce correttamente il locale, per quelli fra voi che vogliano inserire dei punti
(o qualsiasi altra cosa vi chiedano di usare, veramente).
Questa subroutine aggiungerà le virgole al vostro numero:
sub virgolante {
local $_ = shift;
1 while s/^([-+]?\d+)(\d{3})/$1,$2/;
return $_;
}
Questa espressione regolare di Benjamin Goldberg, aggiungerà virgole ai numeri:
s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
È più semplice vederla con i commenti:
s/(
^[-+]? # l'inizio del numero.
\d+? # la prima cifra prima della prima virgola
(?= # seguita da, (ma non incluso nel match):
(?>(?:\d{3})+) # alcuni multipli positivi di tre cifre.
(?!\d) # un multiplo *esatto*, non x * 3 + 1 o quant'altro.
)
| # oppure:
\G\d{3} # dopo l'ultimo gruppo, prendi 3 cifre
(?=\d) # ma devono avere altre cifre dopo di loro.
)/$1,/xg;
Utilizzate l'operatore (glob()) <>, documentato in perlfunc. Le vecchie
versioni di Perl richiedono che abbiate installato una shell che espande
le tilde. Le versioni più recenti di Perl contengono direttamente questa
caratteristica al loro interno. Il modulo File::KGlob (disponibile da CPAN)
offre una funzionalità di glob più portabile.
All'interno di Perl, potete utilizzare direttamente questo codice:
$nomefile =~ s{
^ ~ # trova una tilde ad inizio stringa
( # salva in $1
[^/] # un carattere diverso da slash
* # ripetuto una o piu` volte (0 significa se stessi)
)
}{
$1
? (getpwnam($1))[7]
: ( $ENV{HOME} || $ENV{LOGDIR} )
}ex;
Perché state utilizzando qualcosa di simile a quanto di seguito
indicato, che tronca il file e poi vi consente l'accesso ad esso in
lettura e scrittura:
open(FH, "+> /percorso/nome"); # SBAGLIATO (quasi sempre)
Ops. Dovete invece utilizzare questo, che causerà un errore se
il file non esiste.
open(FH, "+< /percorso/nome"); # apre per aggiornare
L'utilizzo di ``>'' cancella il contenuto in ogni caso, o crea il file.
L'utilizzo di ``<'' non fa mai nessuna delle due cose. Il ``+'' non cambia
questo comportamento.
Di seguito sono riportati degli esempi di vari tipi di apertura di file.
Quelli che utilizzano sysopen() presuppongono:
use Fcntl;
Per aprire un file in lettura:
open(FH, "< $percorso") || die $!;
sysopen(FH, $percorso, O_RDONLY) || die $!;
Per aprire un file in scrittura, creando se necessario il nuovo file
oppure troncando quello vecchio:
open(FH, "> $percorso") || die $!;
sysopen(FH, $percorso, O_WRONLY|O_TRUNC|O_CREAT) || die $!;
sysopen(FH, $percorso, O_WRONLY|O_TRUNC|O_CREAT, 0666) || die $!;
Per aprire un file in scrittura, creando un nuovo file che non deve
già esistere:
sysopen(FH, $percorso, O_WRONLY|O_EXCL|O_CREAT) || die $!;
sysopen(FH, $percorso, O_WRONLY|O_EXCL|O_CREAT, 0666) || die $!;
Per aprire un file per aggiungere dati, creandolo se necessario:
open(FH, ">> $percorso") || die $!;
sysopen(FH, $percorso, O_WRONLY|O_APPEND|O_CREAT) || die $!;
sysopen(FH, $percorso, O_WRONLY|O_APPEND|O_CREAT, 0666) || die $!;
Per aprire un file, che deve già esistere, per aggiungere dati:
sysopen(FH, $percorso, O_WRONLY|O_APPEND) || die $!;
Per aprire un file, che deve già esistere, per aggiornarlo:
open(FH, "+< $percorso") || die $!;
sysopen(FH, $percorso, O_RDWR) || die $!;
Per aprire un file per aggiornarlo, creandolo se necessario:
sysopen(FH, $percorso, O_RDWR|O_CREAT) || die $!;
sysopen(FH, $percorso, O_RDWR|O_CREAT, 0666) || die $!;
Per aprire un file, che non deve già esistere, per aggiornarlo:
sysopen(FH, $percorso, O_RDWR|O_EXCL|O_CREAT) || die $!;
sysopen(FH, $percorso, O_RDWR|O_EXCL|O_CREAT, 0666) || die $!;
Per aprire un file senza bloccare, creandolo se necessario:
sysopen(FH, "/tmp/unfile", O_WRONLY|O_NDELAY|O_CREAT)
or die "non riesco ad aprire /tmp/unfile: $!":
Sappiate che né la creazione né la cancellazione dei file
sono garantite essere operazioni atomiche quando si lavora su NFS. Due
processi potrebbero infatti creare o cancellare lo stesso file con successo!
Dunque O_EXCL non è così esclusivo come potreste volere.
Consultate anche anche il nuovo perlopentut se ne siete in possesso
(nuovo nella versione 5.6).
L'operatore <> effettua un'operazione di globbing [espansione degli
argomenti, NdT] (guardate sopra). Nelle versioni di Perl precedenti alla
5.6.0, l'operatore interno glob() effettua un fork di csh(1) per compiere
la reale espansione degli argomenti, ma csh non può gestire
più di 127 elementi e quindi ritorna il messaggio di errore
Argument list too long . Chi ha installato tcsh al posto di csh non
soffre di questo problema, ma gli utenti potrebbero essere sorpresi di
ciò.
Per aggirare questo problema, aggiornate a Perl v5.6.0 o successivo, oppure
effettuate voi stessi l'espansione con readdir() e le espressioni regolari,
oppure servitevi di un modulo come File::KGlob, che non usa la shell per
effettuare l'espansione.
A causa dell'attuale implementazione su alcuni sistemi operativi, quando
utilizzate in contesto scalare la funzione glob() o il suo alias costituito
dalle parentesi angolari, potreste causare una perdita di memoria e/o un
comportamento imprevedibile. È dunque meglio utilizzare glob()
solamente in contesto di lista.
(contributo di Brian McCauley)
La speciale forma a due argomenti della funzione open() del Perl
ignora gli spazi posti alla fine dei nomi dei file, ed
interpreta in maniera speciale alcuni caratteri posti all'inizio (oppure
un ``|'' posto alla fine). Nelle vecchie versioni di Perl questa era la sola
versione di open() e dunque essa è assai comune in codice e libri datati.
A meno di non aver un particolare motivo per usare la forma a due argomenti,
dovreste usare la forma di open() a tre argomenti che non tratta alcun
carattere nel filename come speciale.
open FILE, "<", " file "; # il nome del file e` " file "
open FILE, ">", ">file"; # il nome del file e` ">file"
Se il vostro sistema operativo supporta una valida utility mv(1) oppure
il suo equivalente funzionale, quanto segue funziona:
rename($vecchio, $nuovo) or system("mv", $vecchio, $nuovo);
Può risultare più portabile l'utilizzo del modulo File::Copy.
Copiate semplicemente il nuovo file con il nuovo nome (controllando i valori
di ritorno), e poi cancellate quello vecchio. Semanticamente, ciò
non è equivalente all'utilizzo di rename(), che preserva
metainformazioni quali permessi, dati, informazioni sull'inode, ecc.
Le nuove versioni di File::Copy esportano una funzione move().
La funzione integrata in Perl, flock() (vedete perlfunc per i dettagli),
chiamerà flock(2) se esiste, fcntl(2) se non esiste (sulle versioni
perl 5.004 e successive) e lockf(3) se non esiste nessuna delle due
precedenti chiamate di sistema. Su alcuni sistemi, potrebbe anche usare
una differente forma di locking nativo. Ecco alcune cose da sapere sul
flock() del Perl:
-
Produce un errore fatale se non esiste alcuna delle tre chiamate di sistema
(o loro strette equivalenti).
-
lockf(3) non prevede il locking condiviso e richiede che il filehandle sia
aperto per la scrittura (o per l'append, o per la lettura/scrittura).
-
Alcune versioni di
flock() non possono fare il lock di file in rete (ad
esempio su file system NFS) dunque si dovrebbe forzare l'uso di fcntl(2)
quando si fa il build del Perl. Ma persino questo è dubbio, nel
migliore dei casi. Vedete la voce flock in perlfunc e il file INSTALL
nella distribuzione sorgente per le informazioni su come fare il build del
Perl per farlo.
Due sematiche di flock potenzialmente non ovvie ma tradizionali, sono il
fatto che esso aspetta indefinitamente fino a che il lock non venga assegnato
e che i suoi lock siano soltanto consultivi. Tali lock discrezionali
sono più flessibili, ma offrono poche garanzie. Questo significa
che i file su cui è stato fatto un lock con flock() possono essere
modificati anche da programmi che non usano flock(). Le automobili che si
fermano con il semaforo rosso vanno d'accordo tra loro ma non con le
automobili che non si fermano con il semaforo rosso. Per i dettagli vedete
perlport, la specifica documentazione del vostro port oppure le vostre
manpage locali, specifiche per il sistema. È meglio che si assuma
un comportamento tradizionale se si stanno scrivendo programmi portabili
(Se non lo si sta facendo, ci si dovrebbe sentire, come sempre, perfettamente
liberi di scriversi le proprie idiosincrasie di sistema (a volte chiamate
``caratteristiche''). L'aderenza alle faccende di portabilità senza
originalità non dovrebbe influenzare la maniera con cui portate a
termine il vostro lavoro).
Per maggiori informazioni sul lock dei file vedete anche
perlopentut/``File Locking'' se ce l'avete (nuovo per il 5.6).
Una parte comune di codice DA NON USARE è questa:
sleep(3) while -e "file.lock"; # PER FAVORE NON SI USI
open(LCK, "> file.lock"); # QUESTO CODICE NON CORRETTO
Questa è una classica race condition: si impiegano due passi per
fare qualcosa che deve essere fatto in uno. Questo è il motivo
per cui l'hardware dei computer fornisce una istruzione atomica di
test-and-set. In teoria, questo ``dovrebbe'' funzionare:
sysopen(FH, "file.lock", O_WRONLY|O_EXCL|O_CREAT)
or die "non posso aprire file.lock: $!":
eccetto che, deplorevolmente, la creazione di file (e la cancellazione)
non è atomica su NFS, dunque questo non funzionerà (non
tutte le volte, almeno) in rete. Sono stati suggeriti vari schemi che
coinvolgono link(), ma questi tendono a implicare del busy-wait, anch'esso
poco desiderabile.
Non vi ha mai detto nessuno che i contatori di accesso sulle pagine web
sono inutili? Non contano gli accessi reali, sono una perdita di tempo e
servono solo ad alimentare la vanità di chi li utilizza. Meglio
prendere un numero casuale. È più realistico.
Ad ogni modo, questo è quello che potete fare se proprio non potete
farne a meno:
use Fcntl qw(:DEFAULT :flock);
sysopen(FH, "numfile", O_RDWR|O_CREAT) or die "impossibile aprire numfile: $!";
flock(FH, LOCK_EX) or die "impossibile eseguire flock su numfile: $!";
$num = <FH> || 0;
seek(FH, 0, 0) or die "impossibile tornare all'inizio di numfile: $!";
truncate(FH, 0) or die "impossibile troncare numfile: $!";
(print FH $num+1, "\n") or die "impossibile scrivere numfile: $!";
close FH or die "impossibile chiudere numfile: $!";
Ecco un contatore di accessi alla pagina decisamente migliore :
$accessi = int( (time() - 850_000_000) / rand(1_000) );
Se il numero di accessi non fa colpo sui vostri amici, potete provare ad
impressionarli con il codice. :-)
Se vi trovate su un sistema che implementa correttamente flock() ed
utilizzate il codice di esempio per aggiungere dati ad un file da
``perldoc -f flock'', andrà tutto bene anche se il sistema operativo
su cui siete non implementa l'aggiunta ai file in maniera corretta
(ponendo che un tale sistema esista.) Dunque, se vi va bene limitarvi a
sistemi operativi che implementano flock() (e non si tratta poi di una
grande restrizione), allora quanto detto è ciò che dovete fare.
Se sapete che utilizzerete solamente un sistema che implementa l'aggiunta
ai file correttamente (ad es. non Win32), allora potete omettere seek()
dal codice sopra indicato.
Se sapete che state scrivendo codice che girerà solamente su un
sistema operativo e su un filesystem che implementano l'aggiunta ai file
correttamente (ad esempio un filesystem locale su un moderno Unix), e
tenete il file in modo block-buffered e scrivete una quantità
inferiore alla grandezza di un buffer tra ogni flush [completamento
delle operaziono di I/O, NdT] manuale, allora ciascun contenuto del buffer
è garantito che verrà scritto alla fine del file in una volta sola,
senza che l'output di qualcun altro possa interferire. Potete anche
utilizzare la funzione syswrite(), che altro non è che un wrapper
attorno alla chiamata di sistema write() del vostro sistema.
Teoreticamente, rimane comunque una piccola possibilità che un
segnale interrompa l'operazione di write() a livello di sistema prima che
essa sia completata. C'è anche la possibilità che alcune
implementazioni di STDIO chiamino più di una write() a livello di
sistema anche se il buffer iniziale era vuoto. Ci potrebbero essere alcuni
sistemi dove questa probabilità è ridotta a zero.
Se state semplicemente tentando di modificare una parte di un file binario,
in molti casi qualcosa di semplice come ciò che segue funziona:
perl -i -pe 's{window manager}{window mangler}g' /usr/bin/emacs
Comunque, se i vostri record sono di lunghezza fissa, potreste preferire
qualcosa di questo tipo:
$DIMREC = 220; # dimensione del record, in byte
$numerc = 37; # record da aggiornare
open(FH, "+<qualcosa") || die "non posso aggiornare qualcosa: $!";
seek(FH, $numrec * $DIMREC, 0);
read(FH, $numrec, $DIMREC) == $DIMREC || die "non posso leggere $numrec: $!";
# sovrascrivi questo record
seek(FH, -$DIMREC, 1);
print FH $record;
close FH;
Il locking e la gestione degli errori sono lasciati come esercizi al
lettore. Non dimenticatevene o ve ne pentirete.
Se volete sapere quando è stata l'ultima volta in cui il file
è stato letto, scritto o ha subito una modifica dei suoi meta-dati
(proprietario, ecc.), usate le operazioni di test dei file -A, -M o
-C, che sono documentate in perlfunc. Esse recuperano l'età
del file in giorni, espressa con un numero in virgola mobile (misurata dal
momento di inizio del vostro programma). Alcune piattaforme potrebbero non
disporre di queste informazioni. Consultate perlport per i dettagli. Per
conoscere il tempo ``nudo e crudo'' sotto forma di secondi passati dal momento,
dovreste chiamare la funzione stat, quindi usare localtime(), gmtime()
oppure POSIX::strftime() per convertirlo in una forma leggibile da esseri
umani.
Ecco un esempio:
$ultima_scrittura_in_secondi = (stat($file))[9];
printf "il file %s e` stato aggiornato il %s\n", $file,
scalar localtime($ultima_scrittura_in_secondi);
Se preferite qualcosa di più leggibile, usate il modulo File::stat
(parte della distribuzione standard nelle versioni 5.004 e successive):
# il controllo degli errori e` lasciato al lettore come esercizio
use File::stat;
use Time::localtime;
$stringa_data = ctime(stat($file)->mtime);
print "il file $file e` stato aggiornato il $stringa_data\n";
L'approccio di POSIX::strftime() ha il vantaggio di essere, in teoria,
indipendente dalle impostazioni di locali correnti. Consultate perllocale
per dettagli.
[1] Con il termine 'timestamp' si intende in generale l'orario di qualcosa:
in questo caso, come viene spiegato nel testo della FAQ, l'orario di accesso,
scrittura o modifica dei metadati, NdT
Usate la funzione utime() documentata in perlfunc/utime. A titolo di
esempio, ecco un piccolo programma che copia gli istanti di lettura e
scrittura dal suo primo argomento a tutti i restanti.
if (@ARGV < 2) {
die "usate: cptimes file_del_timestamp altri_file ...\n";
}
$timestamp = shift;
($atime, $mtime) = (stat($timestamp))[8,9];
utime $atime, $mtime, @ARGV;
Il controllo degli errori è, al solito, lasciato come esercizio per
il lettore.
Per utime, perldoc ha anche un esempio che ha lo stesso effetto di touch(1)
sui che esistono già.
Alcuni filesystem hanno una limitata abilità di immagazzinare gli
istanti di un file al livello di precisione che ci si aspetta. Per esempio,
i filsystem FAT e HPFS non sono in grado di creare le date sui file con una
granularità più fine di due secondi. Questa è una
limitazione dei filesystem, non di utime().
[1] Con il termine 'timestamp' si intende in generale l'orario di qualcosa:
in questo caso, come viene spiegato nel testo della FAQ, l'orario di accesso,
scrittura o modifica dei metadati, NdT
Per connettere un filehandle a diversi filehandle di output, potete usare i
moduli IO::Tee o Tie::FileHandle::Multiplex.
Se dovete farlo una volta sola, potete stampare su ogni filehandle uno alla
volta.
for $fh (FH1, FH2, FH3) { print $fh "qualsiasi cosa\n" }
Potete usare il modulo File::Slurp per fare questo in un solo passo.
use File::Slurp;
$tutto_quanto = read_file($nomefile); # l'intero file in uno scalare
@tutte_le_linee = read_file($nomefile); # una linea per elemento
L'approccio proprio del Perl per svolgere un'operazione su tutte le linee
di un file, consiste nel farlo una linea alla volta:
open (INPUT, $file) || die "non posso aprire $file: $!";
while (<INPUT>) {
chomp;
# fai qualcosa con $_
}
close(INPUT) || die "non posso chiudere $file: $!";
Questo è tremendamente più efficiente del leggere l'intero
file in memoria come un array di linee e poi svolgere l'operazione su di esso
un elemento alla volta, che è spesso--se non quasi sempre--l'approccio
sbagliato. Tutte le volte che vedete questo:
@linee = <INPUT>;
dovreste pensare a lungo e accuratamente sul perché avete bisogno di
avere tutto caricato in una volta. È semplicemente una soluzione
non scalabile. Potreste anche trovare più divertente l'uso del
modulo standard Tie::File, o i binding $DB_RECNO del modulo DB_File, che
vi permettono di legare un array ad un file in maniera tale che accedendo
ad un elemento dell'array di fatto si acceda alla linea corrispondente
nel file.
Potete leggere l'intero contenuto del filehandle in uno scalare
{
local(*INPUT, $/);
open (INPUT, $file) || die "non posso aprire $file: $!";
$variabile = <INPUT>;
}
Quel codice rende temporaneamente indefinito il vostro separatore di record,
e chiuderà automaticamente il file all'uscita del blocco. Se il
file è già aperto, limitatevi ad usare questo:
$variabile = do { local $/; <INPUT> };
Per i file ordinari potete inoltre usare la funzione read.
read( INPUT, $variabile, -s INPUT );
Il terzo argomento verifica la dimensione in byte dei dati sul filehandle
INPUT e legge quel numero di byte nel buffer $variabile.
Usate la varaibile $/ (vedete perlvar per i dettagli). Potete sia
impostarla a "" per eliminare i paragrafi vuoti ("abc\n\n\n\ndef" ,
per esempio, viene trattato come se fossero due paragrafi e non tre),
oppure a "\n\n" per accettare paragrafi vuoti.
Notate che una linea vuota non deve contenere spazi. Dunque
"fred\n \nvariecose\n\n" è un paragrafo, ma "fred\n\nvariecose\n\n"
sono due.
Per la maggior parte dei filehandle potete usare la funzione integrata
getc() , ma essa non funzionerà (facilmente) per il terminale.
Per STDIN, usate il modulo Term::ReadKey da CPAN oppure il codice d'esempio
in perlfunc/getc.
Se il vostro sistema supporta l'interfaccia portabile per la programmazione
del sistema operativo (portable operating system programming
interface: POSIX), potete usare il codice seguente, che come potete
notare oltretutto disattiva l'echo dei caratteri.
#!/usr/bin/perl -w
use strict;
$| = 1;
for (1..4) {
my $preso;
print "premi un tasto: ";
$preso = getone();
print "--> $preso\n";
}
exit;
BEGIN {
use POSIX qw(:termios_h);
my ($term, $oterm, $echo, $noecho, $fd_stdin);
$fd_stdin = fileno(STDIN);
$term = POSIX::Termios->new();
$term->getattr($fd_stdin);
$oterm = $term->getlflag();
$echo = ECHO | ECHOK | ICANON;
$noecho = $oterm & ~$echo;
sub cbreak {
$term->setlflag($noecho);
$term->setcc(VTIME, 1);
$term->setattr($fd_stdin, TCSANOW);
}
sub cooked {
$term->setlflag($oterm);
$term->setcc(VTIME, 0);
$term->setattr($fd_stdin, TCSANOW);
}
sub getone {
my $tasto = '';
cbreak();
sysread(STDIN, $tasto, 1);
cooked();
return $tasto;
}
}
END { cooked() }
Il modulo Term::ReadKey di CPAN potrebbe risultare più semplice
da usare. Versioni recenti includono inoltre il supporto per sistemi specifici.
use Term::ReadKey;
open(TTY, "</dev/tty");
print "Premi un tasto: ";
ReadMode "raw";
$tasto = ReadKey 0, *TTY;
ReadMode "normal";
printf "\nHai premuto %s, il cui numero E<egrave> %03d\n",
$tasto, ord $tasto;
La prima cosa da fare è ottenere l'estensione Term::ReadKey da CPAN.
Come già accennato sopra, ora ha anche un limitato supporto per
sistemi non portabili (ossia: sistemi non aperti, chiusi, proprietari, non
POSIX, non Unix, etc.).
Si dovrebbe anche controllare nell'elenco delle Frequently Asked Questions
sui newsgroup comp.unix.* per cose simili: la risposta è
sostanzialmente la stessa. Dipende molto dal sistema operativo. Di
seguito si riporta una soluzione che funziona su sistemi BSD:
sub key_ready {
my($rin, $nfd);
vec($rin, fileno(STDIN), 1) = 1;
return $nfd = select($rin,undef,undef,0);
}
Per sapere quanti caratteri sono in attesa, si puE<ograve> utilizzare anche
la chiamata ioctl FIONREAD. Il programma I<h2ph> che viene distribuito con
il Perl prova a convertire i file include del C in codice Perl, che
puE<ograve> essere utilizzato con il comando C<require>. FIONREAD
E<egrave> definita come funzione nel file I<sys/ioctl.ph>:
require 'sys/ioctl.ph';
$dim = pack("L", 0);
ioctl(FH, FIONREAD(), $dim) or die "Errore in ioctl: $!\n";
$dim = unpack("L", $dim);
Se h2ph non è installato o non funziona, si può effettuare
un grep manuale sui file include:
% grep FIONREAD /usr/include/*/*
/usr/include/asm/ioctls.h:#define FIONREAD 0x541B
Oppure scrivere un piccolo programma C, utilizzando il miglior editor in
circolazione:
% cat > fionread.c
#include <sys/ioctl.h>
main() {
printf("%#08x\n", FIONREAD);
}
^D
% cc -o fionread fionread.c
% ./fionread
0x4004667f
E poi inserire direttamente il valore nel vostro programma, lasciando la
portabilità come esercizio per i vostri posteri.
$FIONREAD = 0x4004667f; # XXX: dipendente dal sistema operativo
$dim = pack("L", 0);
ioctl(FH, $FIONREAD, $dim) or die "Errore in ioctl: $!\n";
$dim = unpack("L", $dim);
FIONREAD richiede che il filehandle sia connesso ad un flusso, ossia socket,
pipe, e periferiche tty funzionano, ma semplici file no.
Per prima cosa provate
seek(GWFILE, 0, 1);
L'istruzione seek(GWFILE, 0, 1) non modifica la posizione corrente, ma
cancella la condizione di end-of-file sull'handle, in maniera tale che il
prossimo <GWFILE> farà in modo che Perl provi di nuovo a leggere.
Se ciò non funziona (si basa su caratteristiche della vostra
implementazione di stdio), allora vi serve più qualcosa del genere:
for (;;) {
for ($pos_curs = tell(GWFILE); <GWFILE>; $pos_curs = tell(GWFILE)) {
# cerca qualcosa e mettilo da parte
}
# aspetta un po'
seek(GWFILE, $curpos, 0); # torniamo nel punto in cui eravamo
}
Se ancora non funziona, guardate il modulo POSIX. POSIX definisce il metodo
learerr(), che può rimuovere la condizione di end-of-file su un
filehandle. Il metodo legge fino alla fine del file, chiama clearerr(),
legge un altro po'. Insaponare, risciacquare, ripetere.
C'è inoltre un modulo File::Tail su CPAN.
Se controllate perlfunc/open, vedrete che svariati dei modi in cui si
può chiamare open() dovrebbero andare bene. Ad esempio:
open(LOG, ">>/tmp/filedilog");
open(STDERR, ">&LOG");
O anche con un descrittore letterale numerico:
$fd = $ENV{MHCONTEXTFD};
open(MHCONTEXT, "<&=$fd"); # like fdopen(3S)
Notate che ``<&STDIN'' crea una copia, ma ``<&=STDIN'' crea un alias. Questo
significa che se chiudete un handle del quale è stato creato un
alias, tutti gli alias diventano inaccessibili. Questo non è vero
per un handle sottoposto a copia.
Il controllo degli errori, come sempre, è lasciato al lettore come
esercizio.
Dovrebbe essere raramente necessario, visto che per i file handle creati
dal Perl bisogna usare la funzione close(), anche quando sono ottenuti per
duplicazione da descrittori numerici, come MHCONTEXT visto sopra [nella faq
precedente, NdT]. Ma se proprio dovete farlo, potete fare così:
require 'sys/syscall.ph';
$rc = syscall(&SYS_close, $fd + 0); # deve forzare il numerico
die "non posso fare un sysclose di $fd: $!" unless $rc == -1;
Oppure, usate la funzionalità fdopen(3S) della funzione open():
{
local *F;
open F, "<&=$fd" or die "Non posso riaprire fd=$fd: $!";
close F;
}
Ops! Avete appena messo un carattere di tabulazione e un form-feed
[avanzamento di linea, NdT] nel nome di quel di file! Ricordate che
all'interno di stringhe delimitate da doppi apici (``come\questa'') il
backslash ('\') è un carattere di escape.
Una lista completa sta in perlop/Quote and Quote-like Operators
[Virgolette e operatori simili, NdT]. Non è sorprendente che
non abbiate un file chiamato ``c:(tab)emp(formfeed)anfaluca'' oppure
``c:(tab)emp(formfeed)anfaluca.exe'' sul vostro vecchio filesystem DOS.
Scrivete le vostre stringhe tra apici singoli, oppure (è preferibile)
usate slash ``in avanti'' / . Dal momento che tutte le versioni di DOS e
Windows a partire da MS-DOS 2.0 o simili hanno trattato / e \ allo
stesso modo nei path, dovreste usare il carattere che non crea problemi a
Perl -- o alla shell POSIX, ANSI C e C++, awk, Tcl, Java, oppure a Python,
tanto per menzionarne qualcuno. Inoltre, i path POSIX sono più
portabili.
Perché anche nelle versioni per sistemi non-Unix, la funzione glob
di Perl segue gli standard Unix per la semantica del ``globbing''. Dovete
usare glob("*") per ottenere tutti i file (non nascosti). Questo rende
glob() portabile anche sui vecchi sistemi. La versione per il vostro
sistema potrebbe includere anche funzioni proprietarie per fare il
globbing. Controllatene la documentazione per dettagli.
Questo è minuziosamente e coscienziosamente descritto nell'articolo
file-dir-perms nella collezione ``Far More Than You Ever Wanted To Know''
[Ben più di quello che avreste sempre voluto sapere, NdT] in
http://www.cpan.org/misc/olddoc/FMTEYEWTK.tgz .
Riassunto sulle cose da fare: imparate come funziona il vostro filesystem.
I permessi su un file dicono cosa può succedere ai dati in quel
file. I permessi su una directory dicono cosa può accadere alla
lista dei file in quella directory. Se cancellate un file, state rimuovendo
il suo nome dalla directory (dunque l'operazione dipende dai permessi della
directory, non del file). Se cercate di scrivere sul file, i permessi del
file sono quelli che comandano, se siete autorizzati a farlo.
Ecco un algoritmo tratto dal Camel Book:
srand;
rand($.) < 1 && ($line = $_) while <>;
Ciò ha un significativo vantaggio in termini di spazio rispetto
alla lettura in memoria dell'intero file. Potete trovare una dimostrazione di
questo metodo su The Art of Computer Programming, Volume 2, Sezione 3.4.2, di Donald E. Knuth.
Potete usare anche il modulo File::Random che fornisce una funzione per
quell'algoritmo:
use File::Random qw/random_line/;
my $linea = random_line($nomefile);
Un altro modo è quello di usare il modulo Tie::File che tratta l'intero file
come un array. Accedete semplicemente in maniera casuale ad un elemento dell'array.
Dire
print "@linee\n";
collega gli elementi di @linee con uno spazio tra di essi. Se @linee
fosse ("piccole", "soffici", "nuvole") allora l'istruzione precedente
stamperebbe
piccole soffici nuvole
ma se ogni elemento di @linee fosse una linea di testo che termina con
un carattere di nuova linea ("piccole\n", "soffici\n", "nuvole\n")
allora stamperebbe:
piccole
soffici
nuvole
Se il vostro array contiene delle linee, stampatele e basta:
print @linee;
Copyright (c) 1997-2006 Tom Christiansen, Nathan Torkington e altri autori menzionati.
Tutti i diritti riservati.
Questa documentazione è libera; potete ridistribuirla e/o modificarla
secondo gli stessi termini applicati al Perl.
Indipendentemente dalle modalità di distribuzione, tutti gli esempi di
codice in questo file sono rilasciati al pubblico dominio. Potete, e siete
incoraggiati a farlo, utilizzare il presente codice o qualunque forma
derivata da esso nei vostri programmi per divertimento o per profitto.
Un semplice commento nel codice che dia riconoscimento alle FAQ sarebbe
cortese ma non è obbligatorio.
La versione su cui si basa questa traduzione è ottenibile con:
perl -MPOD2::IT -e print_pod perlfaq5
Per maggiori informazioni sul progetto di traduzione in italiano si veda
http://pod2it.sourceforge.net/ .
Traduzione a cura di Michele Beltrame.
Revisione a cura di Marco Allegretti.
Mon Jun 11 22:02:15 2012
|