Trong Perl, làm cách nào tôi có thể đọc toàn bộ tệp thành một chuỗi?


118

Tôi đang cố mở một tệp .html dưới dạng một chuỗi dài lớn. Đây là những gì tôi đã có:

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

kết quả là:

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

Tuy nhiên, tôi muốn kết quả trông như sau:

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

Bằng cách này tôi có thể tìm kiếm toàn bộ tài liệu dễ dàng hơn.


8
Thực sự nên kiểm tra định nghĩa của "Cant install" là gì, đây là một vấn đề phổ biến và thường là một đối số không cần phải đưa ra. stackoverflow.com/questions/755168/perl-myths/ từ
Kent Fredric

1
Tôi thực sự không thể sửa đổi bất cứ điều gì trên toàn bộ sever mà tập lệnh này đang chạy, ngoài kịch bản mà nó tự chạy.
goddamnyouryan

Vì vậy, bạn không được phép thêm bất kỳ tập tin, bất cứ nơi nào trên máy chủ?
Brad Gilbert

Mô-đun FatPack vào tập lệnh của bạn? Ngoài ra, có vẻ như bạn có thể nghĩ đến việc phân tích cú pháp HTML bằng các biểu thức thông thường, không.
MkV

Câu trả lời:


81

Thêm vào:

 local $/;

trước khi đọc từ tập tin xử lý. Xem Làm thế nào tôi có thể đọc toàn bộ tập tin cùng một lúc? , hoặc là

$ perldoc -q "toàn bộ tập tin"

Xem các biến liên quan đến tập tin trong perldoc perlvarperldoc -f local.

Ngẫu nhiên, nếu bạn có thể đặt tập lệnh của mình lên máy chủ, bạn có thể có tất cả các mô-đun bạn muốn. Xem Làm thế nào để tôi giữ thư mục mô-đun / thư viện của riêng tôi? .

Ngoài ra, Path :: Class :: File cho phép bạn nhếch nhácphun ra .

Đường dẫn :: Tiny cho phương pháp thuận tiện hơn như slurp, slurp_raw,slurp_utf8 cũng như họ spewđối tác.


33
Bạn có lẽ nên giải thích những tác động nội địa hóa $ / sẽ làm gì cũng như mục đích của nó là gì.
Daniel

12
Nếu bạn sẽ không giải thích bất cứ điều gì về nội địa hóa $/, có lẽ bạn nên thêm liên kết để biết thêm thông tin.
Brad Gilbert

7
Một giải thích từng bước tốt về những gì đang làm: {local $ /; <$ fh>} được cung cấp tại đây: perlmonks.org/?node_id=287647
dawez

Có lẽ chỉ cần nói tại sao bạn phải sử dụng localvà không my.
Geremia

@Geremia Một cuộc thảo luận về phạm vi nằm ngoài phạm vi của câu trả lời này.
Sinan Ünür 14/03/2016

99

Tôi sẽ làm như thế này:

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

Lưu ý việc sử dụng phiên bản ba đối số mở. Nó an toàn hơn nhiều so với các phiên bản đối số hai (hoặc một-) cũ. Cũng lưu ý việc sử dụng một tập tin từ vựng. Các tập tin từ điển đẹp hơn các biến thể bareword cũ, vì nhiều lý do. Chúng tôi đang lợi dụng một trong số họ ở đây: họ đóng cửa khi họ ra khỏi phạm vi.


9
Đây có lẽ là cách tốt nhất để làm điều đó vì nó sử dụng cả 3 đối số mở cũng như giữ biến INPUT_RECORD_SEPARATOR ($ /) được định vị theo ngữ cảnh yêu cầu nhỏ nhất.
Daniel

77

Với tệp :: Slurp :

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

Có, thậm chí bạn có thể sử dụng CPAN .


OP cho biết anh không thể sửa đổi bất cứ điều gì trên máy chủ. Liên kết "Có, thậm chí bạn có thể sử dụng CPAN" ở đây chỉ cho bạn cách khắc phục giới hạn đó, trong hầu hết các trường hợp.
Trenton

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

2
@Dmitry - Vậy cài đặt module. Có một liên kết hướng dẫn cài đặt trên trang metacpan mà tôi đã liên kết từ câu trả lời này.
Quentin

52

Tất cả các bài viết hơi không thành ngữ. Thành ngữ là:

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

Hầu hết, không cần phải đặt $ / thành undef.


3
local $foo = undefchỉ là phương pháp được đề xuất Thực hành tốt nhất (PBP) của Perl. Nếu chúng tôi đang đăng các đoạn mã, tôi nghĩ rằng làm hết sức mình để làm cho nó rõ ràng sẽ là một điều tốt.
Daniel

2
Chỉ cho mọi người cách viết mã không thành ngữ là một điều tốt? Nếu tôi thấy "local $ / = undef" trong mã mà tôi đang làm việc, hành động đầu tiên của tôi sẽ là làm nhục công khai tác giả trên irc. (Và tôi thường không kén chọn các vấn đề về "phong cách".)
jrockway

1
Ok, tôi sẽ cắn: chính xác thì cái gì là xứng đáng với "local $ / = undef"? Nếu câu trả lời duy nhất của bạn là "Nó không phải là thành ngữ", thì (a) tôi không chắc lắm và (b) vậy thì sao? Tôi không chắc lắm, vì nó cực kỳ phổ biến như một cách để làm điều này. Và vì vậy những gì bởi vì nó hoàn toàn rõ ràng và hợp lý ngắn gọn. Bạn có thể kén chọn hơn về các vấn đề phong cách mà bạn nghĩ.
Telemachus

1
Điều quan trọng là "$ /" cục bộ là một phần của thành ngữ nổi tiếng. Nếu bạn đang viết một số mã ngẫu nhiên và viết "local $ Foo :: Bar = undef;", điều đó là tốt. Nhưng trong trường hợp rất đặc biệt này, bạn cũng có thể nói cùng một ngôn ngữ với mọi người khác, ngay cả khi nó "không rõ ràng" (mà tôi không đồng ý; hành vi của "địa phương" được xác định rõ về mặt này).
jrockway

11
Xin lỗi, không đồng ý. Nó là phổ biến hơn nhiều để được rõ ràng khi bạn muốn thay đổi hành vi thực tế của một biến ma thuật; đó là một tuyên bố về ý định. Ngay cả tài liệu cũng sử dụng 'local $ / = undef' (xem perldoc.perl.org/perlsub.html#Tceed-Values-via-local () )
Leonardo Herrera

19

Từ perlfaq5: Làm thế nào tôi có thể đọc toàn bộ tập tin cùng một lúc? :


Bạn có thể sử dụng mô-đun File :: Slurp để thực hiện trong một bước.

use File::Slurp;

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

Cách tiếp cận Perl thông thường để xử lý tất cả các dòng trong một tệp là thực hiện từng dòng một:

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

Điều này hiệu quả hơn nhiều so với việc đọc toàn bộ tệp vào bộ nhớ dưới dạng một mảng các dòng và sau đó xử lý từng phần tử một, thường là - nếu không phải luôn luôn - cách tiếp cận sai. Bất cứ khi nào bạn thấy ai đó làm điều này:

@lines = <INPUT>;

bạn nên suy nghĩ lâu dài về lý do tại sao bạn cần mọi thứ được tải cùng một lúc. Nó chỉ không phải là một giải pháp mở rộng. Bạn cũng có thể thấy thú vị hơn khi sử dụng mô-đun Tie :: File tiêu chuẩn hoặc các ràng buộc $ DB_RECNO của mô-đun DB_File, cho phép bạn buộc một mảng vào một tệp để truy cập một phần tử mà mảng thực sự truy cập vào dòng tương ứng trong tệp .

Bạn có thể đọc toàn bộ nội dung tập tin vào một vô hướng.

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

Điều đó tạm thời làm mất dấu phân tách bản ghi của bạn và sẽ tự động đóng tệp khi thoát khỏi khối. Nếu tệp đã được mở, chỉ cần sử dụng:

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

Đối với các tệp thông thường, bạn cũng có thể sử dụng chức năng đọc.

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

Đối số thứ ba kiểm tra kích thước byte của dữ liệu trên tệp INPUT và đọc nhiều byte đó vào bộ đệm $ var.


7

Một cách đơn giản là:

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

Một cách khác là thay đổi dấu tách bản ghi đầu vào "$ /". Bạn có thể làm điều đó cục bộ trong một khối trống để tránh thay đổi dấu tách bản ghi toàn cầu.

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

1
Có một số lượng đáng kể các vấn đề với cả hai ví dụ bạn đã đưa ra. Vấn đề chính là chúng được viết bằng Perl cổ đại, tôi khuyên bạn nên đọc Modern Perl
Brad Gilbert

@Brad, bình luận đã được thực hiện nhiều năm trước, tuy nhiên quan điểm vẫn đứng vững. tốt hơn là{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger

@Joel mà chỉ tốt hơn một chút. Bạn đã không kiểm tra đầu ra của openhoặc được gọi ngầm close. my $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}. (Điều đó vẫn có vấn đề là nó không chỉ định mã hóa đầu vào.)
Brad Gilbert

use autodie, cải tiến lớn mà tôi muốn thể hiện là tập tin từ vựng và mở 3 arg. Có một số lý do bạn đang doing này? Tại sao không chỉ đổ tập tin vào một biến được khai báo trước khối?
Joel Berger

7

Hoặc được đặt $/thành undef(xem câu trả lời của jrockway) hoặc chỉ nối tất cả các dòng của tệp:

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

Bạn nên sử dụng vô hướng cho các tập tin trên bất kỳ phiên bản Perl nào hỗ trợ nó.


4

Một cách khác có thể:

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

3

Bạn chỉ nhận được dòng đầu tiên từ nhà điều hành kim cương <FILE>vì bạn đang đánh giá nó trong bối cảnh vô hướng:

$document = <FILE>; 

Trong bối cảnh danh sách / mảng, toán tử kim cương sẽ trả về tất cả các dòng của tệp.

@lines = <FILE>;
print @lines;

1
Chỉ cần một lưu ý về danh pháp: toán tử tàu vũ trụ <=><>toán tử kim cương.
cụ

Ồ, cảm ơn, tôi đã không nghe thấy "nhà điều hành kim cương" trước đây và nghĩ rằng cả hai đều có cùng tên. Tôi sẽ sửa nó ở trên.
Nathan

2

Tôi sẽ làm điều đó theo cách đơn giản nhất, vì vậy bất cứ ai cũng có thể hiểu điều gì xảy ra, ngay cả khi có những cách thông minh hơn:

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

Tất cả các kết nối chuỗi sẽ khá tốn kém. Tôi sẽ tránh làm điều này. Tại sao xé dữ liệu chỉ để đặt lại với nhau?
andru

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

<f>- trả về một mảng các dòng từ tệp của chúng tôi (nếu $/có giá trị mặc định "\n") và sau đó join ''sẽ gắn mảng này vào.


2

Đây là một gợi ý về cách KHÔNG làm điều đó. Tôi đã có một thời gian tồi tệ khi tìm thấy một lỗi trong một ứng dụng Perl khá lớn. Hầu hết các mô-đun có tập tin cấu hình riêng. Để đọc toàn bộ các tệp cấu hình, tôi tìm thấy dòng Perl này ở đâu đó trên Internet:

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

Nó gán lại dấu phân cách dòng như đã giải thích trước đó. Nhưng nó cũng gán lại STDIN.

Điều này có ít nhất một tác dụng phụ khiến tôi mất hàng giờ để tìm: Nó không đóng tệp xử lý ẩn đúng cách (vì nó hoàn toàn không gọi close).

Ví dụ: làm điều đó:

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

kết quả trong:

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

Điều kỳ lạ là, bộ đếm dòng $.được tăng cho mỗi tệp một. Nó không được thiết lập lại và nó không chứa số lượng dòng. Và nó không được đặt lại về 0 khi mở tệp khác cho đến khi ít nhất một dòng được đọc. Trong trường hợp của tôi, tôi đã làm một cái gì đó như thế này:

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

Do vấn đề này, điều kiện là sai vì bộ đếm dòng không được đặt lại đúng. Tôi không biết đây là lỗi hay đơn giản là mã sai ... Ngoài ra, việc gọi close;oder close STDIN;không giúp ích gì.

Tôi đã thay thế mã không thể đọc được này bằng cách sử dụng mở, nối chuỗi và đóng. Tuy nhiên, giải pháp được đăng bởi Brad Gilbert cũng hoạt động vì nó sử dụng xử lý tệp rõ ràng thay thế.

Ba dòng ở đầu có thể được thay thế bằng:

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

mà đóng đúng cách xử lý tập tin.


2

Sử dụng

 $/ = undef;

trước $document = <FILE>;. $/dấu phân tách bản ghi đầu vào , là một dòng mới theo mặc định. Bằng cách xác định lại nó undef, bạn đang nói rằng không có dấu tách trường. Đây được gọi là chế độ "nhếch nhác".

Các giải pháp khác như undef $/local $/(nhưng không my $/) redeclare $ / và do đó tạo ra hiệu ứng tương tự.


0

Bạn chỉ có thể tạo một thói quen phụ:

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

0

Tôi không biết nếu nó thực hành tốt, nhưng tôi đã từng sử dụng điều này:

($a=<F>);

-1

Đây là tất cả các câu trả lời tốt. NHƯNG nếu bạn cảm thấy lười biếng và tập tin không lớn và bảo mật không phải là vấn đề (bạn biết rằng bạn không có tên tệp bị nhiễm độc), thì bạn có thể bỏ qua:

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

-2

Bạn có thể sử dụng mèo trong Linux:

@file1=\`cat /etc/file.txt\`;
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.