Perl中的文件句柄File handler就像名字所说明的那样,是指操作文件的handler而非文件本身
Perl预定义了六种文件句柄,STDIN 标准输入流、 STDOUT 标准输出流、 STDERR 标准错误流、ARGV 参数列表、 DATA 和 ARGVOUT,只要不和系统预定义的句柄重名,用户可以定义自己的文件句柄,句柄名字推荐使用全大写表示
STDIN 默认指向用户的键盘,STDOUT 和STDERR 默认指向显示器,但他们都可以被重定向到指定的文件
打开文件句柄:因为Perl并不能直接操作文件,只能通过调用操作系统API操作文件,所以除了预定义的文件句柄之外,用户自己定义的文件句柄必须显式的打开才能使用。打开文件句柄的方式有以下几种:
-- 默认的打开 (可读也可写)open TXTFILE, "test.txt"
-- 只读的打开open TXTFILE, "<test.txt"
-- 覆写的打开open TXTFILE, ">test.txt"
存在就覆盖,不存在则创建
-- 追加的打开open TXTFILE, ">>test.txt"
存在就追加,不存在则创建
- 文件名也可以用变量来替代:
demo5-1
:
#! /usr/bin/perl
my $test = "test.txt";
my $success = open TXTFILE, "<$test";
if($success){
print "success\n";
}else{
print "failed\n";
}
- 运行结果:(先创建一个名为test.txt的空文件)
./demo5-1
success
关闭文件句柄:显式地用
close FILEHANDLER
关闭文件句柄会通知操作系统立刻刷新输出缓冲区,然后关闭对文件的访问。用open
打开一个重名文件句柄也会关闭之前已打开的文件句柄-
使用文件句柄:
- 从用户定义的文件句柄中读取和从标准输入流中读取没什么区别,使用一对尖括号
<>
将文件句柄括起来,就像这样<TXTFILE>
- 往用户定义的文件句柄中写入稍有不同,需要在print之后显式的指定文件句柄
print TXTFILE "Write into this file from Perl successfully"
, 而且就像代码里展示的那样,句柄和输入的内容之间没有逗号
demo5-2
:
- 从用户定义的文件句柄中读取和从标准输入流中读取没什么区别,使用一对尖括号
#! /usr/bin/perl
if(! open NEWFILE, ">new.txt"){
die "Open new.txt to write failed:$!";
}
print NEWFILE "Write into NEWFILE successfully: ".localtime();
close NEWFILE;
if(! open OLDFILE, "<new.txt"){
die "Open new.txt for reading failed:$!";
}
@input = <OLDFILE>;
print "Read from new.txt:\n";
print "@input\n";
close OLDFILE;
- 运行结果
./demo5-2
Read from new.txt:
Write into NEWFILE successfully: Tue Mar 26 17:02:22 2019
-
标准输入流:用文件句柄来理解标准输入流就简单多了,
<STDIN>
只不过是一个默认从键盘获取输入的文件句柄而已。标准输入流<STDIN>
既可以重定向从其他文件里获取输入,用户定义的文件句柄也可以指向从键盘获取输入。所以唯一的不同似乎只有标准输入流定义了更多的默认行为,而且在使用标准输入流之前不用open
来显式打开它- 将标准输入流重定向到文件:
open STDIN, "<test.txt"
这种方式实际是复用Perl预置的文件句柄 - 将用户定义的文件句柄指向键盘输入:
open USER, "<&1"
demo5-3
:
- 将标准输入流重定向到文件:
#! /usr/bin/perl
open USER, "<&1";
print "User input:".<USER>;
当执行demo5-3
时,它讲等待用户输入一行,然后回显用户的输入内容,就像使用<STDIN>
一样!
-
当读取文件句柄(包括标准输入流)和循环结合,有两种形式的循环结构至少在结果上看是一样的:
foreach循环:
foreach (<STDIN>){
print $_;
}
while循环:
while (<STDIN>){
print $_;
}
他们都将回显所有用户输入的内容,直到用户按下Ctrl-D结束输入。
但是,foreach循环是一次性将文件句柄中的所有内容存储到内存中的列表里,然后用$_
遍历该列表,而while一次从文件句柄中只读取一行到内存中的$_
变量,然后打印出来,当文件句柄指向的文件特别大时,这种差别所引起的性能差异将会被明显放大
-
钻石操作符<>, 调用参数和@ARGV数组, 在之前的例子中,钻石操作符用来从文件句柄中获取输入,不管这个句柄是用户定义的,还是Perl预置的,他们的行为都是一样的。除了标准输入/输出/错误流三个预置文件句柄外,Perl还定义了ARGV句柄和@ARGV数组来存储Perl程序的调用参数
demo5-4
#! /usr/bin/perl
foreach(@ARGV){
print "$_\n";
}
执行:(这里的a.out, b.cpp, c.txt是三个不存在的文件)
./demo5-4 a.out b.cpp c.txt
a.out
b.cpp
c.txt
demo5-4
将@ARGV
中的内容逐行输出到屏幕上,而@ARGV
中的内容恰好就是调用demo5-4
时的参数
现在换成<ARGV>
文件句柄试一下
demo5-5
:
#! /usr/bin/perl
foreach(<ARGV>){
print "$_\n";
}
用同样的方式执行:(这里的a.out, b.cpp, c.txt是三个不存在的文件)
./demo5-5 a.out b.cpp c.txt
Can't open a.out: No such file or directory at ./demo5-5 line 2.
Can't open b.cpp: No such file or directory at ./demo5-5 line 2.
Can't open c.txt: No such file or directory at ./demo5-5 line 2.
很显然,demo5-5
尝试去打开我传入的文件名,而且失败了。这说明 <ARGV>
句柄不同与@ARGV
数组,@ARGV
只是单纯地将调用参数保存起来,而<ARGV>
则认为你的调用参数是你指定的输入源,并且尝试将其打开,这是因为<ARGV>
的本质是文件句柄File Handler,所以它的默认动作就是操作文件,不管这个“文件”是存储在磁盘上还是用户的键盘和显示器。
这种差别的意义在于:如果我想写一个类似cat的程序将调用参数指定的文件内容输出到显示器上,那我应该使用<ARGV>
,如果我错误的使用了@ARGV
,那我只能在显式器上看到我输入的参数。但如果我想写一个汇率计算程序,将调用时传入的美元换算成人民币, 那我就应该使用@ARGV
,因为$109.9
并不是一个合法的文件名,<ARGV>
无法打开它,而且我们需要的是调用参数的字面值。
现在修改demo5-1
使用过的test.txt
文件,新增如下内容
This is a test file.
This is the second line of the file.
然后再次运行demo5-5
:
./demo5-5 test.txt
This is a test file.
This is the second line of the file.
注意到这里的换行,因为程序里print 命令打印了换行符,而文件内也有换行符,如果只想打印一次换行,需要在循环体内用chomp
删掉文件里的换行符
-
文件句柄的默认行为:当钻石操作符没有指定文件句柄时,默认指向ARGV,而没有调用参数时ARGV则默认指向标准输入流。
demo5-6
:
#! /usr/bin/perl
foreach(<>){
print "$_\n";
}
用如下四种方式调用:
1. "./demo5-6" 程序将读取键盘的输入(未重定向的标准输入流),直到按下ctrl+D来结束,然后依次在屏幕打印键盘的输入内容
2. "./demo5-6 test.txt" 和"./demo5-5 test.txt"的效果相同,程序将读取test.txt文件中的内容,然后逐行打印到屏幕上,并加上额外的换行符。这里指定了test.txt作为程序新的输入源,而不再使用标准输入流(也就是键盘)
3. "./demo5-6 <test.txt" 程序读取test.txt中的内容,然后打印到屏幕上
4. "./demo5-6 -" 和无调用参数的"./demo5-6"效果相同,ARGV的参数列表里"-"就表示标准输入流,可见ARGV的默认行为是标准输入流
将此程序改写为
demo5-7
:
#! /usr/bin/perl
foreach(<STDIN>){
print "$_\n";
}
这里指定了要从标准输入流STDIN中进行读取,同样用上面的三种方式进行验证:
1. "./demo5-7" 和之前相同,程序将读取键盘的输入,直到按下ctrl+D来结束,然后依次在屏幕打印键盘的输入内容
2. "./demo-7 test.txt" 程序读取键盘的输入,然后打印到屏幕上。虽然这里传入了test.txt作为调用参数,但因为程序里使用的是标准输入流STDIN而不是ARGV,所以仍然使用标准输入流(也就是键盘)作为输入源
3. "./demo5-7 <test.txt" 不同于第2种方式,程序没有读取键盘的输入,而是读取了test.txt中的内容,然后打印到屏幕上。这里用test.txt取代键盘,成为标准输入流,对于Perl程序来说,依然是从标准输入流中获取输入,只是操作系统将标准输入流的内容替换了,相当于实现了隐式的标准输入流重定向,而这个过程对Perl程序是透明的。
但是在./demo5-6 <test.txt
时发生了什么呢?
修改demo5-6
:
demo5-8
:
#! /usr/bin/perl
open STDIN, "<&1";
foreach(<>){
print "$_\n";
}
在代码第一行把标准输入流STDIN重定向到键盘输入"<1&",
再尝试调用
1. "./demo5-8 test.txt" 打印文件内容,说明此时并没有读取标准输入流STDIN
2. "./demo5-8 <test.txt" 回显用户输入,说明使用符号`<`时发生了系统重定向
- 第一种调用方式因为有参数,所以ARGV并没有读取标准输入流的内容
- 第二种方式实际是无参数调用Perl程序,并且系统将标准输入流重定向到text.txt文件。这里是用到了Unix的管道功能,把test.txt的内容通过管道传递给了Perl程序,而这个过程对Perl是透明的,所以除非在代码里显式的将STDIN指回键盘,否则就会从管道中读取,而在这个过程中,Perl一直在读取标准输入流中的内容,虽然它并不清楚标准输入流中的内容来自哪里。
- 格式化输出printf :
- "%10s", 10表示占位,s表示字符串,printf基本是从C语言移植过来的,所以基本语法相同
- 可以用一个标量存储格式化内容,再用printf输出,比如:
$input = <STDIN>
$num = 10;
$format = "%${num}s";
printf $format, $input
$format = "%${num}s";
使用了变量内插
需要注意$input = <STDIN>
没有调用chomp
函数,如果是从键盘读取输入,此时$input
里包含换行符,在printf
进行格式化输出时也会占用占位符的位置
- %%和% 似乎没什么区别,
- print “@array” 会在元素之间加上空格, 这里其实是在标量上下文中应用数组,所以数组在被转成标量时在数组元素之间加上了空格,print @array则会一个接一个的输出数组中的元素,这就是print在列表上下文中的标准做法
- die
- $0 程序名:实际上是命令行的第一个参数,只不过它一般是程序名,而且不保存在@ARGV 参数列表里
- $! 系统诊断信息
- die默认会打印行号,除非在最后加上换行符\n
demo5-9
:
#! /usr/bin/perl -w
use strict;
if(@ARGV == 0){
die "No way Man\n";
}
if(@ARGV == 1){
die "$0 Cannot run";
}
our $success = open FILE, "$ARGV[1]";
if(! $success){
die "fail open file $ARGV[2]:$!";
}
foreach(<FILE>){
print "Line:",$_;
}
用三种方式调用的结果如下:
无参调用:
# ./demo5-9
No way Man
一个参数调用
# ./demo5-9 test.txt
./demo5-1 Cannot run at ./demo5-1 line7.
两个参数调用
# ./demo5-9 test.txt test.txt
Line: This is a test file.
Line: This is the second line of the file.
这里没有使用钻石运算符<>,而是使用了一个文件句柄打开文件输入,效果是一样的。