open函数除了> >> <
这三种最基本的文件句柄模式,还支持更丰富的操作模式,例如管道。其实bash shell支持的重定向模式,perl都支持,即使是2>&1
这种高级重定向模式,perl也有对应的模式。
打开管道文件句柄
perl程序内部也支持管道,以便和操作系统进行交互。例如,将perl的输出在程序内部就输出给操作系统的命令,或者将操作系统的命令执行结果输出给perl程序内部。所以,perl有2种管道模式:句柄到管道、管道到句柄。
例如,将perl print语句的输出,交给操作系统的cat -n
命令来输出行号。也就是说,下面的perl程序和cat -n
命令的效果是一样的。
#!/usr/bin/perl
open LOG,"| cat -n"
or die "Can't open file: $!";
while(<>){
print $_;
}
再例如,将操作系统命令的执行结果通过管道交给perl文件句柄:
#!/usr/bin/perl
open LOG,"cat -n test.log |"
or die "Can't open file: $!";
while(<LOG>){
print "from pipe: $_";
}
虽然只有两种管道模式,但有3种写法:
- 管道输出到文件句柄模式:
-|
- 文件句柄输出到管道模式:
|-
|
写在左边,表示句柄到管道,等价于|-
,|
写在右边,等价于管道到句柄,等价于-|
上面第三点|
的写法见上面的例子便可理解。而|-
和-|
是作为open函数的模式参数的,以下几种写法是等价的:
open LOG, "|tr '[a-z]' '[A-Z]'");
open LOG, "|-", "tr '[a-z]' '[A-Z]'");
open LOG, "|-", "tr", '[a-z]', '[A-Z]');
open LOG, "cat -n '$file'|");
open LOG, "-|", "cat -n '$file'");
open LOG, "-|", "cat", "-n", $file);
而且,管道还可以继续传递给管道:
open LOG, "|tr '[a-z]' '[A-Z]' | cat -n");
但是涉及到两个管道的时候,输出到终端屏幕上时可能不太合意:
[root@xuexi perlapp]# perl 15.plx test.log
[root@xuexi perlapp]# 1 A
2 B
3 C
如何让输出不附加在shell提示符后,我暂时也不知道如何做。
但是,输出到文件中不会出现这样的问题:
open LOG, "|tr '[a-z]' '[A-Z]' | cat -n >test2.log");
[root@xuexi perlapp]# perl 15.plx test.log
[root@xuexi perlapp]# cat test2.log
1 A
2 B
3 C
以读写模式打开
默认情况下:
- 以
>
模式打开文件句柄时,会先截断文件,也就是说无法从此文件句柄关联的文件中读取原有数据,且还会清空原有数据 - 以
>>
模式打开文件句柄时,首先会将指针指向文件的末尾以便追加数据,但无法读取该文件句柄对应的文件数据
如何以"既可写又可读"的模式打开文件句柄?在perl中可以在模式前使用+
符号来实现。
1.打开可供读、写、更新的文件句柄,但不截断文件
open LOG,"+<","/tmp/test.log"
or die "Couldn't open file: $!";
如下面的例子,say语句会将数据写入到test.log的尾部,因为遍历完test.log后,指针在文件的尾部。
#!/usr/bin/perl
use 5.010;
open LOG,"+<","test.log"
or die "Couldn't open file: $!";
while(<LOG>){
print $_;
}
say LOG "from hello world";
但注意,如果将上面的say语句放进while循环,则会出现读、写错乱的问题:
#!/usr/bin/perl
use 5.010;
open LOG,"+<","test.log"
or die "Couldn't open file: $!";
while(<LOG>){
print $_;
say LOG "from hello world";
}
分析下这个错乱:当读取了第一行后,放置好指针位置,然后赋值给$_
并被print输出,然后再写入"from hello world",写入的位置是指针的后面,它会直接更新后面对应数量的字符数。数一数"from hello world"的字符数量和替换掉的字符数量,会发现正好相等。
2.打开可供读、写、更新的文件句柄,但首先截断文件
open LOG,"+>","/tmp/test.log"
or die "Couldn't open file: $!";
因为首先会截断文件,无法直接去读取内容。所以,这种操作模式,需要首先向文件中写入数据,再去读取数据。
#!/usr/bin/perl
use 5.010;
open LOG,"+>","test.log"
or die "Couldn't open file: $!";
say LOG "from hello world1";
say LOG "from hello world2";
say LOG "from hello world3";
while(<LOG>){
say $_;
}
3.打开可供读、追加写的文件句柄。它不会截断文件。
open LOG,"+>>","/tmp/test.log"
or die "Couldn't open file: $!";
因为追加写模式会将指针放置在文件尾部,所以我不太理解这里为什么能读取数据,而且经过的几种测试,也无法读出数据来。
例如:
#!/usr/bin/perl
#
use 5.010;
open LOG,"+>>","test.log"
or die "Couldn't open file: $!";
while(<LOG>){
say $_; # 啥也不输出
}
say LOG "from hello world1";
say LOG "from hello world2";
say LOG "from hello world3";
while(<LOG>){
say $_; # 啥也不输出
}
say LOG "from hello world12";
perl的高级重定向
在shell中可以通过>&
和<&
实现文件描述符的复制(duplicate)从而实现更高级的重定向。在perl中也同样能实现,符号也一样,只不过复制对象是文件句柄。
例如:
open LOG,">&STDOUT"
表示将写入LOG文件句柄的数据重定向到STDOUT中。
shell中很常用的一个符号是>&FILENAME
或>FILENAME 2>&1
,它们都表示标准错误和标准输出都输出到FILENAME中。在perl中实现这种功能的方式为:(注意dup目标使用\*
的方式,且不加引号)
open LOG,">","/dev/null" or die "Can't open filehandle: $!";
open STDOUT,">&",\*LOG or die "Can't dup LOG:$!";
open STDERR,">&",\*STDOUT or die "Can't dup STDOUT: $!";
或者简写一下:
open STDOUT,">","/dev/null" or die "Can't dup LOG:$!";
open STDERR,">&",\*STDOUT or die "Can't dup STDOUT: $!";
测试下:
use 5.010;
open LOG,">>","/tmp/test.log" or die "Can't open filehandle: $!";
open STDOUT,">&",\*LOG or die "Can't dup LOG: $!";
open STDERR,">&",\*STDOUT or die "Can't dup STDOUT: $!";
say "hello world stdout default";
say STDOUT "hello world stdout";
say STDERR "hello world stderr";
会发现所有到STDOUT和STDERR的内容都追加到/tmp/test.log文件中。
如果在同一perl程序中,STDOUT和STDERR有多个输出方向,那么dup这两个文件句柄之前,需要先将它们保存起来。需要的时候再还原回来:
# 保存STDOUT和STDERR到$oldout和OLDERR
open(my $oldout, ">&STDOUT") or die "Can't dup STDOUT: $!";
open(OLDERR, ">&", \*STDERR) or die "Can't dup STDERR: $!";
# 实现标准错误、标准输出都重定向到foo.out的功能,即"&>foo.out"
open(STDOUT, '>', "foo.out") or die "Can't redirect STDOUT: $!";
open(STDERR, ">&STDOUT") or die "Can't dup STDOUT: $!";
# 还原回STDOUT和STDERR
open(STDOUT, ">&", $oldout) or die "Can't dup \$oldout: $!";
open(STDERR, ">&OLDERR") or die "Can't dup OLDERR: $!";
因为这种高级重定向用的很少,所以不多做解释。如需理解,可参考我的shell关于高级重定向的文章:彻底搞懂shell的高级I/O重定向