index | project | pods | responsabili

NOME

perlopentut - tutorial sull'aprir cose in Perl


DESCRIZIONE

Perl ha, integrati, due modi semplici per aprire il file: il sistema shell per la comodità, ed il sistema C per la precisione. Il sistema shell ha anche la forma a due e quella a tre argomenti, che hanno modalità differenti per quanto riguarda l'interpretazione del nome del file. A voi la scelta.


Apertura à la shell

La funzione open di Perl è stata progettata per imitare il sistema di redirezione presente nella linea di comando della shell. Ecco alcuni esempi di base:

    $ mio_programma file1 file2 file3
    $ mio_programma    <  inputfile
    $ mio_programma    >  outputfile
    $ mio_programma    >> outputfile
    $ mio_programma    |  altro_programma 
    $ altro_programma  |  mio_programma

Ed ecco alcuni esempi un po' più avanzati:

    $ altro_programma      | mio_programma f1 - f2
    $ altro_programma 2>&1 | mio_programma -
    $ mio_programma     <&3
    $ mio_programma     >&4

I programmatori abituati a costrutti del genere saranno lieti di sapere che Perl supporta direttamente queste modalità familiari utilizzando praticamente la stessa sintassi della shell.

Aperture Semplici

La funzione open accetta due argomenti: il primo è un filehandle, il secondo è una singola stringa che include sia ciò che va aperto, sia come aprirlo. open restituisce un valore vero quando va a buon fine, mentre quando fallisce restituisce un valore falso ed imposta la variabile speciale $! per riportare l'errore di sistema. Se il filehandle era già aperto verrà, prima di tutto, chiuso implicitamente.

Ad esempio:

    open(INFO,      "datafile") || die("impossibile aprire datafile: $!");
    open(INFO,   "<  datafile") || die("impossibile aprire datafile: $!");
    open(RISULT ,">  runstats") || die("impossibile aprire runstats: $!");
    open(LOG,    ">> logfile ") || die("impossibile aprire logfile:  $!");

Se preferite la versione con meno punteggiatura, potete anche scrivere tutto ciò come segue:

    open INFO,   "<  datafile"  or die "impossibile aprire datafile: $!";
    open RISULT ,">  runstats"  or die "impossibile aprire runstats: $!";
    open LOG,    ">> logfile "  or die "impossibile aprire logfile:  $!";

Vanno notate alcune cose. Prima di tutto, il segno di minore all'inizio è opzionale. Se omesso, Perl assume che volete aprire il file in lettura.

Osservate anche che nel primo esempio si utilizza l'operatore logico ||, mentre nel secondo si utilizza or, che ha una precedenza inferiore. L'utilizzo di || nel secondo caso in realtà significherebbe

    open INFO, ( "<  datafile"  || die "impossibile aprire datafile: $!" );

che non è sicuramente quel che volete.

L'altra cosa importante da notare è che, proprio come nella shell, qualsiasi spazio prima o dopo il nome del file viene ignorato. Questo è corretto, perché non vorreste comportamenti differenti dalle istruzioni che seguono:

    open INFO,   "<datafile"   
    open INFO,   "< datafile" 
    open INFO,   "<  datafile"

Ignorare gli spazi di contorno aiuta anche quando leggete il nome di un file da un altro file, e vi dimenticate di eliminare gli spazi prima di aprire:

    $nomefile = <INFO>;         # oops, \n e` rimasto
    open(EXTRA, "< $nomefile") || die "impossibile aprire $nomefile: $!";

Non si tratta di un bug, ma di una caratteristica inserita appositamente. Visto che open imita la shell nel suo stile di utilizzare le frecce di redirezione per specificare come aprire il file, lo fa anche nel modo di considerare gli spazi bianchi aggiuntivi intorno al nome del file stesso. Per accedere a file con nomi fantasiosi vi rimandiamo alla sezione Fugare il Dweomer.

Esiste anche una versione a tre parametri di open, che vi consente di inserire i caratteri speciali di redirezione in un argomento apposito:

    open( INFO, ">", $datafile ) || die "Impossibile creare $datafile: $!";

In questo caso, il nome del file da aprire è la stringa vera e propria nella variabile $datafile, per cui non dovete preoccuparvi del fatto che $datafile contenga o meno caratteri in grado di modificare la modalità di apertura, o dell'assorbimento degli spazi all'inizio del nome del file come avverrebbe nella versione a due argomenti. Inoltre, qualsiasi riduzione di interpolazioni di stringhe non necessarie è una buona cosa.

Filehandle Indiretti

Il primo argomento di open può essere un riferimento ad un filehandle. A partire da perl 5.6.0, se l'argomento non è inizializzato, Perl creerà un filehandle automaticamente e inserirà un riferimento ad esso nel primo argomento, come segue:

   open( my $in, $infile )   or die "Impossibile leggere $infile: $!";
   while ( <$in> ) {
      # fai qualcosa con $_
   }
   close $in;

I filehandle indiretti semplificano la gestione degli spazi dei nomi. Poiché i filehandle sono globali all'interno del pacchetto corrente, due subroutine che provino ad aprire INFILE si scontrerebbero l'una con l'altra. Con due funzioni che aprono filehandle indiretti come my $infile, invece, non c'è pericolo di scontro e nessun bisogno di preoccuparsi riguardo a possibili conflitti futuri.

Un altro comportamento conveniente è dato dal fatto che un filehandle indiretto viene chiuso automaticamente quando va fuori visibilità o quando lo forzate su undef:

   sub prima_riga {
      open( my $in, shift ) && return scalar <$in>;
      # non e` richiesta alcuna close()
   }

Apertura di Pipe

Nel linguaggio C, quando volete aprire un file utilizzando la libreria di I/O standard, utilizzate la funzione fopen, ma quando volete aprire una pipe utilizzate popen. Nella shell, d'altro canto, utilizzate semplicemente un carattere di redirezione differente. Questa semplificazione si ha anche in Perl. La chiamata open rimane la stessa -- cambiano solamente gli argomenti.

Se il carattere iniziale è un simbolo pipe [il carattere ``|'', N.d.T.] open fa partire un nuovo comando ed apre un filehandle in sola scrittura che sfocia in quel comando. In tal modo vi è consentito scrivere in questo handle e fare in modo che ciò che scrivete sia disponibile allo standard input del comando. Ad esempio:

    open(STAMPANTE, "| lpr -Plp1")    || die "impossibile lanciare lpr: $!";
    print STAMPANTE "bla bla bla\n";
    close(STAMPANTE)                  || die "impossibile chiudere lpr: $!";

Se il carattere finale è un simbolo pipe, viene lanciato un nuovo comando ed aperto un filehandle in sola lettura che legge dall'uscita di tale comando. Questo vi consente di accedere in lettura, attraverso questo handle, a tutto ciò che il comando scrive sul proprio standard output. Ad esempio:

    open(RETE, "netstat -i -n |")    || die "impossibile lanciare netstat: $!";
    while (<RETE>) { }               # fai qualcosa con l'input
    close(RETE)                      || die "impossibile chiudere netstat: $!";

Cosa succede se provate ad aprire una pipe da o verso un comando che non esiste? Se possibile, Perl intercetterà il fallimneto ed imposterà $! come di consueto. Ma se il comando contiene caratteri speciali per la shell, come > o *, noti anche come 'metacaratteri', Perl non eseguirà il comando direttamente. Piuttosto, Perl lancia la shell, che a sua volta prova a lanciare il comando. Ciò implica che è la shell che raccoglie l'indicazione di errore. In tal caso, la chiamata a open indicherà un fallimento solamente se Perl non riesce nemmeno a lanciare la shell. Consultate perlfaq8/``Come posso catturare STDERR da un comando esterno?'' per trovare una possibile soluzione a questo problema. Potete anche trovare una spiegazione in perlipc.

Se volete aprire una pipe bidirezionale, la libreria IPC::Open2 vi sarà di aiuto. Consultate perlipc/``Comunicazione Bidirezionale con un Altro Processo''.

Il File ``Meno''

Nuovamente, seguendo il corso delle utilità standard disponibili nella shell, la funzione open di Perl tratta un file il cui nome è un semplice ``-'' in modo particolare. Se aprite ``meno'' in lettura, in realtà significa che accederete allo standard input. Se lo aprite in scrittura, in realtà significa che accederete allo standard output.

Se ``meno'' può essere usato come l'input o l'output di default, cosa succede se aprite una pipe verso o da ``meno''? Qual è il comando di default che verrà lanciato? Beh, è lo stesso script che state eseguendo! In realtà c'è una chiamata a fork ben nascosta dietro la chiamata alla stessa open. Consultate perlipc/``Aperture di Pipe Sicure'' per i dettagli.

Mischiare Letture e Scritture

È possibile specificare l'accesso sia in lettura che in scrittura. Tutto ciò che dovete fare è mettere un simbolo ``+'' immediatamente prima del carattere di redirezione. Ma, come nella shell, utilizzare un ``minore'' su un file non ne crea mai uno nuovo: ne apre solo uno che già esiste. D'altro canto, utilizzare un ``maggiore di'' vi sporcherà sempre qualsiasi file esistente (poiché lo tronca a lunghezza zero), o ne crea uno nuovo di zecca se non ne esiste già uno. Aggiungendo un ``+'' per rendere la redirezione sia di lettura che di scrittura, non altera tali proprietà: funzionerà solo su file esistenti oppure vi sporcherà sempre il file esistente.

    open(WTMP, "+< /usr/adm/wtmp") 
        || die "impossibile aprire /usr/adm/wtmp: $!";
    open(SCREEN, "+> lkscreen")
        || die "impossibile aprire lkscreen: $!";
    open(LOGFILE, "+>> /var/log/applog"
        || die "impossibile aprire /var/log/applog: $!";

Il primo esempio non creerà un nuovo file, mentre il secondo esempio vi sporcherà sempre il file esistente. Il terzo esempio creerà un nuovo file se necessario, e non sporcherà un eventuale file preesistente, dandovi la possibilità di leggere da qualsiasi punto nel file, laddove però potrete solo scrivere aggiungendo dati alla fine del file. In breve, il primo caso è sostanzialmente più comune degli altri due, che si rivelano sbagliati nella maggior parte dei casi. (Se conoscete il linguaggio C, il ``più'' nella open di Perl è storicamente derivato da quello nella funzione C fopen(3S), che viene chiamata dietro le quinte).

Di fatto, quando occorre aggiornare un file, a meno che non stiate lavorando su un file binario come nel caso di WTMP esposto in precedenza, probabilmente non vorrete utilizzare questo approccio per effettuare gli aggiornamenti. Piuttosto, il parametro Perl a linea di comando -i viene a darvi una mano. Il comando che segue prende tutti i file sorgente o di intestazione C, C++ o yacc, e cambia tutti i ``pippo'' in ``pluto'', lasciando la vecchia versione non modificata con il nome originale ed un'estensione ``.orig'' agganciata alla fine:

    $ perl -i.orig -pe 's/\bpippo\b/pluto/g' *.[Cchy]

Questa rappresenta una scorciatoia per effettuare alcuni giochetti di ridenominazione, che sono in realtà il modo migliore per aggiornare i file di testo. Consultate anche la seconda domanda in perlfaq5 per maggiori dettagli.

Filtri

Uno degli usi più comuni per open è uno del quale non vi accorgete nemmeno. Quando accedete il filehandle <ARGV>, in realtà Perl effettua una chiamata ad open implicita a ciascun file in @ARGV. Per questo motivo, un programma chiamato come segue:

    $ mioprogramma file1 file2 file3

può ottenere tutti i file elencati aperti ed elaborati, uno alla volta, utilizzando un costrutto semplice come:

    while (<>) {
        # fai qualcosa con $_
    }

Se @ARGV è vuota quando il ciclo comincia all'inizio, Perl finge che abbiate voluto aprire ``meno'', ossia l'input standard. $ARGV, che rappresenta il file attualmente aperto durante l'elaborazione di <ARGV>, è persino impostato a ``-'' in queste circostante.

Potete tranquillamente pre-elaborare l'array @ARGV prima di entrare nel ciclo, per essere sicuri che sia di vostro gradimento. Una ragione per farlo potrebbe essere quella di rimuovere le opzioni a riga di comando che iniziano con un carattere ``meno''. Se da una parte potete sempre gestire le situazioni semplici ``a mano'', i moduli Getopts sono bravi proprio a far questo:

    use Getopt::Std;
    # -v, -D, -o ARG, imposta $opt_v, $opt_D, $opt_o
    getopts("vDo:");
    # -v, -D, -o ARG, imposta $args{v}, $args{D}, $args{o}
    getopts("vDo:", \%args);

Oppure il modulo standard Getopt::Long per consentirvi di utilizzare argomenti con nome:

    use Getopt::Long;
    GetOptions( "verboso"  => \$verboso,        # --verboso
                "Debug"    => \$debug,          # --Debug
                "output=s" => \$output );       
            # --output=qualcosa oppure --output qualcosa

Un'altra ragione per effettuare la pre-elaborazione degli argomenti può essere quello di impostare per default la lista di tutti i file, nel caso la lista degli argomenti sia vuota:

    @ARGV = glob("*") unless @ARGV;

Potete persino filtrare via tutti quei file che non sono semplici file di testo. Il tutto è piuttosto silenzioso, ovviamente, e potreste preferire di menzionare esplicitamente all'utilizzatore quando effettuate operazioni di esclusione come questa:

    @ARGV = grep { -f && -T } @ARGV;

Se state utilizzando le opzioni a riga di comando -n o -p, dovreste confinare i cambiamenti a @ARGV in un blocco BEGIN{}.

Ricordate che una open ha proprietà speciali, nel senso che potrebbe chiamare fopen(3S) oppure popen(3S), a seconda di quali sono i suoi parametri; questo è il motivo per cui è detta, a volte, ``open magica''. Ecco un esempio:

    $pwdinfo = `domainname` =~ /^(\(none\))?$/
                    ? '< /etc/passwd'
                    : 'ypcat passwd |';
    open(PWD, $pwdinfo)                 
                or die "impossibile aprire $pwdinfo: $!";

Questo genere di cose entra in gioco nell'elaborazione di filtraggio. Poiché l'elaborazione di <ARGV> impiega la normale open in stile shell di Perl, rispetta anche tutti i casi particolari che abbiamo già visto:

    $ mioprogramma f1 "cmd1|" - f2 "cmd2|" f3 < tmpfile

Questo programma leggerà dal file f1, dal processo cmd1, da standard input (ossia, tmpfile in questo caso), dal file f2, dal comando cmd2 ed infine dal file f3.

Sì, tutto ciò significa anche che se nella vostra directory avete file chiamati ``-'' (e così via), questi non saranno elaborati da open come nomi di file letterali. Avrete bisogno di passarli come ``./-'', proprio come dovreste fare per il programma rm, o potreste in alternativa utilizzare la funzione sysopen descritta più avanti.

Una delle applicazioni più interessanti consiste nel cambiare in pipe i file con un certo nome. Ad esempio, per auto-elaborare file compressi con gzip o compress utilizzando gzip:

    @ARGV = map { /^\.(gz|Z)$/ ? "gzip -dc $_ |" : $_  } @ARGV;

O anche, se avete il programma GET installato dal modulo LWP, potete scaricare dalle URL prima di elaborarle:

    @ARGV = map { m#^\w+://# ? "GET $_ |" : $_ } @ARGV;

Non a caso viene chiamata <ARGV> magica. Carino, eh?


Open à la C

Se volete la convenienza della shell, allora la funzione open di Perl è sicuramente la strada giusta da prendere. D'altra parte, se volete una precisione più fine rispetto a quanto vi fornisce la semplice fopen(3S), dovreste rivolgervi alla funzione sysopen, che è un aggancio diretto alla chiamata di sistema open(2). Ciò significa che è un tantino più involuta, ma questo è il prezzo della precisione.

sysopen accetta 3 o 4 argomenti.

    sysopen HANDLE, PERCORSO, OPZIONI, [MASCHERA]

Il parametro HANDLE è un filehandle proprio come per open. il PERCORSO è un percorso letterale, ossia nel quale non si presta attenzione alla presenza di caratteri ``maggiore'', ``minore'' o ``pipe'' o ``meno'', né si ignorano gli spazi. Se ci sta qualcosa, fa parte del percorso. L'argomento OPZIONI contiene uno o più valori derivati dal modulo Fcntl, che sono stati fusi insieme con un'operazione di or binario attraverso l'operatore ``|''. L'argomento finale, MASCHERA, è opzionale; se presente, viene combinato con la umask corrente dell'utente per stabilire i bit di modo nella creazione del file. Di solito dovreste omettere questo parametro.

Sebbene i valori tradizionali di sola-lettura, sola-scrittura e lettura-scrittura siano, rispettivamente, 0, 1 e 2, ciò non è vero su tutti i sistemi. È meglio, piuttosto, caricare le costanti giuste dal modulo Fcntl, che fornisce le seguenti opzioni standard:

    O_RDONLY            Sola lettura
    O_WRONLY            Sola scrittura
    O_RDWR              Lettura e scrittura
    O_CREAT             Crea il file se non esiste
    O_EXCL              Fallisce se il file esiste
    O_APPEND            Aggiunge al file
    O_TRUNC             Tronca il file
    O_NONBLOCK          Accesso non bloccante

Alcune opzioni meno comuni che sono talvolta disponibili in alcuni sistemi operativi includono O_BINARY, O_TEXT, O_SHLOCK, O_EXLOCK, O_DEFER, O_SYNC, O_ASYNC, O_DSYNC, O_RSYNC, O_NOCTTY, O_NDELAY e O_LARGEFILE. Consultate la pagina di manuale di open(2) o il suo equivalente locale per i dettagli. (Nota: a partire dalla versione 5.6 l'opzione O_LARGEFILE, se disponibile, è automaticamente aggiunta alle opzioni passate a sysopen() perché i file grandi sono utilizzati di default).

Ecco un esempio di come utilizzare sysopen per emulare le semplici chiamate open che abbiamo introdotto in precedenza. Ometteremo i controlli || die $! per maggiore chiarezza, ma assicuratevi di controllare sempre i valori restituiti nel codice reale. Essi non sono esattamente gli stessi, poiché open eliminerà gli spazi all'inizio ed alla fine, ma potrete farvi un'idea.

Per aprire un file in lettura:

    open(FH, "< $percorso");
    sysopen(FH, $percorso, O_RDONLY);

Per aprire un file in scrittura, creando un file nuovo se necessario o altrimenti troncando un file pre-esistente:

    open(FH, "> $percorso");
    sysopen(FH, $percorso, O_WRONLY | O_TRUNC | O_CREAT);

Per aprire un file per aggiungere alla fine, creandolo se necessario:

    open(FH, ">> $percorso");
    sysopen(FH, $percorso, O_WRONLY | O_APPEND | O_CREAT);

Per aprire un file per aggiornamento, laddove il file deve necessariamente esistere:

    open(FH, "+< $percorso");
    sysopen(FH, $percorso, O_RDWR);

Quelle che seguono, invece, sono le cose che potete fare con sysopen ma che non potete fare con una open regolare. Come avrete modo di vedere, è solo una questione di controllare quali opzioni utilizzare nel terzo parametro.

Per aprire un file in scrittura, creando un nuovo file che non deve esistere precedentemente:

    sysopen(FH, $percorso, O_WRONLY | O_EXCL | O_CREAT);

Per aprire un file in aggiunta, laddove il file deve già esistere:

    sysopen(FH, $percorso, O_WRONLY | O_APPEND);

Per aprire un file per aggiornamento, creando un nuovo file se necessario:

    sysopen(FH, $percorso, O_RDWR | O_CREAT);

Per aprire un file per aggiornamento, laddove il file non deve già esistere:

    sysopen(FH, $percorso, O_RDWR | O_EXCL | O_CREAT);

Per aprire un file in modalità non bloccante, creandone uno se necessario:

    sysopen(FH, $percorso, O_WRONLY | O_NONBLOCK | O_CREAT);

Permessi à la mode

Se omettete l'argomento MASCHERA nella chiamata a sysopen, Perl utilizza il valore ottale 0666. La normale MASCHERA da utilizzare per gli eseguibili e le directory dovrebbe essere 0777, e 0666 per tutto il resto.

Perché così permissivo? Beh, in realtà non è proprio così. La MASCHERA verrà modificata dalla umask del processo corrente. Una umask è un numero che rappresenta i bit di permesso disabilitati, ossia quei bit che non saranno impostati nel campo dei permessi dei file che vengono creati.

Ad esempio, se la vostra umask fosse 027, allora la parte 020 disabiliterebbe la scrittura per il gruppo, e la parte 007 disabiliterebbe lettura, scrittura ed esecuzione per gli ``altri''. In queste condizioni, passando 0666 a sysopen in realtà creerebbe un file con modalità 0640, poiché 0666 & ~027 è appunto 0640.

Dovreste aver bisogno di usare l'argomento MASCHERA piuttosto di rado. In questo modo si preclude all'utente la libertà di decidere quali permessi debbano avere i nuovi file. Negare la scelta è quasi sempre una cosa sbagliata. Un'eccezione potrebbe esserci per casi nei quali vengono immagazzinati dati sensibili o privati, come nel caso di cartelle di email, file cookie e file temporanei ad uso interno.


Oscuri Trucchi di Apertura

Ri-Aprire File (duplicati)

A volte avete già un filehandle aperto, e volete un altro handle che ne sia un duplicato. Nella shell, mettiamo un carattere ``&'' immediatamente prima del numero del descrittore di file in fase di redirezione. Ad esempio, 2>&1 fa sì che il descrittore numero 2 (corrispondente a STDERR in Perl) sia rediretto nel descrittore numero 1 (che di solito corrisponde a STDOUT in Perl). Tutto ciò rimane sostanzialmente invariato in Perl: un nome di file che inizi con un carattere ``&'' è trattato alla stregua di un descrittore di file se si tratta di un numero, o come un filehandle se si tratta di una stringa.

    open(SALVAOUT, ">&SALVAERR") || die "impossibile duplicare SALVAERR: $!";
    open(MHCONTEXT, "<&4")     || die "impossibile duplicare fd4: $!";

Questo significa che se una funzione si aspetta di ricevere un nome di file, ma non volete fornirlo perché il file in questione è già aperto, potete passare semplicemente il filehandle precedendolo con un carattere ``&''. In ogni caso, è meglio utilizzare un handle pienamente qualificato, per tenere in conto il caso che la funzione si trovi in un package differente:

    una_qualche_funzione("&main::LOGFILE");

In questo modo, se una_qualche_funzione() vuole aprire il parametro passato, può riutilizzare l'handle già aperto. Questo approccio è differente dal passare semplicemente un handle, perché con questo non potete aprire file. Qui avete un meccanismo che vi permette di passare qualcosa di accettabile per open().

Se avete uno di quegli oggetti I/O strani ed avveniristici di cui la gente di C++ blatera tanto, questo approccio non funziona perché non sono dei filehandle nativi veri e propri. In questo caso dovete utilizzare fileno() e passare il numero di descrittore di file, sempre che sia possibile:

    use IO::Socket;
    $handle = IO::Socket::INET->new("www.perl.com:80");
    $fd = $handle->fileno;
    una_qualche_funzione("&$fd");  # non una chiamata indiretta di funzione

Può risultare più semplice (e di sicuro più veloce) usare semplicemente filehandle reali:

    use IO::Socket;
    local *REMOTO = IO::Socket::INET->new("www.perl.com:80");
    die "impossibile connettersi" unless defined(fileno(REMOTO));
    una_qualche_funzione("&main::REMOTO");

Se il filehandle o il numero di descrittore sono preceduti da una combinazione ``&='' piuttosto che da un semplice ``&'', Perl non creerà un descrittore aperto completamente nuovo utilizzando la chiamata di sistema dup(2): genererà piuttosto un alias a quello esistente utilizzando la chiamata di libreria fdopen(3S). Questo approccio è leggermente meno oneroso in termini di risorse di sistema, sebbene al giorno d'oggi questo non sia un grosso problema. Ecco un esempio:

    $fd = $ENV{"MHCONTEXTFD"};
    open(MHCONTEXT, "<&=$fd")   or die "errore di fdopen $fd: $!";

Se state utilizzando l'input magico <ARGV>, potete persino passare come parametro a linea di comando in @ARGV qualcosa come "<&=$MHCONTEXTFD", ma non abbiamo mai visto nessuno farlo.

Fugare il Dweomer

Perl è un linguaggio più DWIMmico di altri tipo Java -- laddove DWIM è un acronimo per ``do what I mean'' [letteralmente ``fa quello che intendo'', ossia cerca di capire le mie reali intenzioni da solo, N.d.T.]. Ma questo principio a volte porta ad un quantitativo di magia nascosta superiore a quella che uno sa di poter utilizzare. In questo senso, Perl è anche i<dweomer>, una parola arcana che indica un sortilegio [Wikipedia: oggetto magico o magia prodotta dallo stesso, N.d.T.]. A volte, la DWIMmicità di Perl assomiglia un po' troppo ad un dweomer perché ci si possa sentire a posto.

Se la open magica è un po' troppo magica per voi, non siete per forza costretti ad utilizzare sysopen. Per aprire un file con caratteri arbitrariamente strani è necessario proteggere qualunque spazio all'inizio ed alla fine. Gli spazi iniziali si proteggono inserendo un "./" prima di un nome di file che inizia con uno spazio. Gli spazi in fondo si proteggono aggiungendo un byte ASCII NUL ("\0") alla fine della stringa.

    $file =~ s#^(\s)#./$1#;
    open(FH, "< $file\0")   || die "impossibile aprire $file: $!";

Si assume, chiaramente, che il vostro sistema consideri ``.'' la directory di lavoro corrente, ``/'' il separatore delle directory, e che non ammetta degli ASCII NUL come carattere valido in un nome di file. La maggior parte dei sistemi si adattano a questa convenzione, inclusi tutti i sistemi POSIX così come i sistemi proprietari Microsoft. L'unico sistema vagamente popolare che non funziona in questo modo è il sistema Macintosh ``Classic'', che utilizza ``:'' laddove il resto delle persone utilizza ``/''. Forse sysopen non è proprio un'idea malvagia.

Se volete utilizzare l'elaborazione di <ARGV> in una maniera completamente noiosa e non magica, potete fare così:

    #   "Sam si sedette a terra e si prese la testa fra le mani.
    #   'Vorrei non essere mai venuto qui, e non voglio vedere
    #   altra magia', disse, e sprofondo` nel silenzio."
    for (@ARGV) { 
        s#^([^./])#./$1#;
        $_ .= "\0";
    } 
    while (<>) {  
        # ora si elabora $_
    }

Ma attenzione che gli utenti non apprezzeranno il fatto di non poter utilizzare ``-'' per intendere l'input standard, per una convenzione standard.

Percorsi come Aperture

Avrete probabilmente notato che le funzioni warn e die possono produrre messaggi come:

    Un qualche warning at scriptname line 29, <FH> line 7.

Questo è possibile perché avete aperto un filehandle FH, e ci avete letto sette record. Ma qual era il nome del file, invece che l'handle?

Se non state lavorando con strict refs attivi, o se li avete disabilitati momentaneamente, allora non dovete far altro che questo:

    open($path, "< $path") || die "impossibile aprire $path: $!";
    while (<$path>) {
        # quello che serve
    }

Visto che state utilizzando il percorso al file come handle, otterrete dei messaggi più simili a

    Un qualche warning at scriptname line 29, </etc/motd> line 7.

Apertura a Parametro Singolo

Vi ricordate quando abbiamo detto che la open di Perl accetta due parametri? Si trattava di un abuso. Vedete, in realtà può accettare anche un parametro solamente. Se e solo se la variabile è una variabile globale, e non lessicale, potete passare a open un solo parametro, il filehandle, e questo avrà lo stesso percorso della variabile scalare globale che ha lo stesso nome.

    $FILE = "/etc/motd";
    open FILE or die "impossibile aprire $FILE: $!";
    while (<FILE>) {
        # quello che serve
    }

Perché tutto questo? Qualcuno deve fare i conti con le consuetudini. È qualcosa che è stato presente in Perl dall'inizio, se non prima.

Giocare con STDIN e STDOUT

Una mossa intelligente da fare con STDOUT è chiuderlo esplicitamente quando il programma termina.

    END { close(STDOUT) || die "impossibile chiudere stdout: $!" }

Se non lo fate, ed il vostro programma riempie la partizione del disco per una redirezione della riga di comando, non vi segnalerà l'errore di uscita con uno stato di errore.

Non siete obbligati a piegarvi agli STDIN e STDOUT che vi vengono dati. Siete liberi di riaprirli a vostro piacimento.

    open(STDIN, "< datafile")
        || die "impossibile aprire datafile: $!";
    open(STDOUT, "> output")
        || die "impossibile aprire output: $!";

In seguito, essi verranno utilizzati direttamente o passati ai sottoprocessi. È come se il programma fosse stato chiamato con queste redirezioni direttamente dalla linea di comando.

Probabilmente è più interessante per connettersi alle pipe. Ad esempio:

    $paginatore = $ENV{PAGER} || "(less || more)";
    open(STDOUT, "| $paginatore")
        || die "impossibile invocare un paginatore: $!";

Ciò fa sì che sembri che il vostro programma sia stato chiamato con il suo output standard rediretto già sul vostro programma di paginazione. Potete anche utilizzare questo approccio in congiunzione con una fork esplicita. Potreste volerlo fare se preferite gestire l'elaborazione successiva nel vostro stesso programma, ma in un processo differente:

    prime_righe(100);
    while (<>) {
        print;
    }
    sub prime_righe {
        my $righe = shift || 20;
        return if $pid = open(STDOUT, "|-");       # esci se processo padre
        die "errore in fork: $!" unless defined $pid;
        while (<STDIN>) {
            last if --$lines < 0;
            print;
        } 
        exit;
    }

Questa tecnica può essere applicata per inserire quanti filtri volete nel vostro stream di uscita.


Altre Questioni di I/O

Gli argomenti che seguono non riguardano esattamente open o sysopen, ma influiscono sul modo in cui aprite i file.

Aprire File che Non-Sono-File

Quand'è che un file non è un file? Beh, potreste dire quando esiste ma non è un file puro e semplice. Controlliamo se è un collegamento simbolico prima di tutto, tanto per sicurezza.

    if (-l $file || ! -f _) {
        print "$file non un file semplice\n";
    }

Quali altri tipi di file ci sono oltre, ehm, i file? Le directory, i collegamenti simbolici, pipe con nome, socket Unix e dispositivi a blocchi ed a caratteri. Questi sono tutti file -- solo che non sono file semplici. Non è lo stesso problema del fatto che sia un file di testo o meno. Non tutti i file di solo testo sono file semplici. Come non tutti i file semplici sono file di solo testo. Ecco perché ci sono due test -f e -T distinti.

Per aprire una directory, dovreste utilizzare la funzione opendir, elaborandola con readdir, avendo cura di inserire il nome della directory se necessario:

    opendir(DIR, $nomedir) or die "errore di opendir su $nomedir: $!";
    while (defined($file = readdir(DIR))) {
        # utilizza "$nomedir/$file"
    }
    closedir(DIR);

Se volete accedere ricorsivamente alle directory, è meglio utilizzare il modulo File::Find. L'esempio che segue stampa ricorsivamente tutti i file ed aggiunge un carattere ``/'' se il file è una directory.

    @ARGV = qw(.) unless @ARGV;
    use File::Find;
    find sub { print $File::Find::name, -d && '/', "\n" }, @ARGV;

Quest'altro trova tutti i collegamenti simbolici fasulli nell'albero, al di sotto di una determinata directory:

    find sub { print "$File::Find::name\n" if -l && !-e }, $dir;

Come potete vedere, con i collegamenti simbolici potete semplicemente far finta che si tratti del file a cui stanno puntando. O, se volete sapere a cosa sta puntando un collegamento, allora entra in gioco readlink:

    if (-l $file) {
        if (defined($dove = readlink($file))) {
            print "$file punta a $dove\n";
        } else {
            print "$file non punta da nessuna parte: $!\n";
        } 
    }

Aprire Pipe con Nome

Le pipe con nome sono un'altra faccenda. Basta far finta che si tratti di file regolari, ricordando però che le aperture sono normalmente bloccanti finché non ci siano sia un lettore che uno scrittore. Potete leggere di più sull'argomento in perlipc/``Pipe con nome''. I socket Unix sono delle bestie piuttosto differenti; sono descritti in perlipc/``Client e Server TCP nel Dominio Unix''.

Quando si tratta di aprire dispositivi, può essere sia facile che complicato. Assumeremo che se state aprendo un dispositivo a blocchi sappiate quello che state facendo. I dispositivi a caratteri sono più interessanti. Tipicamente sono utilizzati per i modem, i mouse e qualche tipo di stampante. Il tutto è descritto in perlfaq8/``Come leggo e scrivo su una porta seriale?''. Spesso è sufficiente aprirli con una certa cautela:

    sysopen(TTYIN, "/dev/ttyS1", O_RDWR | O_NDELAY | O_NOCTTY)
                # (O_NOCTTY non e` piu` necessario nei sistemi POSIX)
        or die "errore open /dev/ttyS1: $!";
    open(TTYOUT, "+>&TTYIN")
        or die "errore dup TTYIN: $!";
    $ofh = select(TTYOUT); $| = 1; select($ofh);
    print TTYOUT "+++at\015";
    $risposta = <TTYIN>;

Riguardo ai descrittori che non avete aperto con sysopen, come nel caso dei socket, potete impostarli come non bloccanti utilizzando fcntl:

    use Fcntl;
    my $vecchi_indicatori = fcntl($handle, F_GETFL, 0) 
        or die "impossibile ottenere la configurazione attuale: $!";
    fcntl($handle, F_SETFL, $vecchi_indicatori | O_NONBLOCK) 
        or die "impossibile impostare come non bloccante: $!";

Piuttosto che perdervi in una palude di ioctl rotanti, tutte diverse fra loro, se dovete manipolare tty è meglio fare chiamate al programma esterno stty(1), se l'avete, o altrimenti utilizzare l'interfaccia POSIX portabile. Per farvi un'idea precisa, avrete bisogno di leggere la pagina di manuale di termios(3), che descrive l'interfaccia POSIX per i dispositivi tty, e successivamente POSIX, che descrive l'interfaccia Perl per POSIX. Esistono anche alcuni moduli di alto livello su CPAN per aiutavi con questi giochini. Date un'occhiata a Term::Readkey e a Term::Readline.

Aprire Socket

Che altro potete aprire? Per aprire una connessione utilizzando i socket, non potete utilizzare una delle due funzioni open. Consultate perlipc/``Socket: Comunicazione Client/Server'' per capire come fare. Ecco comunque un esempio; una volta aperto, potete utilizzare FH come filehandle bidirezionale.

    use IO::Socket;
    local *FH = IO::Socket::INET->new("www.perl.com:80");

Per aprire una URL, il dottore ordina i moduli LWP presenti su CPAN. Non c'è alcuna interfaccia tipo filehandle, ma è comunque facile ottenere il contenuto di un documento:

    use LWP::Simple;
    $doc = get('http://www.linpro.no/lwp/');

File Binari

Su certi sistemi più obsoleti con modelli di I/O che si potrebbero pietosamente chiamare convoluti in fase terminale (altri direbbero rotti), un file non è un file -- almeno per quanto riguarda la libreria I/O standard di C. Su questi vecchi sistemi le cui librerie (ma non i kernel) fanno distinzione fra stream di testo e stream binari, per far sì che i file si comportino a modo, avrebbe bisogno di piegarvi al passato per evitare fastidiosi problemi. In tali sistemi infelici, i socket e le pipe sono già aperti in modalità binaria, e non c'è al momento alcun modo per evitarlo. Con i file, avete più opzioni.

Un'altra opzione consiste nell'utilizzare la funzione binmode sui filehandle appropriati prima di effettuarci le regolari operazioni di I/O:

    binmode(STDIN);
    binmode(STDOUT);
    while (<STDIN>) { print }

Si può anche passare un'opzione non standard a sysopen, che aprirà il file in modalità binaria nei sistemi che lo supportano. Questo equivale ad aprire il file normalmente, per poi chiamare binmode sull'handle.

    sysopen(BINDAT, "record.dati", O_RDWR | O_BINARY)
        || die "impossible aprire record.dati: $!";

Ora potete utilizzare read e print sull'handle senza dovervi preoccupare se librerie non standard di sistema corromperanno i vostri dati o meno. Non è quello che si dice un bel quadro, ma si può dire raramente quando si parla di sistemi obsoleti. CP/M ci accompagnerà fino alla fine dei nostri giorni, ed oltre.

Su sistemi con sistemi di I/O esotici, si dà il caso che, abbastanza stupefacentemente, persino l'I/O non bufferizzato utilizzando sysread e syswrite può portare a qualche insidiosa mutilazione di dati alle vostre spalle.

    while (sysread(WHENCE, $buf, 1024)) {
        syswrite(WHITHER, $buf, length($buf));
    }

Dipendentemente dalle vicissitudini del vostro sistema a runtime, persino queste chiamate potrebbero aver bisogno di binmode o O_BINARY. Una lista di sistemi noti per essere liberi da queste difficoltà include Unix, Mac OS, Plan 9 ed Inferno.

Blocco di un File

In un ambiente multitasking [ossia, in grado di eseguire più compiti alla volta, N.d.T.] potreste aver bisogno di fare attenzione a non scontrarvi con altri processi che vogliono effettuare I/O sugli stessi file su cui state lavorando. Avrete spesso bisogno di lock [blocchi, nel senso del verbo bloccare, N.d.T.] condivisi o esclusivi sui file per leggere e scrivere, rispettivamente. Potreste anche far finta che esistano solo lock esclusivi.

Non utilizzate mai il test di esistenza di un file -e $file come indicazione di bloccaggio, perché esiste una ``race condition'' [``corsa critica'' o ``sezione critica'', N.d.T.] tra il momento del test di esistenza del file e la sua creazione. È possibile che un altro processo crei un file in quel breve intervallo fra il vostro test di esistenza ed il vostro tentativo di creare il file. L'atomicità è critica.

L'interfaccia di blocco più portabile in Perl è attraverso la funzione flock, la cui semplicità viene emulata sui sistemi che non la supportano direttamente come SysV e Windows. La semantica sottostante può influire su come funziona il tutto, quindi è opportuno che impariate come sia stata implementata flock nel porting di Perl sul vostro sistema.

Il lock di un file non impedisce ad un altro processo di fare I/O. Un lock di un file semplicemente blocca altri nel tentativo di ottenere un lock stesso, non i processi che provano a fare I/O. Poiché il rispetto di questi lock non è tassativo, se un processo utilizza questo meccanismo ed un altro no, tutti gli schemi saltano.

Per default, la chiamata a flock bloccherà il processo finché non viene assegnato il lock. Una richiesta di un lock condiviso sarà assegnata appena non ci siano più lock esclusivi. Una richiesta di lock esclusivo sarà assegnata solo quando non c'è più un lock di alcun tipo. I lock vengono posti sui descrittori di file, non sui nomi dei file. Non potete bloccare un file finché non lo aprite, e non potete tenere un lock su un file una volta chiuso.

Ecco come ottenere un lock condiviso bloccante su un file, tipicamente utilizzato per la lettura:

    use 5.004;
    use Fcntl qw(:DEFAULT :flock);
    open(FH, "< nomefile")  or die "impossibile aprire nomefile: $!";
    flock(FH, LOCK_SH)      or die "impossibile bloccare nomefile: $!";
    # ora leggi da FH

Potete ottenere un lock non bloccante utilizzando LOCK_NB.

    flock(FH, LOCK_SH | LOCK_NB)
        or die "impossibile bloccare nomefile: $!";

Ciò può essere utile per ottenere un comportamento più gentile con l'utente avvisando che state per bloccare:

    use 5.004;
    use Fcntl qw(:DEFAULT :flock);
    open(FH, "< nomefile")  or die "impossibile aprire nomefile: $!";
    unless (flock(FH, LOCK_SH | LOCK_NB)) {
        $| = 1;
        print "Aspetto il lock...";
        flock(FH, LOCK_SH)  or die "impossibile bloccare nomefile: $!";
        print "preso.\n"
    } 
    # ora leggi da FH

Per ottenere un lock esclusivo, tipicamente utilizzato in scrittura, dovete prestare attenzione. Effettuiamo una sysopen del file, in modo che possa essere bloccato prima che sia svuotato. Potete ottenere la versione non bloccante utilizzando LOCK_EX | LOCK_NB.

    use 5.004;
    use Fcntl qw(:DEFAULT :flock);
    sysopen(FH, "nomefile", O_WRONLY | O_CREAT)
        or die "impossibile aprire nomefile: $!";
    flock(FH, LOCK_EX)
        or die "impossibile bloccare nomefile: $!";
    truncate(FH, 0)
        or die "impossibile troncare nomefile: $!";
    # ora scrivi su FH

Infine, per colpa di quei milioni che non possono essere distolti dal perdere cicli CPU su quei dispositivi di inutile vanità chiamati contatori di richieste di pagine web, ecco come incrementare un numero in un file in maniera sicura:

    use Fcntl qw(:DEFAULT :flock);
    sysopen(FH, "numfile", O_RDWR | O_CREAT)
        or die "impossibile aprire numfile: $!";
    # autoflush FH
    $ofh = select(FH); $| = 1; select ($ofh);
    flock(FH, LOCK_EX)
        or die "impossibile bloccare numfile per la scrittura: $!";
    $num = <FH> || 0;
    seek(FH, 0, 0)
        or die "impossibile tornare all'inizio di numfile : $!";
    print FH $num+1, "\n"
        or die "impossibile scrivere numfile: $!";
    truncate(FH, tell(FH))
        or die "errore di truncate su numfile: $!";
    close(FH)
        or die "impossibile chiudere numfile: $!";

Strati IO

In Perl 5.8.0 è stato introdotto un nuovo ambiente di I/O chiamato ``PerlIO''. Si tratta di un nuovo ``sistema idrico'' per la gestione di tutto l'I/O in Perl; nella maggior parte dei casi funzionerà tutto come prima, ma PerlIO introduce anche nuove caratteristiche come la possibilità di pensare l'I/O come una serie di ``strati''. Uno strato I/O può, oltre al semplice spostamento dei dati, effettuarvi delle trasformazioni. Queste trasformazioni includono compressione e decompressione, crittazione e decrittazione e la trasformazione fra le varie codifiche dei caratteri.

Una discussione approfondita sulle caratteristiche di PerlIO è fuori dagli obiettivi di questo tutorial, ma ecco come riconoscere gli strati in uso:

  • Viene utilizzata a forma di open a tre (o più) parametri ed il secondo argomento contiene qualcosa in più rispetto ai soliti caratteri '<', '>', '>>', '|' e relative varianti, ad esempio:
        open(my $fh, "<:utf8", $fn);

  • Viene utilizzata la forma a due parametri di binmode, ad esempio
        binmode($fh, ":encoding(utf16)");

Per una discussione più dettagliata su PerlIO consultate PerlIO; per una discussione più dettagliata su Unicode e I/O consultate perluniintro.


CONSULTATE ANCHE

Le funzioni open e sysopen in perlfunc(1); le pagine di manuale per le funzioni di sistema open(2), dup(2), fopen(3) e fdopen(3); la documentazione POSIX.


AUTORE e COPYRIGHT

Copyright 1998 Tom Christiansen.

This documentation is free; you can redistribute it and/or modify it under the same terms as Perl itself.

Irrespective of its distribution, all code examples in these files are hereby placed into the public domain. You are permitted and encouraged to use this code in your own programs for fun or for profit as you see fit. A simple comment in the code giving credit would be courteous but is not required.

[Le note di copyright sono usualmente valide solo nella lingua originale, viste le implicazioni legali. Il testo corrisponde grosso modo a quanto segue:

 Questa documentazione e` libera; potete ridistribuirla e/o modificarla
 sotto gli stessi termini di Perl stesso.
 Indipendentemente dalla sua distribuzione, tutti i codici di esempio
 in questi file sono qui posti nel pubblico dominio. Vi e` consentito,
 e siete anzi incoraggiati a farlo, utilizzare questo codice nei vostri
 propri programmi per divertimento o per profitto, come vi sembra meglio.
 Un semplice commento nel codice per dare credito sarebbe cortese ma non
 e` richiesto.

N.d.T.]


STORIA

Prima release: Sabato 9 Gennaio 1999, 08:09:11 MST


TRADUZIONE

Versione

La versione su cui si basa questa traduzione è ottenibile con:

   perl -MPOD2::IT -e print_pod perlopentut

Per maggiori informazioni sul progetto di traduzione in italiano si veda http://pod2it.sourceforge.net/ .

Traduttore

Traduzione a cura di Flavio Poletti.

Revisore

Revisione a cura di dree.


Mon Jun 11 22:02:18 2012