Di Perl, bagaimana saya bisa membaca seluruh file menjadi string?

118

Saya mencoba membuka file .html sebagai satu string panjang yang besar. Inilah yang saya punya:

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

yang mengakibatkan:

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

Namun, saya ingin hasilnya terlihat seperti:

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

Dengan cara ini saya dapat mencari seluruh dokumen dengan lebih mudah.

sialan
sumber
8
Benar-benar harus memeriksa apa definisi "Cant install", ini adalah masalah umum dan biasanya argumen yang tidak perlu dibuat. stackoverflow.com/questions/755168/perl-myths/…
Kent Fredric
1
Saya sebenarnya tidak dapat mengubah apa pun di seluruh server yang menjalankan skrip ini, selain dari skrip itu sendiri.
goddamnyouryan
Jadi Anda tidak diizinkan menambahkan file apa pun, di mana pun di server?
Brad Gilbert
Modul FatPack ke dalam skrip Anda? Selain itu, sepertinya Anda berpikir untuk mengurai HTML dengan ekspresi reguler, jangan.
MkV

Jawaban:

82

Menambahkan:

 local $/;

sebelum membaca dari pegangan file. Lihat Bagaimana saya bisa membaca seluruh file sekaligus? , atau

$ perldoc -q "seluruh file"

Lihat Variabel yang terkait dengan penanganan file di perldoc perlvardan perldoc -f local.

Kebetulan, jika Anda dapat meletakkan skrip Anda di server, Anda dapat memiliki semua modul yang Anda inginkan. Lihat Bagaimana cara menyimpan direktori modul / perpustakaan saya sendiri? .

Selain itu, Path :: Class :: File memungkinkan Anda untuk menyeruput dan memuntahkan .

Jalur :: kecil memberikan metode kenyamanan bahkan lebih seperti slurp, slurp_raw,slurp_utf8 serta mereka spewrekan-rekan.

Sinan Ünür
sumber
33
Anda mungkin harus menjelaskan apa efek pelokalan $ / yang akan dilakukan serta apa tujuannya.
Danny
12
Jika Anda tidak akan menjelaskan apa pun tentang pelokalan $/, Anda mungkin harus menambahkan tautan untuk informasi lebih lanjut.
Brad Gilbert
7
Penjelasan langkah demi langkah yang bagus tentang apa yang sedang dilakukan: {local $ /; <$ fh>} disediakan di sini: perlmonks.org/?node_id=287647
dawez
Mungkin katakan saja mengapa Anda harus menggunakan localdan tidak my.
Geremia
@Geremia Diskusi tentang pelingkupan berada di luar cakupan jawaban ini.
Sinan Ünür
99

Saya akan melakukannya seperti ini:

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

Perhatikan penggunaan versi tiga argumen dari open. Ini jauh lebih aman daripada dua versi argumen lama (atau satu). Perhatikan juga penggunaan filehandle leksikal. Penanganan file leksikal lebih bagus daripada varian bareword lama, karena berbagai alasan. Kami memanfaatkan salah satunya di sini: mereka menutup saat berada di luar jangkauan.

Chas. Owens
sumber
9
Ini mungkin cara non-cpan terbaik untuk melakukannya karena menggunakan argumen 3 terbuka serta menjaga variabel INPUT_RECORD_SEPARATOR ($ /) dilokalkan ke konteks terkecil yang diperlukan.
Danny
77

Dengan File :: Slurp :

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

Ya, bahkan Anda bisa menggunakan CPAN .

Quentin
sumber
OP mengatakan dia tidak dapat mengubah apapun di server. Tautan "Ya, bahkan Anda dapat menggunakan CPAN" di sini menunjukkan kepada Anda bagaimana mengatasi batasan itu, dalam banyak kasus.
Trenton
Can't locate File/Slurp.pm in @INC (@INC contains: /usr/lib/perl5/5.8/msys:(
Dmitry
2
@Dmitry - Jadi instal modulnya. Ada tautan instruksi pemasangan di halaman metacpan yang saya tautkan dari jawaban ini.
Quentin
53

Semua postingan sedikit non-idiomatik. Idiomnya adalah:

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

Sebagian besar, tidak perlu menyetel $ / ke undef.

jrockway
sumber
3
local $foo = undefhanyalah metode yang disarankan Perl Best Practice (PBP). Jika kita memposting potongan kode, saya pikir melakukan yang terbaik untuk menjelaskannya adalah Hal yang Baik.
Danny
2
Menunjukkan kepada orang-orang bagaimana menulis kode non-idiomatik adalah hal yang baik? Jika saya melihat "local $ / = undef" dalam kode yang saya kerjakan, tindakan pertama saya adalah mempermalukan penulis di depan umum di irc. (Dan saya biasanya tidak pilih-pilih tentang masalah "gaya".)
jrockway
1
Oke, saya akan menggigit: apa sebenarnya yang membuat lelucon tentang "local $ / = undef"? Jika satu-satunya jawaban Anda adalah "Ini bukan idiomatis," maka (a) saya tidak begitu yakin dan (b) terus kenapa? Saya tidak begitu yakin, karena itu sangat umum sebagai cara untuk melakukan ini. Dan mengapa karena itu sangat jelas dan cukup singkat. Anda mungkin lebih pilih-pilih tentang masalah gaya yang menurut Anda.
Telemakus
1
Kuncinya adalah "local $ /" adalah bagian dari idiom yang terkenal. Jika Anda menulis beberapa kode acak dan menulis "local $ Foo :: Bar = undef;", tidak masalah. Tetapi dalam kasus yang sangat khusus ini, Anda mungkin juga berbicara dengan bahasa yang sama seperti orang lain, meskipun itu "kurang jelas" (yang tidak saya setujui; perilaku "lokal" didefinisikan dengan baik dalam hal ini).
jrockway
11
Maaf, tidak setuju. Jauh lebih umum untuk bersikap eksplisit saat Anda ingin mengubah perilaku aktual variabel ajaib; itu adalah deklarasi niat. Bahkan dokumentasinya menggunakan 'local $ / = undef' (lihat perldoc.perl.org/perlsub.html#Temporary-Values-via-local () )
Leonardo Herrera
19

Dari perlfaq5: Bagaimana saya bisa membaca seluruh file sekaligus? :


Anda dapat menggunakan modul File :: Slurp untuk melakukannya dalam satu langkah.

use File::Slurp;

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

Pendekatan Perl biasa untuk memproses semua baris dalam file adalah melakukannya satu baris dalam satu waktu:

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

Ini jauh lebih efisien daripada membaca seluruh file ke dalam memori sebagai deretan baris dan kemudian memprosesnya satu elemen pada satu waktu, yang seringkali - jika tidak hampir selalu - pendekatan yang salah. Kapanpun Anda melihat seseorang melakukan ini:

@lines = <INPUT>;

Anda harus berpikir panjang dan keras tentang mengapa Anda membutuhkan semuanya dimuat sekaligus. Ini bukan solusi yang terukur. Anda mungkin juga merasa lebih menyenangkan menggunakan modul Tie :: File standar, atau binding $ DB_RECNO modul DB_File, yang memungkinkan Anda mengikat array ke file sehingga mengakses elemen array benar-benar mengakses baris yang sesuai dalam file .

Anda dapat membaca seluruh konten penanganan file ke dalam skalar.

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

Itu sementara undefs pemisah catatan Anda, dan secara otomatis akan menutup file di blok keluar. Jika file sudah terbuka, gunakan saja ini:

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

Untuk file biasa Anda juga dapat menggunakan fungsi baca.

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

Argumen ketiga menguji ukuran byte data pada INPUT filehandle dan membaca banyak byte ke dalam buffer $ var.

brian d foy
sumber
8

Cara yang sederhana adalah:

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

Cara lain adalah dengan mengubah pemisah catatan masukan "$ /". Anda dapat melakukannya secara lokal dalam blok kosong untuk menghindari perubahan pemisah data global.

{
    open(F, "filename");
    local $/ = undef;
    $d = <F>;
}
Peter Mortensen
sumber
1
Ada banyak masalah dengan kedua contoh yang Anda berikan. Masalah utamanya adalah bahwa mereka ditulis dalam Perl kuno, saya akan merekomendasikan membaca Perl Modern
Brad Gilbert
@Brad, komentar itu dibuat bertahun-tahun yang lalu, namun intinya masih berlaku. lebih baik adalah{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger
@ Joel itu hanya sedikit lebih baik. Anda tidak memeriksa output dari openatau yang dipanggil secara implisit close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (Itu masih memiliki masalah karena tidak menentukan pengkodean input.)
Brad Gilbert
use autodie, peningkatan besar yang ingin saya tunjukkan adalah filehandle leksikal dan 3 arg terbuka. Apakah ada alasan Anda melakukan doini? mengapa tidak membuang file ke variabel yang dideklarasikan sebelum blok?
Joel Berger
7

Baik setel $/ke undef(lihat jawaban jrockway) atau cukup gabungkan semua baris file:

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

Direkomendasikan untuk menggunakan skalar untuk penanganan file pada versi Perl yang mendukungnya.

kixx
sumber
4

Cara lain yang mungkin:

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

Anda hanya mendapatkan baris pertama dari operator berlian <FILE>karena Anda mengevaluasinya dalam konteks skalar:

$document = <FILE>; 

Dalam konteks daftar / larik, operator berlian akan mengembalikan semua baris file.

@lines = <FILE>;
print @lines;
Nathan
sumber
1
Hanya catatan tentang nomenklatur: operator pesawat ruang angkasa adalah <=>dan <>operator intan.
Perkakas
Oh, terima kasih, saya belum pernah mendengar "operator berlian" sebelumnya dan mengira mereka berdua memiliki nama yang sama. Saya akan memperbaikinya di atas.
Nathan
2

Saya akan melakukannya dengan cara yang paling sederhana, sehingga siapa pun dapat memahami apa yang terjadi, meskipun ada cara yang lebih cerdas:

my $text = "";
while (my $line = <FILE>) {
    $text .= $line;
}
SesuatuSesuatu
sumber
Semua rangkaian string itu akan menjadi sangat mahal. Saya akan menghindari melakukan ini. Mengapa merobek data hanya untuk disatukan kembali?
andru
2
open f, "test.txt"
$file = join '', <f>

<f>- mengembalikan array baris dari file kita (jika $/memiliki nilai default "\n") dan kemudian join ''akan menempelkan array ini ke.

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

Ini lebih merupakan saran tentang bagaimana TIDAK melakukannya. Saya baru saja mengalami kesulitan menemukan bug di aplikasi Perl yang agak besar. Sebagian besar modul memiliki file konfigurasinya sendiri. Untuk membaca file konfigurasi secara keseluruhan, saya menemukan satu baris Perl ini di suatu tempat di Internet:

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

Ini menetapkan kembali pemisah garis seperti yang dijelaskan sebelumnya. Tetapi itu juga menetapkan ulang STDIN.

Ini memiliki setidaknya satu efek samping yang menghabiskan waktu berjam-jam untuk menemukannya: Ini tidak menutup pegangan file implisit dengan benar (karena tidak memanggil closesama sekali).

Misalnya, melakukan itu:

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

menghasilkan:

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

Hal yang aneh adalah, bahwa penghitung baris $.meningkat untuk setiap file satu per satu. Ini tidak diatur ulang, dan tidak berisi jumlah baris. Dan itu tidak diatur ulang ke nol saat membuka file lain sampai setidaknya satu baris dibaca. Dalam kasus saya, saya melakukan sesuatu seperti ini:

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

Karena masalah ini, kondisinya salah karena penghitung saluran tidak diatur ulang dengan benar. Saya tidak tahu apakah ini bug atau kode yang salah ... Panggilan close;oder close STDIN;juga tidak membantu.

Saya mengganti kode yang tidak dapat dibaca ini dengan menggunakan open, string concatenation, dan close. Namun, solusi yang diposting oleh Brad Gilbert juga berfungsi karena menggunakan pegangan file eksplisit sebagai gantinya.

Tiga baris di awal dapat diganti dengan:

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

yang dengan benar menutup pegangan file.

rahang
sumber
2

Menggunakan

 $/ = undef;

sebelumnya $document = <FILE>;. $/adalah pemisah rekaman input , yang merupakan baris baru secara default. Dengan mendefinisikan ulang menjadi undef, Anda mengatakan tidak ada pemisah bidang. Ini disebut mode "slurp".

Solusi lain seperti undef $/dan local $/(tetapi tidak my $/) mendeklarasikan ulang $ / dan dengan demikian menghasilkan efek yang sama.

Geremia
sumber
0

Anda cukup membuat sub-rutin:

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

Saya tidak tahu apakah ini latihan yang baik, tetapi saya biasa menggunakan ini:

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

Ini semua adalah jawaban yang bagus. TETAPI jika Anda merasa malas, dan file tidak sebesar itu, dan keamanan bukanlah masalah (Anda tahu Anda tidak memiliki nama file yang tercemar), maka Anda dapat keluar:

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

Anda dapat menggunakan cat di Linux:

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