在Perl中打开和读取文件的最佳方法是什么?

46
请注意 - 我并不是在寻找打开/读取文件的“正确”方法,或者每次都应该打开/读取文件的方式。我只是想了解大多数人使用的方式,并可能同时学习一些新方法 :) *
在我的Perl程序中,一个非常常见的代码块是打开一个文件并读取或写入它。我看到过很多种做法,而我在执行此任务时的风格也在这几年中多次改变。我只是想知道最好(如果有最好的方法)的方法是什么?
我曾经像这样打开一个文件:
my $input_file = "/path/to/my/file";
open INPUT_FILE, "<$input_file"  || die "Can't open $input_file: $!\n";

但是我认为这会出现错误捕获的问题。

添加括号似乎可以解决错误捕获:

open (INPUT_FILE, "<$input_file")  || die "Can't open $input_file: $!\n";

我知道你也可以将文件句柄分配给一个变量,所以不必像上面那样使用“INPUT_FILE”,而是可以使用$input_filehandle - 这种方式更好吗?

如果要读取一个小文件,使用globbing有什么问题吗?像这样:

my @array = <INPUT_FILE>;

或者

my $file_contents = join( "\n", <INPUT_FILE> );

或者你应该总是像这样循环遍历:

,还是应该使用其他方法?

my @array;
while (<INPUT_FILE>) {
  push(@array, $_);
}

我知道在Perl中有很多方法来完成任务,我只是想知道打开和读取文件的首选/标准方法是否存在?


perlopentut页面非常详细地介绍了大部分相关问题。 - converter42
12个回答

59

没有通用标准,但有理由偏好某个格式。我的首选形式是:

open( my $input_fh, "<", $input_file ) || die "Can't open $input_file: $!";

原因如下:
  • 您可以立即报告错误。(如果需要,将 "die" 替换为 "warn")
  • 现在您的文件句柄已经被引用计数,所以一旦您不再使用它,它将自动关闭。 如果您使用全局名称 INPUT_FILEHANDLE,则必须手动关闭文件,否则它将保持打开直到程序退出。
  • 读取模式指示符 "<" 与 $input_file 分开,增加了可读性。

如果文件很小并且您知道要获取所有行,那么以下方法非常适用:

my @lines = <$input_fh>;

如果您需要将所有行作为单个字符串处理,也可以这样做:

my $text = join('', <$input_fh>);

对于较长的文件,您可以使用while迭代行,或使用read方法。


打开我的 $input_fh,'<', $input_file 或者死亡 "无法打开 $input_file: $!" - draegtun
2
我仍然认为这是样板文件。只需使用File::SlurpTie::File即可。 - Svante
5
还要考虑使用 use autodie;,它将使您的 IO 操作默认为致命错误。比到处编写 "or die" 更加简便。 - rjh
1
以下是更多关于这个好处的原因: 1)文件句柄在词法作用域中,而不是包(全局)中,因此其他代码不太可能意外使用它。 2)您可以轻松地将文件句柄传递给子例程,而无需处理类型球。 3)分离读取模式指示符“<”不仅仅是为了可读性;如果文件名以“>”字符开头,则可以防止出现不良影响。 - Lqueryvg
我使用完后关闭它可以吗?如果我调用 close($input_fh) 会发生什么? - Agostino

15
如果您想将整个文件作为一个字符串处理,就无需遍历它。
use strict;
use warnings;
use Carp;
use English qw( -no_match_vars );
my $data = q{};
{
   local $RS = undef; # This makes it just read the whole thing,
   my $fh;
   croak "Can't open $input_file: $!\n" if not open $fh, '<', $input_file;
   $data = <$fh>;
   croak 'Some Error During Close :/ ' if not close $fh;
}

上述内容满足perlcritic --brutal的要求,这是一种测试“最佳实践”的好方法。在此处仍未定义$input_file,但其余部分都符合规范。

local $RS = undef; 是什么意思? - Nathan Garabedian
2
$RS$/相同,这是由English为您设置的。$/是跟踪<$fh>行分隔符值的变量,它与get-line概念或$fh->getline()同义。本质上,它包含内部读取算法用于了解何时已读取完整数据line的值,并将其设置为undef表示“没有标记表示完整行”,因此它会将整个文件作为“line”读入。 - Kent Fredric

14

我不喜欢在每个地方都写"or die",这让我感到很烦。我更喜欢用以下方式打开文件:

use autodie;

open(my $image_fh, '<', $filename);

虽然只需输入很少的内容,但有很多重要的事情需要注意:

  • 我们正在使用autodie编译器指示,这意味着如果出现问题,Perl的所有内置函数都会抛出异常。它消除了在代码中编写or die ...的需要,生成友好、易读的错误消息,并具有词法作用域。它可以从CPAN获得。

  • 我们正在使用open的三个参数版本。这意味着即使我们有一个包含字符如<>|的奇怪文件名,Perl仍然会做正确的事情。在我在OSCON的Perl Security教程中,我展示了许多让2个参数的open行为不当的方法。本教程的笔记可从Perl Training Australia免费下载

  • 我们正在使用标量文件句柄。这意味着我们不会偶然关闭其他同名的文件句柄,如果我们使用包文件句柄,这种情况可能会发生。它还意味着strict可以检测到拼写错误,并且如果文件句柄超出范围,它将自动清理。

  • 我们正在使用一个有意义的文件句柄。在这种情况下,看起来我们要写入一个图像。

  • 文件句柄以_fh结尾。如果我们看到自己像使用常规标量一样使用它,那么我们知道这可能是一个错误。


非常有见地,谢谢!我也从未见过“打开”方法的三个参数 - 我想我喜欢这种方式!谢谢! - BrianH

11
如果您的文件大小足够小,可以将整个文件读入内存中,则使用File::Slurp。它使用非常简单的API读取和写入完整的文件,而且还会执行所有的错误检查,因此您不必自己处理。

1
File::Slurp很棒,但它比Kent Fredric的直接读取慢得多。(每个nytprof中的4000个10-30k文件在7秒内直接读取,而在56秒内被吞掉) - Bill Ruppert

6
没有最好的打开和读取文件的方法,这是错误的问题。文件里有什么?你需要在任何时候需要多少数据?你需要一次性获取所有数据吗?你需要对数据做什么?在考虑如何打开和读取文件之前,你需要弄清楚这些问题。
你现在做的事情是否会导致问题?如果没有,难道你没有更好的问题需要解决吗? :)
大部分问题只是语法问题,这些问题都可以在Perl文档中找到答案(特别是perlopentut)。你可能还想学习Learning Perl,它可以回答你在问题中遇到的大部分问题。
祝你好运,:)

也许我不应该问什么是打开/读取文件的最佳方式,而应该问大多数人都是怎么做的。我已经编写了数百个Perl程序来打开文件,只是想确保我正在以正确的方式进行操作。我没有遇到任何问题 - 我只是好奇其他人是如何处理的。谢谢! - BrianH
再次阅读第一段。最好的方法取决于你正在做什么。 - brian d foy
我并不是说 Perl::Critic 就是铁律,但是在《Learning Perl》中打开文件的许多方式都无法通过 Perl::Critic 的检查。事实上,我以前一直使用的打开文件方式就是我从《Learning Perl》中学到的方法。我认为,大多数情况下都可以应用最佳实践来打开文件,并且你不需要知道微小的细节——否则我会问:“打开二进制文件并计算字节数的最佳方法是什么?”之类的问题。99% 的文件我打开都是普通文本文件,我只是想将其读入数组中。我很有兴趣了解最佳实践。 - BrianH
你一定有一本旧版的《学习Perl》。 - brian d foy
你不必费力寻找第五版,它是目前的版本。 - brian d foy
显示剩余2条评论

5

对于面向对象编程,我喜欢:

use FileHandle;
...
my $handle = FileHandle->new( "< $file_to_read" );
croak( "Could not open '$file_to_read'" ) unless $handle;
...
my $line1 = <$handle>;
my $line2 = $handle->getline;
my @lines = $handle->getlines;
$handle->close;

是的,它可以使用“迭代运算符”工作,但您也可以使用$handle->getline或$handle->getlines读取它。 - Axeman

5

确实有很多在Perl中打开文件的最佳方法,就像有很多其他编程语言一样。

$files_in_the_known_universe * $perl_programmers

虽然这并不重要,但是了解通常的做法还是很有趣的。我的首选 slurping(一次性读取整个文件) 的方式是:

use strict;
use warnings;

use IO::File;

my $file = shift @ARGV or die "what file?";

my $fh = IO::File->new( $file, '<' ) or die "$file: $!";
my $data = do { local $/; <$fh> };
$fh->close();

# If you didn't just run out of memory, you have:
printf "%d characters (possibly bytes)\n", length($data);

逐行查看代码时:

my $fh = IO::File->new( $file, '<' ) or die "$file: $!";
while ( my $line = <$fh> ) {
    print "Better than cat: $line";
}
$fh->close();

当然,读者需要注意:这些只是我在日常工作中记忆力所致的方法,它们可能完全不适合您要解决的问题。


5
我曾经使用过

the

这个
open (FILEIN, "<", $inputfile) or die "...";
my @FileContents = <FILEIN>;
close FILEIN;

现在,我经常使用File::Slurp来处理那些我想要完全保存在内存中的小文件,而对于那些我想要可扩展地访问和/或需要就地更改的大文件,我则使用Tie::File


3

使用单行代码将整个文件$file读入变量$text中。

$text = do {local(@ARGV, $/) = $file ; <>};

作为一个函数。
$text = load_file($file);
sub load_file {local(@ARGV, $/) = @_; <>}

2
如果这些程序只是为了提高您的生产力,那么任何方式都可以!尽可能多地构建错误处理。
如果文件很大,读取整个文件可能不是长期处理事务的最佳方式,因此您可能希望在逐行处理时进行处理,而不是将它们加载到数组中。
我从《程序员修炼之道》(Hunt & Thomas)的其中一章得到的一个提示是,在脚本开始切割和处理之前,您可能希望让脚本为您保存文件备份。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接