Perl语言入门系列之三:文件输入与输出

2022-05-05 11:40:17 浏览数 (1)

在上一篇文章中我介绍了基本的输入与输出方法,通过键盘与屏幕实现用户与脚本的交互,但是为了完成更复杂的任务,输入与输出往往需要直接调用文件数据。

⑴<>操作符

<>操作符俗称钻石操作符,它是行标准输入<STDIN>的特例,不是从键盘获得数据而是从用户指定的位置读取数据,这里的用户指定位置指运行程序时命令行程序后面紧跟的参数,也即命令行参数。

与<STDIN>一样<>也是以行为单位来读取内容,当读取完文件最后一行之后,会返回undef。为了对读取情况进行判断,可以使用defined函数,正常读取时defined函数返回值为真,读取完最后一行之后得到undef时,defined函数返回值为假,具体示例如下:

代码语言:javascript复制
use 5.010;
while (defined($line = <>)) {
    chomp($line);
    say "It was "$line" that I saw!";
}

上面的脚本会逐行读取并打印命令行参数指示的文件,运行如下所示:

需要注意的是<>会处理所有的参数输入,在读取第一个文件也即text1.txt最后一行之后不会返回undef,会快速的跳到第二个文件,当读取完最后一个文件最后一行之后才会返回undef,从而使defined函数返回值为假跳出while循环,因此,在一个程序中钻石操作符只能出现一次

在上面程序中,由于即使这一行为空行、0,也会有换行符的存在,在布尔值上下文中仍为真,所以去掉defined函数仍是成立的while ($line = <>) {…}。

⑵参数数组

钻石操作符无法分开处理不同命令行参数指定的文件,perl编译器的命令行参数实际上是储存在事先建立的特殊数组@ARGV,这个数组的值就是由命令行参数组成的列表,可以像其他数组一样进行操作,我们可以通过调取这个数组的元素实现不同参数的选择处理,也可以在脚本中强制指定文件。事实上命令行参数不仅可以指定文件,还可以有选项参数(一般是以“-”开头),用来控制程序的执行。关于文件处理示例如下:

代码语言:javascript复制
use 5.010;
@argv = @ARGV;
@ARGV = shift @argv;
while (defined($line = <>)) {
    chomp($line);
    say "It was "$line" that I saw in the first file!";
}
@ARGV = shift @argv;
while (defined($line = <>)) {
    chomp($line);
    say "It was "$line" that I saw in the next file!";
}

运行结果如下所示:

可以发现,钻石操作符总是读取当前@ARGV里指定的文件,因此可以对这个数组进行操作使其读取不同文件的内容。在命令行参数里也可以利用通配符来同时指定多个文件进行文件批量处理。

⑶printf格式化输出

无论是print还是say操作符,只能输出特定的列表,而printf操作符借用自C语言,控制能力更强,可以输出格式化的字符串。printf操作符的包含两个参数,一是格式字符串,二是要输出的数据列表。格式字符串每个输出元素的格式以%开头,以字母或者换行符结尾,指定每个输出数据元素的格式(字符串、整数、浮点数、对齐方式等),中间空格隔开或者直接相连,内插在双引号里。常见的格式输出如下所示:

①输出浮点数:

-e表示在命令行直接运行Perl指令,将要运行的Perl代码放在后面单引号内;这里为了突出两部分参数将输出数据列表添加了括号,括号可以省略。

②输出整数并且右对齐:

③输出保留两位小数的百分数:

④输出左对齐的字符串:

需要注意的是,格式字符串不一定出现在printf最前面,也可以内插在字符串内部,只要其后紧跟要格式化的内容并且一一对应就行,如下代码:

代码语言:javascript复制
my @items = qw/ wilma dino pebbles /;
printf "The items are:n".("sn" x @items), @items;

上面printf语句中字符串不仅包含格式字符串,还包含"The itemsare:n"这一输出内容,运行结果如下所示:

⑷文件句柄

文件句柄(filehandle)就是程序里代表Perl进程与外界之间的输入输出(I/O)的名称,也即外界数据在Perl里面的代称,从而实现类似于Bash里面的数据流定向的功能,让Perl程序更加方便的处理并保存数据。Perl有保留的内置文件句柄名,具体如下所示:

代码语言:javascript复制
STDIN: 标准输入流(standard input stream),最基本的是键盘输入例如行输入操作符<STDIN>,也可以根据用户要求从文件输入或者经由管道(pipe)读取另一个程序的输出;
STDOUT: 标准输出流(standard output stream),最基本的是输出到屏幕例如print和say操作符,也可以根据用户要求输出到文件或另一个程序;
STDERR: 标准错误流(standard error stream),也即程序返回的报错信息,上述三个均为基于Unix的标准I/O流;
DATA: 指向的是当前文件中__DATA__之后的内容。
ARGV: 存储命令行参数的内置数组;
ARGVOUT: 当使用参数-i进行文件原位编辑(edit-in-place)时指向当前打开的文件。

除此之外,Perl允许用户根据需要创建自己的文件句柄,最好以全大写字母命名。

⑸自定义文件句柄

如若想根据需要自定义文件句柄,可以使用open操作符,其使用格式如下所示:

代码语言:javascript复制
open (FILE1, "<file1.txt");    #创建名为FILE1的句柄,并将file1.txt中的全部内容读取到FILE1
open FILE2, 'file2.txt';    #同理,这里省略了括号与<,单双引号均可以,如果内插变量,只能用双引号
open FILE3, '>file3.txt';    #创建名为FILE3的句柄,并将其中全部内容写入到file3.txt,若file3.txt存在,则覆盖原内容
open LOG, '>>log.file';    #创建名为LOG的句柄,并将其中全部内容以追加的方式写到log.file原有内容后面
open PIPE1,"process |";     #读管道进程标准输出的结果open PIPE2,"| to process";  #往管道进程中写数据
open FILE, '>', 'file.txt';    #Perl 5.6及更新版本允许的写法,这种写法的好处是可以添加特定编码
open FILE, '>:encoding(UTF-8)', 'file.txt';    #以二进制(UTF-8编码)写入

实际应用中,我们更常用的是从命令行参数或变量指定的文件名来读取或写入数据,如下所示:

代码语言:javascript复制
open (FILE1, "<$ARGV[0]");
open (OUT1, ">$ARGV[1]");
open (OUT2, "<$my_input");

在Perl 5.6及更高的版本中,可以直接把文件句柄放到标量变量里,如下所示:

代码语言:javascript复制
open my $rocks_fh, '<', 'rocks.txt';

如果某数据流任务已经完成,可以使用close操作符关闭相应的文件句柄,如下所示:

代码语言:javascript复制
close FILE;

⑹句柄错误信息

在Perl中可以使用die操作符处理程序遇到的致命错误,die函数会立刻中止程序运行,并输出指定的错误信息到标准错误流STDERR中(这与内置警告warn有本质区别,触发警告并不中断程序)。事实上,每个程序都会返回一个退出码,0代表成功,非0代表失败,其中1代表命令参数语法错误,2代表程序错误,3找不到配置文件,die的使用如下所示:

代码语言:javascript复制
if (! open FILE, '<file.txt') {
  die "Cannot find file.txt: $!";
}

由于非0为失败,这与布尔值相反,所以if语句中使用!取否,此外$!为Perl中存储系统内置错误信息的变量,运行上面代码,结果如下所示:

由于我们根本没输入file.txt这个文件,所以这里给出报错“No such file…”,这里die函数会给出发生错误的脚本名称及行号,如果在die的结尾加上换行符则不显示这些内容die "Cannot findfile.txt: $!n"。上述代码还可以直接简写为以下格式:

代码语言:javascript复制
open FILE, '<file.txt' or die "Cannot find file.txt: $!";

这是一个or判断语句,如果or之前为真则忽略后面语句,or之前为假则判断or之后是否为真,为真则执行后面的语句。在Perl 5.10及更高的版本,可以使用编译指令autodie,如果系统遇到致命错误则自动die并给出错误信息,下面代码与上面写法是等效的:

代码语言:javascript复制
use autodie;
open FILE, '<file.txt';

⑺使用文件句柄

一旦一个文件句柄被成功创建,便可以在Perl程序中使用,以读取模式打开的句柄会存入文件信息,可以使用钻石操作符来逐行读取内容,如下所示:

代码语言:javascript复制
$line = <FILE1>;  #读取FILE1内的一行内容

以写入或者添加模式打开的句柄,可以使用print、say、printf操作符来写入内容,如下所示:

代码语言:javascript复制
print FILE2 "It's a practicen";
say FILE2 "It's a practice";
printf FILE3 "%-10sn%-10sn", "Fred", "Barney";

注意在print中句柄之后不能加逗号,之所以这样,是因为当句柄储存在标量变量里时,print可以判断这个标量变量是句柄还是要打印的内容:

代码语言:javascript复制
print $rocks_fh "…";    #将字符串打印到$rocks_fh所储存的句柄中,也可以加花括号来区分print {$rocks_fh} "…"
print $rocks_fh, "…";  #打印变量$rocks_fh的内容和字符串到标准输出

下面为文件句柄在Perl程序的使用示例:

代码语言:javascript复制
open FILE1, '<', "$ARGV[0]" or die "Cannot open file: $!";
open FILE2, '<', "$ARGV[1]" or die "Cannot open file: $!";
open FILE3, '>', "$ARGV[2]" or die "Cannot open file: $!";
my $line = <FILE2>;
while (<FILE1>) {
    chomp($_);
    print FILE3 $_ . ": ", $line;
}
close FILE1;
close FILE2;
close FILE3;

运行结果如下所示:

往期相关文章

Perl语言入门系列值一

Perl语言入门系列值二

0 人点赞