In Perl, come posso leggere un intero file in una stringa?


118

Sto cercando di aprire un file .html come una stringa lunga e grande. Questo è quello che ho:

open(FILE, 'index.html') or die "Can't read file 'filename' [$!]\n";  
$document = <FILE>; 
close (FILE);  
print $document;

che si traduce in:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN

Tuttavia, voglio che il risultato sia simile a:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">

In questo modo posso cercare più facilmente l'intero documento.


8
Veramente dovrebbe controllare qual è la definizione di "Non posso installare", è un problema comune ed è comunemente un argomento che non ha bisogno di essere fatto. stackoverflow.com/questions/755168/perl-myths/…
— Kent Fredric,

1
In realtà non sono in grado di modificare nulla sull'intero server su cui è in esecuzione questo script, a parte lo script stesso.
— goddamnyouryan

Quindi non sei autorizzato ad aggiungere alcun file, ovunque sul server?
— Brad Gilbert

Moduli FatPack nel tuo script? Inoltre, sembra che tu stia pensando di analizzare l'HTML con espressioni regolari, non farlo.
— MkV

Risposte:


82

Inserisci:

 local $/;

prima di leggere dall'handle del file. Vedi Come posso leggere un intero file tutto in una volta? , o

$ perldoc -q "intero file"

Vedere Variabili relative ai filehandle in perldoc perlvare perldoc -f local.

Per inciso, se puoi mettere il tuo script sul server, puoi avere tutti i moduli che vuoi. Vedi Come mantengo la mia directory modulo / libreria? .

Inoltre, Path :: Class :: File ti permette di bere e vomitare .

Percorso :: Piccolo dà ancora più metodi di convenienza, come slurp, slurp_raw,slurp_utf8 così come le loro spewcontroparti.


33
Probabilmente dovresti spiegare quali effetti avrà la localizzazione di $ / e qual è il suo scopo.
— Danny,

12
Se non hai intenzione di spiegare nulla sulla localizzazione $/, dovresti probabilmente aggiungere collegamenti per ulteriori informazioni.
— Brad Gilbert

7
Una buona spiegazione passo passo di cosa sta facendo: {local $ /; <$ fh>} è fornito qui: perlmonks.org/?node_id=287647
— dawez

Forse dì solo perché devi usare locale non my.
— Geremia

@Geremia Una discussione sullo scoping va oltre lo scopo di questa risposta.
— Sinan Ünür

99

Lo farei così:

my $file = "index.html";
my $document = do {
    local $/ = undef;
    open my $fh, "<", $file
        or die "could not open $file: $!";
    <$fh>;
};

Notare l'uso della versione a tre argomenti di open. È molto più sicuro delle vecchie versioni a due (o uno) argomento. Notare anche l'uso di un filehandle lessicale. I filehandle lessicali sono più belli delle vecchie varianti bareword, per molte ragioni. Ne stiamo approfittando qui: si chiudono quando escono dal campo di applicazione.


9
Questo è probabilmente il miglior modo non cpan per farlo poiché utilizza sia l'argomento 3 aperto che mantiene la variabile INPUT_RECORD_SEPARATOR ($ /) localizzata nel contesto più piccolo richiesto.
— Danny,

77

Con File :: Slurp :

use File::Slurp;
my $text = read_file('index.html');

Sì, anche tu puoi usare CPAN .


L'OP ha detto che non può modificare nulla sul server. Il link "Sì, anche tu puoi usare CPAN" qui mostra come aggirare questa limitazione, nella maggior parte dei casi.
— Trenton

Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
— Dmitry

2
@Dmitry - Quindi installa il modulo. C'è un collegamento alle istruzioni di installazione nella pagina del metacpan a cui ho collegato da questa risposta.
— Quentin

53

Tutti i post sono leggermente non idiomatici. L'idioma è:

open my $fh, '<', $filename or die "error opening $filename: $!";
my $data = do { local $/; <$fh> };

Per lo più, non è necessario impostare $ / su undef.


3
local $foo = undefè solo il metodo suggerito da Perl Best Practice (PBP). Se pubblichiamo frammenti di codice, penso che fare del nostro meglio per renderlo chiaro sarebbe una buona cosa.
— Danny,

2
Mostrare alle persone come scrivere codice non idiomatico è una buona cosa? Se vedessi "local $ / = undef" nel codice su cui stavo lavorando, la mia prima azione sarebbe stata quella di umiliare pubblicamente l'autore su irc. (E generalmente non sono schizzinoso riguardo alle questioni di "stile".)
— jrockway

1
Ok, mordo: cosa è esattamente degno di finta di "local $ / = undef"? Se la tua unica risposta è "Non è idiomatico", allora (a) non ne sono così sicuro e (b) e allora? Non ne sono così sicuro, perché è dannatamente comune come un modo per farlo. E allora perché è perfettamente chiaro e ragionevolmente breve. Potresti essere più esigente riguardo ai problemi di stile che pensi.
— Telemaco

1
La chiave è che "local $ /" fa parte di un noto idioma. Se stai scrivendo del codice casuale e scrivi "local $ Foo :: Bar = undef;", va bene. Ma in questo caso molto speciale, potresti anche parlare la stessa lingua di tutti gli altri, anche se è "meno chiara" (cosa su cui non sono d'accordo; il comportamento di "locale" è ben definito sotto questo aspetto).
— jrockway

11
Scusa, non sono d'accordo. È molto più comune essere espliciti quando si desidera modificare il comportamento effettivo di una variabile magica; è una dichiarazione di intenti. Anche la documentazione usa 'local $ / = undef' (vedi perldoc.perl.org/perlsub.html#Temporary-Values-via-local () )
— Leonardo Herrera,

19

Da perlfaq5: come posso leggere un intero file tutto in una volta? :


Puoi usare il modulo File :: Slurp per farlo in un solo passaggio.

use File::Slurp;

$all_of_it = read_file($filename); # entire file in scalar
@all_lines = read_file($filename); # one line per element

Il consueto approccio Perl per elaborare tutte le righe in un file è di farlo una riga alla volta:

open (INPUT, $file)     || die "can't open $file: $!";
while (<INPUT>) {
    chomp;
    # do something with $_
    }
close(INPUT)            || die "can't close $file: $!";

Questo è tremendamente più efficiente che leggere l'intero file in memoria come un array di righe e poi elaborarlo un elemento alla volta, il che è spesso - se non quasi sempre - l'approccio sbagliato. Ogni volta che vedi qualcuno fare questo:

@lines = <INPUT>;

dovresti pensare a lungo e intensamente al motivo per cui hai bisogno di caricare tutto in una volta. Non è solo una soluzione scalabile. Potresti anche trovare più divertente usare il modulo standard Tie :: File, o le associazioni $ DB_RECNO del modulo DB_File, che ti consentono di legare un array a un file in modo che accedendo a un elemento l'array acceda effettivamente alla riga corrispondente nel file .

Puoi leggere l'intero contenuto del filehandle in uno scalare.

{
local(*INPUT, $/);
open (INPUT, $file)     || die "can't open $file: $!";
$var = <INPUT>;
}

Questo annulla temporaneamente la definizione del separatore di record e chiude automaticamente il file all'uscita dal blocco. Se il file è già aperto, usa questo:

$var = do { local $/; <INPUT> };

Per i file ordinari puoi anche usare la funzione di lettura.

read( INPUT, $var, -s INPUT );

Il terzo argomento verifica la dimensione in byte dei dati sul filehandle INPUT e legge altrettanti byte nel buffer $ var.


8

Un modo semplice è:

while (<FILE>) { $document .= $_ }

Un altro modo è modificare il separatore del record di input "$ /". Puoi farlo localmente in un blocco nudo per evitare di cambiare il separatore di record globale.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}

1
C'è un numero significativo di problemi con entrambi gli esempi che hai fornito. Il problema principale è che sono scritti in Perl antico, consiglierei di leggere Modern Perl
— Brad Gilbert

@ Brad, il commento è stato fatto anni fa, il punto resta comunque. meglio è{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
— Joel Berger

@ Joel che è solo leggermente migliore. Non hai controllato l'output openo la chiamata implicita close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (Questo ha ancora il problema che non specifica la codifica di input.)
— Brad Gilbert

use autodie, il miglioramento principale che intendevo mostrare era il filehandle lessicale e l'apertura di 3 arg. C'è qualche motivo per cui lo stai facendo do? perché non scaricare semplicemente il file in una variabile dichiarata prima del blocco?
— Joel Berger

7

O impostato $/su undef(vedi la risposta di jrockway) o concatena semplicemente tutte le righe del file:

$content = join('', <$fh>);

Si consiglia di utilizzare gli scalari per i filehandle su qualsiasi versione di Perl che lo supporti.


4

Un altro modo possibile:

open my $fh, '<', "filename";
read $fh, my $string, -s $fh;
close $fh;

3

Ricevi solo la prima riga dall'operatore diamante <FILE>perché la stai valutando in un contesto scalare:

$document = <FILE>; 

Nel contesto elenco / matrice, l'operatore rombo restituirà tutte le righe del file.

@lines = <FILE>;
print @lines;

1
Solo una nota sulla nomenclatura: l'operatore dell'astronave è <=>e l' <>operatore è il diamante.
— toolic

Oh, grazie, non avevo sentito prima "operatore di diamanti" e pensavo che entrambi condividessero lo stesso nome. Lo correggerò sopra.
— Nathan

2

Lo farei nel modo più semplice, così chiunque può capire cosa succede, anche se ci sono modi più intelligenti:

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}

Tutte queste concatenazioni di stringhe saranno piuttosto costose. Eviterei di farlo. Perché dividere i dati solo per rimetterli insieme?
— andru

2
open f, "test.txt"
$file = join '', <f>

<f>- restituisce un array di righe dal nostro file (se $/ha il valore predefinito "\n") e poi join ''inserirà questo array in.


2

Questo è più un suggerimento su come NON farlo. Ho appena avuto problemi a trovare un bug in un'applicazione Perl piuttosto grande. La maggior parte dei moduli aveva i propri file di configurazione. Per leggere i file di configurazione nel loro insieme, ho trovato questa singola riga di Perl da qualche parte su Internet:

# Bad! Don't do that!
my $content = do{local(@ARGV,$/)=$filename;<>};

Riassegna il separatore di riga come spiegato prima. Ma riassegna anche STDIN.

Questo ha avuto almeno un effetto collaterale che mi è costato ore da trovare: non chiude correttamente l'handle del file implicito (poiché non chiama closeaffatto).

Ad esempio, in questo modo:

use strict;
use warnings;

my $filename = 'some-file.txt';

my $content = do{local(@ARGV,$/)=$filename;<>};
my $content2 = do{local(@ARGV,$/)=$filename;<>};
my $content3 = do{local(@ARGV,$/)=$filename;<>};

print "After reading a file 3 times redirecting to STDIN: $.\n";

open (FILE, "<", $filename) or die $!;

print "After opening a file using dedicated file handle: $.\n";

while (<FILE>) {
    print "read line: $.\n";
}

print "before close: $.\n";
close FILE;
print "after close: $.\n";

risultati in:

After reading a file 3 times redirecting to STDIN: 3
After opening a file using dedicated file handle: 3
read line: 1
read line: 2
(...)
read line: 46
before close: 46
after close: 0

La cosa strana è che il contatore di riga $.viene aumentato di uno per ogni file. Non è ripristinato e non contiene il numero di righe. E non viene azzerato quando si apre un altro file fino a quando non viene letta almeno una riga. Nel mio caso, stavo facendo qualcosa del genere:

while($. < $skipLines) {<FILE>};

A causa di questo problema, la condizione era falsa perché il contatore di riga non è stato reimpostato correttamente. Non so se sia un bug o semplicemente un codice sbagliato ... Anche chiamare close;oder close STDIN;non aiuta.

Ho sostituito questo codice illeggibile usando open, concatenazione di stringhe e close. Tuttavia, la soluzione pubblicata da Brad Gilbert funziona anche poiché utilizza invece un handle di file esplicito.

Le tre righe all'inizio possono essere sostituite da:

my $content = do{local $/; open(my $f1, '<', $filename) or die $!; my $tmp1 = <$f1>; close $f1 or die $!; $tmp1};
my $content2 = do{local $/; open(my $f2, '<', $filename) or die $!; my $tmp2 = <$f2>; close $f2 or die $!; $tmp2};
my $content3 = do{local $/; open(my $f3, '<', $filename) or die $!; my $tmp3 = <$f3>; close $f3 or die $!; $tmp3};

che chiude correttamente l'handle del file.


2

Uso

 $/ = undef;

prima $document = <FILE>;. $/è il separatore del record di input , che per impostazione predefinita è una nuova riga. Ridefinendolo in undef, stai dicendo che non esiste un separatore di campo. Questa è chiamata modalità "slurp".

Altre soluzioni come undef $/e local $/(ma non my $/) ridichiarano $ / e quindi producono lo stesso effetto.


0

Potresti semplicemente creare una sub-routine:

#Get File Contents
sub gfc
{
    open FC, @_[0];
    join '', <FC>;
}

0

Non so se sia una buona pratica, ma usavo questo:

($a=<F>);

-1

Queste sono tutte buone risposte. MA se ti senti pigro e il file non è così grande e la sicurezza non è un problema (sai di non avere un nome file contaminato), puoi sborsare:

$x=`cat /tmp/foo`;    # note backticks, qw"cat ..." also works

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.