很多Perl程序是用来处理文本文件(如配置文件或者日志文件),所以为了让我们的知识可以用起来,一开始就学习文件处理是很重要的。

首先来看一下怎么写文件。

在写之前,你需要打开文件,让操作系统(Windows,Linux,OSX等)给你的程序开启一个与文件“对话”的通道。为了这样做,Perl提供了一个语法有些奇怪的open函数。

use strict;
use warnings;

my $filename = 'report.txt';
open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
print $fh "My first report generated by perl\n";
close $fh;
print "done\n";

这是个好例子,我们在后面会用到它,而现在先来看一个更简单的例子:

简单示例

use strict;
use warnings;

open(my $fh, '>', 'report.txt');
print $fh "My first report generated by perl\n";
close $fh;
print "done\n";

简单解释一下。open 函数可以传入3个参数。

第一个,$fh,是个在 open() 调用中定义的标量。我们也可以在函数调用之前就定义它,不过,即便开始的时候你觉得别扭,选择在调用过程中定义这个变量会让代码更清晰一些。第二个参数定义了文件的打开方式。本例中的大于号(>)表示打开文件是为了后面的写操作。第三个参数是文件路径。

当调用这个函数的时候,它在$fh变量中设定了一个特殊的记号,称为文件句柄。我们并不关心这个变量的内容,仅仅在后面使用它。需要记住的是:文件的内容仍然在磁盘上,并不 在$fh变量中。

一旦打开了文件我们就可以在perl()的语句中使用文件句柄$fh。它看起来跟教程其它部分的print()很像,但是这里第一个参数是文件句柄,并且后面没有(!)逗号。

上面的print()会在文件里写入文本。

后一行关闭了文件句柄。严格来说这在Perl并不是必须的。Perl会在该变量超出作用域的时候自动并且适当的关闭所有的文件句柄。(至少在脚本的最后是这么做的) 无论如何, 显式地关闭文件是一种很好的实践习惯。

最后一行print "done\n"是为了让下个例子更加清晰:

错误处理

我们可以再举上边的例子,但是这次使用一个不存在的文件路径。例如:

open(my $fh, '>', 'some_strange_name/report.txt');

如果现在执行脚本,你会看到这样的错误信息:

print() on closed file-handle $fh at ...
done

事实上,这只是一个警告; 脚本会继续执行,这也是我们在屏幕上看到"done"被打印出来的原因。

进一步来说,只有在显式地使用use warnings语句的时候才会有警告信息。 你可以尝试一下注释掉use warnings,来验证:创建文件失败的时候,脚本现在是保持安静的。(不会提示警告信息) 这样的话, 在用户或者老板抱怨之前你都不会注意到它。

不过这仍是一个问题:我们尝试着去打开文件,尽管失败了,但是仍然想要往里print()数据。

我们最好在进一步处理之前先检查一下open()是否成功。

幸运的是,open() 的函数调用本身会返回 TRUE(成功), FALSE(失败) , 所以我们可以这么写:

Open or die

open(my $fh, '>', 'some_strange_name/report.txt') or die;

这是标准的open or die习语。 在Perl中很常见。

die 是一个函数调用,它会抛出一个异常并停止执行脚本。

"open or die" 是一个逻辑表达式。正如你从教程的前半部分看到的那样,"or" 在Perl中是短路执行的。这意味着,如果左半部分是TRUE,我们就能知道整个表达式是TRUE,式子右半部分是不执行的。相反,如果左半部分是FALSE,那么右半部分会执行,而且它的结果就是整个表达式的结果。

在本例中,我们使用短路特性来写表达式。

如果open()成功执行, 返回TRUE, 那么右边部分不会执行。 脚本会到下一行。

如果open()执行失败, 返回FALSE。 那么or右边部分会执行。 它会抛出异常并退出脚本。

在上边的例子中我们并不关心逻辑表达式实际的返回值, 只是使用它的"副作用"。

如果你尝试执行上面更改的脚本,会得到错误信息:

Died at ...

并不会打印"done"。

更好的错误报告

除了不加参数的调用die以外,我们也可以附加一些说明信息。

open(my $fh, '>', 'some_strange_name/report.txt')
  or die "Could not open file 'some_strange_name/report.txt'";

将会输出

  Could not open file 'some_strange_name/report.txt' ...

这会好一些,但是如果有人会尝试把文件的路径修改正确 ...

open(my $fh, '>', 'correct_directory_with_typo/report.txt')
  or die "Could not open file 'some_strange_name/report.txt'";

...而你得到的仍然还是原来的错误信息。这是因为他们只是在open()调用中进行了修改,却忘了改传给die的参数。

用一个变量来代替文件名,可能会更好点:

my $filename = 'correct_directory_with_typo/report.txt';
open(my $fh, '>', $filename) or die "Could not open file '$filename'";

现在我们能获得正确的错误信息,不过仍然不知道为什么会失败。为了进一步探究,我们可以使用Perl的内置变量$!来打印出操作系统的错误提示:

my $filename = 'correct_directory_with_typo/report.txt';
open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";

这样会输出

Could not open file 'some_strange_name/report.txt' No such file or directory ...

好多了吧。

现在我们可以回到最初的例子。

大于?

在调用open时使用的大于号可能有点不太清晰,但是如果你熟悉命令行的重定向的话,这应该对你不成问题。不然,可以把它想象成指示数据流方向(流向右侧的文件)的箭头。

非拉丁字符?

在需要处理非ASCII字符的情况下, 你可能想要使用UTF-8来存储它们。这样的话,你需要告诉Perl:你正在使用UTF-8编码打开文件。

open(my $fh, '>:encoding(UTF-8)', $filename)
  or die "Could not open file '$filename'";