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

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

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

che risulta in:

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

Tuttavia, voglio che il risultato assomigli:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">

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

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" 

Vedi Variabili relative ai filehandle in perldoc perlvar e perldoc -f local .

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

Inoltre, Path :: Class :: File ti consente di fare lo slurp e spew .

Path :: Tiny offre ancora più metodi di comodità come slurp , slurp_raw , slurp_utf8 e le loro controparti spew .

Lo farei così:

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

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

Con File :: Slurp :

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

Sì, anche tu puoi usare CPAN .

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 $ / per undef .

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 l’elaborazione di tutte le righe in un file consiste nel fare una riga alla volta:

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

Questo è tremendamente più efficiente della lettura dell’intero file in memoria come una serie di linee e quindi l’elaborazione di un elemento alla volta, che è spesso – se non quasi sempre – l’approccio sbagliato. Ogni volta che vedi qualcuno fai questo:

 @lines = ; 

dovresti pensare a lungo e duramente perché hai bisogno di caricare tutto in una volta. Non è 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 permettono di bind una matrice a un file in modo che accedendo a un elemento la matrice effettivamente acceda alla riga corrispondente nel file .

È ansible leggere l’intero contenuto del filehandle in uno scalare.

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

Questo temporaneamente annulla il tuo separatore di record e chiuderà automaticamente il file all’uscita del blocco. Se il file è già aperto, basta usare questo:

 $var = do { local $/;  }; 

Per i file ordinari è anche ansible utilizzare la funzione di lettura.

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

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

Un modo semplice è:

 while () { $document .= $_ } 

Un altro modo è quello di cambiare 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 = ; } 

O imposta $/ to undef (vedi la risposta di jrockway) o concatena tutte le righe del file:

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

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

Un altro modo ansible:

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

Stai ricevendo solo la prima riga dall’operatore diamond perché la stai valutando in un contesto scalare:

 $document = ; 

Nel contesto lista / matrice, l’operatore diamante restituirà tutte le righe del file.

 @lines = ; print @lines; 

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

 my $text = ""; while (my $line = ) { $text .= $line; } 
 open f, "test.txt" $file = join '',  

– restituisce una matrice di linee dal nostro file (se $/ ha il valore predefinito "\n" ) e poi join '' inserirà questo array.

Questo è più di un suggerimento su come NON farlo. Ho appena passato un brutto periodo 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 complesso, 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 linee come spiegato in precedenza. Ma riassegna anche lo STDIN.

Questo ha avuto almeno un effetto collaterale che mi è costato ore per trovare: non chiude correttamente il gestore di file implicito (poiché non chiama affatto affatto).

Ad esempio, facendo così:

 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 () { 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 $. è aumentato per ogni file di uno. Non è resettato e non contiene il numero di linee. E non viene ripristinato a zero quando si apre un altro file fino a quando almeno una riga viene letta. Nel mio caso, stavo facendo qualcosa del genere:

 while($. < $skipLines) {}; 

A causa di questo problema, la condizione era falsa perché il contatore di riga non è stato ripristinato correttamente. Non so se si tratta di un bug o semplicemente di un codice sbagliato … Chiamando anche close; o close STDIN; non aiuta.

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

Le tre linee 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.

Uso

  $/ = undef; 

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

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

Potresti semplicemente creare una sub-routine:

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

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

 ($a=); 

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

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

Puoi usare cat in Linux:

 @file1=\`cat /etc/file.txt\`;