Wie kann ich in Perl eine ganze Datei in eine Zeichenfolge einlesen?

118

Ich versuche, eine HTML-Datei als eine große lange Zeichenfolge zu öffnen. Das habe ich:

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

was in ... endet:

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

Ich möchte jedoch, dass das Ergebnis wie folgt aussieht:

<!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">

Auf diese Weise kann ich das gesamte Dokument einfacher durchsuchen.

goddamnyouryan
quelle
8
Sollte wirklich überprüfen, was die Definition von "Cant install" ist, es ist ein häufiges Problem und es ist häufig ein Argument, das nicht gemacht werden muss. stackoverflow.com/questions/755168/perl-myths/…
Kent Fredric
1
Ich kann auf dem gesamten Server, auf dem dieses Skript ausgeführt wird, nichts ändern, außer dem Skript selbst.
goddamnyouryan
Sie dürfen also nirgendwo auf dem Server Dateien hinzufügen?
Brad Gilbert
FatPack-Module in Ihr Skript? Es sieht auch so aus, als würden Sie vielleicht daran denken, HTML mit regulären Ausdrücken zu analysieren, nicht wahr?
MkV

Antworten:

81

Hinzufügen:

 local $/;

vor dem Lesen aus dem Dateihandle. Siehe Wie kann ich eine ganze Datei auf einmal einlesen? , oder

$ perldoc -q "gesamte Datei"

Siehe Variablen zu Dateihandles in perldoc perlvarund perldoc -f local.

Wenn Sie Ihr Skript auf den Server stellen können, können Sie übrigens alle gewünschten Module haben. Siehe Wie behalte ich mein eigenes Modul- / Bibliotheksverzeichnis? .

Darüber hinaus können Sie mit Path :: Class :: File schlürfen und spucken .

Pfad :: Tiny gibt noch mehr Komfort Methoden wie slurp, slurp_raw,slurp_utf8 sowie deren spewPendants.

Sinan Ünür
quelle
33
Sie sollten wahrscheinlich erklären, welche Auswirkungen die Lokalisierung von $ / hat und welchen Zweck sie hat.
Danny
12
Wenn Sie nichts über die Lokalisierung erklären möchten $/, sollten Sie wahrscheinlich Links für weitere Informationen hinzufügen.
Brad Gilbert
7
Eine gute schrittweise Erklärung, was zu tun ist: {local $ /; <$ fh>} wird hier bereitgestellt: perlmonks.org/?node_id=287647
dawez
Vielleicht sagen Sie einfach, warum Sie verwenden müssen localund nicht my.
Geremia
@Geremia Eine Diskussion über das Scoping würde den Rahmen dieser Antwort sprengen.
Sinan Ünür
99

Ich würde es so machen:

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

Beachten Sie die Verwendung der Drei-Argument-Version von open. Es ist viel sicherer als die alten Versionen mit zwei (oder einem) Argumenten. Beachten Sie auch die Verwendung eines lexikalischen Dateihandles. Lexikalische Dateihandles sind aus vielen Gründen schöner als die alten Bareword-Varianten. Wir nutzen hier einen davon: Sie schließen, wenn sie den Rahmen verlassen.

Chas. Owens
quelle
9
Dies ist wahrscheinlich der beste Weg, dies ohne Cpan zu tun, da sowohl das Argument 3 offen ist als auch die Variable INPUT_RECORD_SEPARATOR ($ /) im kleinsten erforderlichen Kontext lokalisiert bleibt.
Danny
77

Mit File :: Slurp :

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

Ja, auch Sie können CPAN verwenden .

QUentin
quelle
Das OP sagte, er könne nichts auf dem Server ändern. Der Link "Ja, auch Sie können CPAN verwenden" zeigt Ihnen, wie Sie diese Einschränkung in den meisten Fällen umgehen können.
Trenton
Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
Dmitry
2
@Dmitry - Installieren Sie also das Modul. Auf der Metacpan-Seite, auf die ich über diese Antwort verlinkt habe, befindet sich ein Link mit Installationsanweisungen.
Quentin
52

Alle Beiträge sind leicht nicht idiomatisch. Die Redewendung lautet:

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

Meistens muss $ / nicht festgelegt werden undef.

Jrockway
quelle
3
local $foo = undefist nur die von Perl Best Practice (PBP) vorgeschlagene Methode. Wenn wir Codefragmente veröffentlichen, würde ich denken, dass es eine gute Sache wäre, unser Bestes zu geben, um dies klar zu machen.
Danny
2
Menschen zu zeigen, wie man nicht-idiomatischen Code schreibt, ist eine gute Sache? Wenn ich "local $ / = undef" in dem Code sehen würde, an dem ich arbeite, wäre meine erste Aktion, den Autor auf irc öffentlich zu demütigen. (Und ich bin im Allgemeinen nicht wählerisch in
Bezug auf
1
Ok, ich werde beißen: Was genau ist an "local $ / = undef" spöttisch? Wenn Ihre einzige Antwort "Es ist nicht idiomatisch" ist, dann (a) bin ich mir nicht so sicher und (b) na und? Ich bin mir nicht so sicher, weil es schrecklich üblich ist, dies zu tun. Und was nun, weil es vollkommen klar und ziemlich kurz ist. Sie sind möglicherweise wählerischer in Bezug auf Stilprobleme, die Sie denken.
Telemachos
1
Der Schlüssel ist, dass das "lokale $ /" Teil einer bekannten Redewendung ist. Wenn Sie zufälligen Code schreiben und "local $ Foo :: Bar = undef;" schreiben, ist das in Ordnung. Aber in diesem ganz besonderen Fall können Sie genauso gut dieselbe Sprache sprechen wie alle anderen, auch wenn es "weniger klar" ist (dem ich nicht zustimme; das Verhalten von "lokal" ist in dieser Hinsicht genau definiert).
Jrockway
11
Entschuldigung, nicht einverstanden. Es ist weitaus üblicher, explizit zu sein, wenn Sie das tatsächliche Verhalten einer magischen Variablen ändern möchten. es ist eine Absichtserklärung. Sogar die Dokumentation verwendet 'local $ / = undef' (siehe perldoc.perl.org/perlsub.html#Temporary-Values-via-local () )
Leonardo Herrera
19

Von perlfaq5: Wie kann ich eine ganze Datei auf einmal einlesen? ::


Sie können das File :: Slurp-Modul verwenden, um dies in einem Schritt zu tun.

use File::Slurp;

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

Der übliche Perl-Ansatz zum Verarbeiten aller Zeilen in einer Datei besteht darin, dies zeilenweise zu tun:

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

Dies ist enorm effizienter, als die gesamte Datei als Zeilenarray in den Speicher einzulesen und dann Element für Element zu verarbeiten, was häufig - wenn nicht fast immer - der falsche Ansatz ist. Wann immer Sie jemanden sehen, tun Sie Folgendes:

@lines = <INPUT>;

Sie sollten lange und gründlich darüber nachdenken, warum Sie alles auf einmal laden müssen. Es ist einfach keine skalierbare Lösung. Möglicherweise macht es auch mehr Spaß, das Standardmodul Tie :: File oder die $ DB_RECNO-Bindungen des DB_File-Moduls zu verwenden, mit denen Sie ein Array an eine Datei binden können, sodass das Array beim Zugriff auf ein Element tatsächlich auf die entsprechende Zeile in der Datei zugreift .

Sie können den gesamten Inhalt des Dateihandles in einen Skalar einlesen.

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

Dadurch wird Ihr Datensatztrennzeichen vorübergehend deaktiviert, und die Datei wird beim Blockausgang automatisch geschlossen. Wenn die Datei bereits geöffnet ist, verwenden Sie einfach Folgendes:

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

Für normale Dateien können Sie auch die Lesefunktion verwenden.

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

Das dritte Argument testet die Bytegröße der Daten im INPUT-Dateihandle und liest so viele Bytes in den Puffer $ var.

brian d foy
quelle
7

Ein einfacher Weg ist:

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

Eine andere Möglichkeit besteht darin, das Trennzeichen für den Eingabedatensatz "$ /" zu ändern. Sie können dies lokal in einem leeren Block tun, um zu vermeiden, dass das globale Datensatztrennzeichen geändert wird.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}
Peter Mortensen
quelle
1
Bei beiden von Ihnen angegebenen Beispielen gibt es eine erhebliche Anzahl von Problemen. Das Hauptproblem ist, dass sie im alten Perl geschrieben sind. Ich würde empfehlen, Modern Perl
Brad Gilbert
@Brad, der Kommentar wurde vor Jahren gemacht, der Punkt steht aber noch. besser ist{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger
@ Joel das ist nur wenig besser. Sie haben die Ausgabe von openoder die implizit aufgerufene nicht überprüft close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (Das hat immer noch das Problem, dass es die Eingabecodierung nicht spezifiziert.)
Brad Gilbert
use autodieDie Hauptverbesserung, die ich zeigen wollte, war das lexikalische Dateihandle und das 3-Arg-Open. Gibt es einen Grund, warum Sie das dotun? Warum nicht einfach die Datei in eine Variable ausgeben, die vor dem Block deklariert wurde?
Joel Berger
7

Entweder Satz $/an undef(jrockway Antwort sehen) oder verketten nur alle Zeilen der Datei:

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

Es wird empfohlen, Skalare für Dateihandles in jeder Perl-Version zu verwenden, die dies unterstützt.

kixx
quelle
4

Ein anderer möglicher Weg:

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

Sie erhalten nur die erste Zeile vom Diamantoperator, <FILE>weil Sie sie im skalaren Kontext auswerten:

$document = <FILE>; 

Im Listen- / Array-Kontext gibt der Diamantoperator alle Zeilen der Datei zurück.

@lines = <FILE>;
print @lines;
Nathan
quelle
1
Nur ein Hinweis zur Nomenklatur: Der Raumschiffoperator ist <=>und der <>ist der Diamantoperator.
Toolic
Oh, danke, ich hatte noch nie "Diamond Operator" gehört und dachte, sie hätten beide den gleichen Namen. Ich werde es oben korrigieren.
Nathan
2

Ich würde es auf einfachste Weise tun, damit jeder verstehen kann, was passiert, auch wenn es intelligentere Wege gibt:

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}
Etwas etwas
quelle
All diese String-Verkettungen werden ziemlich teuer sein. Ich würde das vermeiden. Warum die Daten nur zerreißen, um sie wieder zusammenzusetzen?
Andru
2
open f, "test.txt"
$file = join '', <f>

<f>- gibt ein Array von Zeilen aus unserer Datei zurück (falls $/der Standardwert vorliegt "\n") und join ''steckt dieses Array dann in.

Тима Епанчинцев
quelle
2

Dies ist eher ein Vorschlag, wie man es NICHT macht. Ich hatte gerade eine schlechte Zeit, einen Fehler in einer ziemlich großen Perl-Anwendung zu finden. Die meisten Module hatten eigene Konfigurationsdateien. Um die Konfigurationsdateien als Ganzes zu lesen, habe ich diese einzelne Perl-Zeile irgendwo im Internet gefunden:

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

Der Zeilentrenner wird wie zuvor erläutert neu zugewiesen. Es ordnet aber auch den STDIN neu zu.

Dies hatte mindestens einen Nebeneffekt, dessen Suche mich Stunden gekostet hat: Das implizite Dateihandle wird nicht ordnungsgemäß geschlossen (da es überhaupt nicht aufgerufen wird close).

Zum Beispiel:

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";

Ergebnisse 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

Das Seltsame ist, dass der Zeilenzähler $.für jede Datei um eins erhöht wird. Es wird nicht zurückgesetzt und enthält nicht die Anzahl der Zeilen. Und es wird beim Öffnen einer anderen Datei nicht auf Null zurückgesetzt, bis mindestens eine Zeile gelesen wurde. In meinem Fall habe ich so etwas gemacht:

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

Aufgrund dieses Problems war die Bedingung falsch, da der Zeilenzähler nicht ordnungsgemäß zurückgesetzt wurde. Ich weiß nicht, ob dies ein Fehler oder einfach ein falscher Code ist ... Auch das Aufrufen von close;oder close STDIN;hilft nicht.

Ich habe diesen unlesbaren Code durch Öffnen, Verketten von Zeichenfolgen und Schließen ersetzt. Die von Brad Gilbert veröffentlichte Lösung funktioniert jedoch auch, da stattdessen ein explizites Dateihandle verwendet wird.

Die drei Zeilen am Anfang können ersetzt werden durch:

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};

Dadurch wird das Dateihandle ordnungsgemäß geschlossen.

Kiefer
quelle
2

Verwenden

 $/ = undef;

vorher $document = <FILE>;. $/ist das Trennzeichen für Eingabedatensätze , bei dem es sich standardmäßig um eine neue Zeile handelt. Wenn undefSie es neu definieren, sagen Sie, dass es kein Feldtrennzeichen gibt. Dies wird als "Slurp" -Modus bezeichnet.

Andere Lösungen wie undef $/und local $/(aber nicht my $/) deklarieren $ / und erzeugen somit den gleichen Effekt.

Gerämie
quelle
0

Sie können einfach eine Unterroutine erstellen:

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

Ich weiß nicht, ob es eine gute Praxis ist, aber ich habe diese verwendet:

($a=<F>);
zawy
quelle
-1

Das sind alles gute Antworten. ABER wenn Sie sich faul fühlen und die Datei nicht so groß ist und die Sicherheit kein Problem darstellt (Sie wissen, dass Sie keinen verdorbenen Dateinamen haben), können Sie Folgendes berappen:

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

Sie können cat unter Linux verwenden:

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