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.