在Perl中,将文件读入字符串的最佳方法是什么?

55

是的,有多种方法可以做到这一点,但必须有一个规范的、最有效的、最简洁的方法。我会添加我所知道的答案,然后看看哪个能脱颖而出。

明确一下,问题是如何最好地将文件内容读入字符串中。 每个答案提供一种解决方案。

17个回答

77

如果您不希望文件不存在时程序终止,该怎么办? - dreeves
4
确保这种情况不太可能发生的最简单方法是先检查文件是否存在... - Leon Timmermans
1
这种方法的缺点是它不包含在开箱即用的Perl中,至少不包含在我的Windows版ActiveState Perl(v5.10.0)中。 - Kip
3
请注意,最近发现File::Slurp存在严重的安全问题:https://rt.cpan.org/Ticket/Display.html?id=83126。 - brian d foy
嗨,我得到了“Undefined subroutine &main::read_text”错误。应该使用“use File::Slurper 'read_text';”。https://metacpan.org/pod/File::Slurper - stenlytw
File::Slurp 不是一种可移植的解决方案。在Windows上,它会因所有非ANSI文件名而抛出错误。我没有测试过 Path::Tiny,但考虑到Perl对Windows的普遍鄙视程度,我敢打赌它也是一样的。可能可以使用 Win32::LongPath 打开一个文件,并传递文件句柄而不是文件路径。(这是Perl在所有文件输入/输出中真正应该支持的内容)。 - Freon Sandoz

48

我喜欢使用do块来做这件事,其中我将@ARGV本地化,以便我可以使用钻石操作符为我执行文件操作。

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

如果您需要更加健壮的功能,您可以轻松地将此转换为子程序。
如果您需要处理各种特殊情况并且需要真正强大的功能,请使用File::Slurp。即使您不打算使用它,也请查看源代码以了解它必须处理的所有奇怪情况。File::Slurp存在一个重大安全问题,目前尚无解决方案。部分原因是它未能正确处理编码。即使是我提供的快速答案也存在这个问题。如果您需要处理编码(可能是因为您未默认使用UTF-8),则可以扩展为:
my $contents = do {
    open my $fh, '<:encoding(UTF-8)', $file or die '...';
    local $/;
    <$fh>;
    };

如果您不需要更改文件,您可以使用File::Map


8
我很懒,写成了 my $contents = do {local (@ARGV,$/) = $file; <>};,这与原来的意思完全相同,但使用更少的字符 :) - ephemient
我在想为什么本地变量 @ARGV = $file; <> 会与 <$file> 有所不同。 - Powerlord
@Bemrose:因为$file不是文件句柄。 - brian d foy
1
我在给一个文件添加方法时,不小心搞砸了。这个文件后面已经使用了<>来从标准输入读取数据,但是我却期望它从文件中读取。<>的行为在第一次调用和后续调用时有所不同,而由于我改变了第一次调用的方式,也就改变了现有调用的行为(它原本期望的是<>的标准输入行为)。 - Adam Millerchip

35

在编写文件读取的操作时,使用File::Slurp是最佳方式。Uri Guttman对多种文件读取方式进行了深入研究,并记录了他的发现和总结,并将其纳入了File::Slurp。


4
请注意,最近发现File::Slurp存在严重的安全问题:https://rt.cpan.org/Ticket/Display.html?id=83126。 - brian d foy

24
open(my $f, '<', $filename) or die "OPENING $filename: $!\n";
$string = do { local($/); <$f> };
close($f);

11

需要考虑的事项(特别是与其他解决方案相比):

  1. 词法文件句柄
  2. 减少范围
  3. 减少魔法

因此,我得到:

my $contents = do {
  local $/;
  open my $fh, $filename or die "Can't open $filename: $!";
  <$fh>
};

我并不是魔术的粉丝,除非真正使用魔术。与其伪装,为什么不直接使用open调用呢?这并不需要更多工作,而且更加明确。(真正的魔术,特别是处理“-”时,要完美模拟则需要更多的工作,但我们在这里并没有使用它。)


3
而且,对于那些在家跟进的人来说,很明显,在花括号块的末尾,$fh超出了范围,文件句柄会自动关闭。 - dland

10

mmap(内存映射)字符串可以在以下情况下很有用:

  • 有很大的字符串,您不想将其加载到内存中
  • 希望进行盲目快速初始化(访问时会获得逐步I/O)
  • 对字符串进行随机或延迟访问。
  • 可能希望更新字符串,但仅扩展或替换字符:
#!/usr/bin/perl
use warnings; use strict;

use IO::File;
use Sys::Mmap;

sub sip {

    my $file_name = shift;
    my $fh;

    open ($fh, '+<', $file_name)
        or die "Unable to open $file_name: $!";

    my $str;

    mmap($str, 0, PROT_READ|PROT_WRITE, MAP_SHARED, $fh)
      or die "mmap failed: $!";

    return $str;
}

my $str = sip('/tmp/words');

print substr($str, 100,20);

更新:2012年5月

在将Sys::Mmap替换为File::Map后,以下内容应该基本等同。

#!/usr/bin/perl
use warnings; use strict;

use File::Map qw{map_file};

map_file(my $str => '/tmp/words', '+<');

print substr($str, 100, 20);

实际上,现在使用File::Map(免责声明:由我编写)会是更好的选择。它更加可移植(可以在Unix和Windows上工作),并且使用起来也更加简单(“map_file my $str, $file_name;”)。 - Leon Timmermans

8
use Path::Class;
file('/some/path')->slurp;

8
这并不快速,也不跨平台,实在是太邪恶了,但它很短(我曾经在Larry Wall的代码中看到过这个):
 my $contents = `cat $file`;

孩子们,不要在家里这样做 ;-).

7
{
  open F, $filename or die "Can't read $filename: $!";
  local $/;  # enable slurp mode, locally.
  $file = <F>;
  close F;
}

6
use IO::All;

# read into a string (scalar context)
$contents = io($filename)->slurp;

# read all lines an array (array context)
@lines = io($filename)->slurp;

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