在Perl中,如何将整个文件读取为字符串?


118

我正在尝试打开.html文件作为一个大的长字符串。这是我得到的:

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

结果是:

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

但是,我希望结果看起来像:

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

这样,我可以更轻松地搜索整个文档。


8
确实应该检查“ Cant安装”的定义是什么,它是一个常见的问题,并且它通常是不需要提出的参数。stackoverflow.com/questions/755168/perl-myths/...
肯特弗雷德里克

1
除了脚本本身,我实际上无法在运行该脚本的整个服务器上进行任何修改。
goddamnyouryan

因此,您不允许在服务器上的任何位置添加任何文件吗?
布莱德·吉尔伯特

FatPack模块插入您的脚本?另外,您似乎也考虑过不使用正则表达式解析HTML。
MkV

Answers:


81

加:

 local $/;

从文件句柄读取之前。请参阅如何一次读取整个文件?, 要么

$ perldoc -q“整个文件”

请参阅相关的文件句柄的变量perldoc perlvarperldoc -f local

顺便说一句,如果您可以将脚本放在服务器上,则可以拥有所需的所有模块。请参阅如何保留自己的模块/库目录?

另外,Path :: Class :: File允许您喷吐

路径::微小给出更加方便的方法,例如slurpslurp_rawslurp_utf8以及他们的spew同行。


33
您可能应该解释将$ /本地化的作用以及其作用。
丹尼(Danny)

12
如果您不打算解释有关本地化的任何信息$/,则可能应添加链接以获取更多信息。
布莱德·吉尔伯特

7
一个很好的逐步说明,它在做什么:{local $ /; 此处提供了<$ fh>}:perlmonks.org/?node_id=287647
dawez 2012年

也许只是说出为什么必须使用local而不是my
Geremia

@Geremia有关范围界定的讨论不在此答案的范围内。
SinanÜnür16年

99

我会这样做:

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

注意open的三个参数版本的使用。它比旧的两个(或一个)参数版本安全得多。还要注意词汇文件句柄的使用。由于许多原因,词汇文件句柄比旧的裸字变体更好。我们在这里利用其中一个优势:它们超出范围时会关闭。


9
这可能是最好的非覆盖方法,因为它同时使用了3参数,并且将INPUT_RECORD_SEPARATOR($ /)变量本地化为所需的最小上下文。
Danny

77

使用File :: Slurp

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

是的,即使您可以使用CPAN


OP表示他无法修改服务器上的任何内容。在大多数情况下,此处的“是,甚至您都可以使用CPAN”链接显示了如何解决该限制。
特伦顿

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

2
@Dmitry —因此安装模块。我从此答案链接到metacpan页面上有一个安装说明链接。
昆汀

52

所有的职位都是非惯用语。这个成语是:

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

通常,不需要将$ /设置为undef


3
local $foo = undef只是Perl最佳实践(PBP)建议的方法。如果我们要发布代码片段,我认为尽我们所能将其弄清楚将是一件好事。
丹尼(Danny)

2
向人们展示如何编写非惯用的代码是一件好事吗?如果我在正在处理的代码中看到“ local $ / = undef”,那么我的第一个动作就是在irc上公开羞辱作者。(而且我通常对“样式”问题不挑剔。)
jrockway,2009年

1
好吧,我咬一口:“ local $ / = undef”到底有什么值得模拟的?如果您唯一的回答是“这不是惯用语言”,那么(a)我不太确定,(b)那么呢?我不太确定,因为这样做真是太该死了。那又是什么,因为它非常清楚且相当简短。您可能会对自己认为的样式问题更挑剔。
Telemachus

1
关键是“本地$ /”是众所周知的成语的一部分。如果您正在编写一些随机代码并编写“ local $ Foo :: Bar = undef;”,那很好。但是在这种非常特殊的情况下,即使“不太清楚”(我不同意;“本地”的行为在这方面也已明确定义),您也可能会和其他人说相同的语言。
jrockway

11
抱歉,不同意。当您想更改魔术变量的实际行为时,显式表示更为常见。这是一个意向声明。即使文档使用“本地$ / =民主基金”(见perldoc.perl.org/perlsub.html#Temporary-Values-via-local()
莱昂纳多·埃雷拉

19

来自perlfaq5:如何一次读取整个文件?


您可以使用File :: Slurp模块一步完成。

use File::Slurp;

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

处理文件中所有行的常用Perl方法是一次处理一行:

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

这比将整个文件作为一行行读取到内存中,然后一次处理一个元素要有效得多,这通常是(即使并非总是如此)错误的方法。每当您看到有人这样做时:

@lines = <INPUT>;

您应该认真思考为什么需要一次加载所有内容。这不是一个可扩展的解决方案。您可能还会发现使用标准的Tie :: File模块或DB_File模块的$ DB_RECNO绑定更有趣,这允许您将数组绑定到文件,以便访问数组中的元素实际上访问文件中的相应行。 。

您可以将整个文件句柄内容读入标量。

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

这会暂时取消记录分隔符的定义,并会在块退出时自动关闭文件。如果文件已经打开,请使用以下命令:

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

对于普通文件,您还可以使用读取功能。

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

第三个参数测试INPUT文件句柄上数据的字节大小,并将那么多字节读入缓冲区$ var。


7

一种简单的方法是:

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

另一种方法是更改​​输入记录分隔符“ $ /”。您可以在本地以裸露的方式进行操作,以避免更改全局记录分隔符。

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

1
您给出的两个示例都存在很多问题。主要问题是它们是用古老的Perl编写的,我建议阅读Modern Perl
Brad Gilbert

@布拉德,评论是在多年前提出的,但是问题仍然存在。更好的是{local $/; open(my $f, '<', 'filename'); $d = <$f>;}
Joel Berger

@Joel只会稍微好一点。您没有检查open或隐式调用的输出closemy $d = do{ local $/; open(my $f, '<', 'filename') or die $!; my $tmp = <$f>; close $f or die $!; $tmp}。(这仍然存在未指定输入编码的问题。)
Brad Gilbert

use autodie,我要显示的主要改进是词法文件句柄和3 arg open。您是否因为某些原因而do这么做?为什么不只是将文件转储到在块之前声明的变量中?
乔尔·伯杰

7

设置$/undef(请参阅jrockway的答案),或者只是连接文件的所有行:

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

建议在支持它的任何Perl版本上对文件句柄使用标量。


4

另一种可能的方式:

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

3

您仅从Diamond运算符获得第一行,<FILE>因为您正在标量上下文中对其进行评估:

$document = <FILE>; 

在列表/数组上下文中,菱形运算符将返回文件的所有行。

@lines = <FILE>;
print @lines;

1
只是关于术语的注释:太空飞船的运营商是<=><>而钻石运营商是。
toolic 2010年

哦,谢谢,我以前从没听过“钻石操作员”,并以为他们两个人名字相同。我会在上面更正它。
内森2010年

2

我将以最简单的方式进行操作,因此即使有更聪明的方法,任何人都可以理解会发生什么:

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

所有这些字符串连接将非常昂贵。我会避免这样做。为什么只拆开数据以将其放回原处?
andru

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

<f>-从文件中返回一个行数组(如果$/具有默认值"\n"),然后join ''将其插入其中。


2

这更多是关于如何这样做的建议。我只是在一个相当大的Perl应用程序中发现错误的时间很短。大多数模块都有自己的配置文件。要整体读取配置文件,我在Internet上的某处找到了Perl的这一行:

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

如前所述,它重新分配了行分隔符。但是它也重新分配了STDIN。

这至少有一个副作用,这使我花了几个小时才能找到:它无法正确关闭隐式文件句柄(因为它根本没有调用close)。

例如,这样做:

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

结果是:

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

奇怪的是,$.每个文件的行计数器都增加一个。它不会重置,并且不包含行数。并且在打开另一个文件之前,它不会重置为零,直到读取至少一行为止。就我而言,我正在做这样的事情:

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

由于此问题,该条件为假,因为未正确重置行计数器。我不知道这是错误还是简单的错误代码...调用close;oder close STDIN;也无济于事。

我使用打开,字符串连接和关闭替换了此不可读的代码。但是,Brad Gilbert发布​​的解决方案也可以使用,因为它使用显式文件句柄代替。

开头的三行可以替换为:

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

可以正确关闭文件句柄。


2

 $/ = undef;

之前$document = <FILE>;$/输入记录分隔符,默认情况下是换行符。通过将其重新定义为undef,您表示没有字段分隔符。这称为“灌浆”模式。

其他解决方案(例如undef $/和)local $/(但不是my $/)重新声明$ /,从而产生相同的效果。


0

您可以简单地创建一个子例程:

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

0

我不知道这是否是一个好习惯,但是我曾经使用过:

($a=<F>);

-1

这些都是很好的答案。但是,如果您感到懒惰,并且文件不是那么大,并且安全性不是问题(您知道文件名没有污点),则可以使用以下方法:

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

-2

您可以在Linux中使用cat:

@file1=\`cat /etc/file.txt\`;
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.