Perl 中的简单文件读取
Posted
技术标签:
【中文标题】Perl 中的简单文件读取【英文标题】:SIMPLE file reading in Perl 【发布时间】:2011-02-18 02:29:07 【问题描述】:Perl 如何读入文件,它如何告诉它前进到文本文件的下一行,以及它如何使它读取 .txt 文件中的所有行,直到,例如,它到达 item “banana” “?
【问题讨论】:
【参考方案1】:读取文件基本上有两种方式:
-
读取文件意味着一次读取所有文件。这会占用大量内存并需要一段时间,但之后整个文件内容都在内存中,您可以使用它做任何您想做的事情。
如果您不想读取整个文件(例如,当您到达“香蕉”时停止),则最好逐行读取文件(在 while 循环中)。
对于这两种方式,您都需要使用“open”命令创建 FILEHANDLE,如下所示:
open(my $yourhandle, '<', 'path/to/file.txt') # always use a variable here containing filename
or die "Unable to open file, $!";
然后您可以通过将文件放入数组来 slurp 文件:
my @entire_file=<$yourhandle>; # Slurp!
或使用 while 循环逐个读取文件
while (<$yourhandle>) # Read the file line per line (or otherwise, it's configurable).
print "The line is now in the $_ variable";
last if $_ eq 'banana'; # Leave the while-loop.
之后,不要忘记关闭文件。
close($yourhandle)
or warn "Unable to close the file handle: $!";
这只是基础知识.. 与文件有很多关系,尤其是在异常处理中(当文件不存在、不可读、正在写入时该怎么办),所以你必须阅读或者问一下:)
【讨论】:
不要将它用于生产代码——这只是为了开始学习。一定要到处检查异常情况! Konerak,你能开始使用和教授词法文件句柄吗? 这是一篇很好的文章,但它有几个严重的问题。请不要鼓励使用包全局文件句柄——使用词法句柄是减少错误的重要做法。您的另一个错误是open
和close
在失败时不会生成异常(除非您使用autodie
),它们只会返回false。混淆使用重要的技术词汇对新手非常有害。
从好的方面来说,你对“啜饮”给出了特别清晰的解释,并清楚地描述了实践中的问题。
使用词法文件句柄现在需要成为标准做法,尤其是在与新手谈论系统时。我已经编辑了您的问题以反映现代实践,因为它被接受为答案。如果您对此感觉非常强烈,请继续并恢复它,但请考虑保留它。在没有充分理由的情况下教人们 10 多年前的做事方式实际上是对 Perl 社区的普遍伤害。【参考方案2】:
René 和 Konerak 写了几个很好的回复,展示了如何打开和读取文件。不幸的是,他们在推广最佳实践方面存在一些问题。因此,我会迟到并尝试添加对最佳实践方法的清晰解释以及为什么使用最佳实践方法更好。
什么是文件句柄?
文件句柄是我们用来代表文件本身的名称。当您想要对文件进行操作(读取、写入、移动等)时,请使用文件句柄来指示要操作的文件。文件句柄与文件名或路径不同。
可变范围和文件句柄
变量的作用域决定了在程序的哪些部分可以看到该变量。一般来说,将每个变量的作用域保持在尽可能小的范围内是个好主意,这样复杂程序的不同部分就不会相互破坏。
在 Perl 中严格控制变量范围的最简单方法是使其成为词法变量。词法变量仅在声明它们的块内可见。使用my
声明一个词法变量:my $foo;
# Can't see $foo here
my $foo = 7;
print $foo;
# Can't see $foo here
Perl 文件句柄可以是全局的或词法的。当您将 open 与一个裸词(不带引号或符号的文字字符串)一起使用时,您将创建一个全局句柄。当你打开一个未定义的词法标量时,你创建了一个词法句柄。
open FOO, $file; # Global file handle
open my $foo, $file; # Lexical file handle
# Another way to get a lexical handle:
my $foo;
open $foo, $file;
全局文件句柄的一个大问题是它们在程序的任何地方都是可见的。所以如果我在子例程中创建了一个名为 FOO 的文件句柄,我必须非常小心,以确保我不会在另一个例程中使用相同的名称,或者如果我使用相同的名称,我必须绝对确定在任何情况下都不能它们相互冲突。简单的替代方法是使用不能有相同类型名称冲突的词法句柄。
词法句柄的另一个好处是很容易将它们作为子例程参数传递。
open
函数
open
函数具有各种功能。它可以运行子进程、读取文件,甚至提供标量内容的句柄。您可以为其提供许多不同类型的参数列表。它非常强大和灵活,但这些功能带有一些陷阱(执行子流程不是您想偶然做的事情)。
对于打开文件的简单情况,最好始终使用 3 参数形式,因为它可以防止意外激活所有这些特殊功能:
open FILEHANDLE, MODE, FILEPATH
FILEHANDLE
是要打开的文件句柄。
MODE
是如何打开文件,>
是覆盖,'>>for write in append mode,
+>for read and write, and
FILEPATH
是要打开的文件的路径。
成功时,open
返回一个真值。失败时,$!
设置为指示错误,并返回 false 值。
因此,要使用 3 参数 open
创建一个词法文件句柄,我们可以使用它来读取文件:
open my $fh, '<', $file_path;
逻辑返回值便于检查错误:
open my $fh, '<', $file_path
or die "Error opening $file_path - $!\n";
我喜欢将错误处理放到一个新行并缩进它,但这是个人风格。
关闭句柄
当您使用全局句柄时,务必小心谨慎地在使用完每个句柄后显式关闭它。不这样做可能会导致奇怪的错误和可维护性问题。
close FOO;
当变量被销毁时词法句柄会自动关闭(当引用计数下降到 0 时,通常是当变量超出范围时)。
在使用词法句柄时,通常依赖于句柄的隐式关闭而不是显式关闭它们。
钻石是 Perl 最好的朋友。
菱形运算符<>
允许我们遍历文件句柄。就像open
它有超能力。我们现在将忽略其中的大部分。 (搜索输入记录分隔符、输出记录分隔符和 NULL 文件句柄的信息以了解它们。)
重要的是,在标量上下文中(例如,分配给标量)它的行为类似于readline
函数。在列表上下文中(例如分配给数组),它的作用类似于 read_all_lines
函数。
假设您要读取包含三个标题行(日期、时间和位置)和一堆数据行的数据文件:
open my $fh, '<', $file_path
or die "Ugh - $!\n";
my $date = <$fh>;
my $time = <$fh>;
my $loc = <$fh>;
my @data = <$fh>;
听到人们谈论啜食文件是很常见的。这意味着一次将整个文件读入一个变量。
# Slurp into array
my @slurp = <$fh>;
# Slurp into a scalar - uses tricks outside the scope of this answer
my $slurp;
local $/ = undef; $slurp = <$fh>;
把它们放在一起
open my $fh, '<', 'my_file'
or die "Error opening file - $!\n";
my @before_banana;
while( my $line = <$fh> )
last if $line =~ /^banana$/;
push @before_banana, $line;
将所有内容放在一起 - 特别额外信用版
my $fh = get_handle( 'my_file' );
my @banana = read_until( $fh, qr/^banana$/ ); # Get the lines before banana
read_until( $fh, qr/^no banana$/ ); # Skip some lines
my @potato = read_until( $fh, qr/^potato$/ ); # Get the lines before potato
sub get_handle
my $file_path = shift;
open my $fh, '<', $file_path
or die "Can't open '$file_path' for reading - $!\n";
return $fh;
sub read_until
my $fh = shift;
my $match = shift;
my @lines;
while( my $line = <$fh> )
last if $line =~ /$match/;
push @line, $line;
return @lines;
为什么会有这么多不同的方式?为什么会有这么多陷阱?
Perl 是一门古老的语言;它的包袱可以追溯到 1987 年。多年来,人们发现了各种设计问题并进行了修复——但很少允许修复损害向后兼容性。
此外,Perl 旨在让您灵活地在您想要的时候做您想做的事。这是非常宽容的。这样做的好处是你可以深入到阴暗的深处,做一些非常酷的魔法事情。坏事是,如果你忘记缓和自己的热情,没有专注于编写可读的代码,很容易自取其辱。
仅仅因为你有足够多的绳子,并不意味着你必须上吊。
【讨论】:
哇,这太棒了。感谢您如此深入的回复,我真的很感激。这有助于我理解这个过程。 @daotoad:很好的答案!! 进程退出时全局句柄是否关闭(如果没有别的,操作系统有效)?或者,对于用于写入且未在 Perl 脚本中关闭的全局文件句柄,数据实际上是否会丢失? 关于 “Diamonds are a Perl's best friend” 小节:我认为应该提到迭代“二进制”文件的文件句柄是否有意义(比如说,一些不是文本文件的可执行文件)。这个问题可能暗示了这一点,但我认为明确表达并不会有什么坏处。特别是,可以在二进制文件上使用 slurping(其中行没有意义。或者将$/
设置为空、未定义或类似的工作)?
@PeterMortensen,全局句柄将在脚本结束时被销毁,然后应该刷新到磁盘。但是,如果您在干净关闭之前编写崩溃代码,缓冲的数据将会丢失。对于二进制文件,输入记录分隔符 ($/
) 可以设置为任何值。因此可以使用任何表示记录结尾的字节序列。例如,您可以使用0x210xF9
将动画 GIF 拆分为帧(不完美,序列可能出现在图像数据中)。将$/
设置为undef
也适用于二进制文件。【参考方案3】:
首先,你必须打开文件:
open (my $SOME_FILEHANDLE, "<", "filename.txt");
您可能想检查文件打开是否成功:
open (my $SOME_FILEHANDLE, "<", "filename.txt") or die "could not open filename";
打开文件后,您可以从 $SOME_FILEHANDLE 中读取每一行。下一行是 <$SOME_FILEHANDLE>
构造:
my $next_line = <$SOME_FILEHANDLE>;
$next_line
在读取最后一行后未定义。所以,你可以把整个事情放到一个while
循环中:
while (my $next_line = <$SOME_FILEHANDLE>)
do_something($next_line);
这是有效的,因为未定义的值在 while 条件下计算为 false
。
如果你想在遇到“banana”时退出循环,你可能会使用正则表达式来检查香蕉:
while (my $next_line = <$SOME_FILEHANDLE>)
last if $next_line =~ /banana/;
do_something($next_line);
last
运算符退出 while 循环,并在 $next_line
匹配香蕉时“触发”。
【讨论】:
然后将此反对票视为对过时做法的劝阻。 为什么不能使用词法句柄?你有充分的理由吗? 第二次投反对票。教词法文件句柄不再是一种选择。 glob 文件句柄语法已经过时了 10 多年,充满了危险,需要被压扁。如果有更改,我很乐意删除反对票。 我同意应该鼓励教授词汇文件句柄,我有罪,因为我在回答中没有这样做。但是关于自己开始使用它们我不能同意,因为我有很多 Perl 脚本可以工作,因为我需要它们长时间工作,而且它们都使用 old 非-lexical 文件句柄,我不明白为什么要更改我的脚本,尤其是因为没有其他人看到它们并且它们工作正常。 没有理由更改现有的工作脚本或程序。 可能修改旧脚本是有意义的,因为它们需要其他维护。我很难理解为什么您不想在新脚本中使用更好、更安全的方法。你的文章在其他方面非常好,所以+1 -1 = 0,所以我不要投反对票,改变它,我会很高兴地投赞成票。以上是关于Perl 中的简单文件读取的主要内容,如果未能解决你的问题,请参考以下文章