Perl的IO操作(2):更多文件句柄模式
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(<LOG>){
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
更多关于open和管道的解释参见:Perl进程间通信。
以读写模式打开
默认情况下:
- 以
>
模式打开文件句柄时,会先截断文件,也就是说无法从此文件句柄关联的文件中读取原有数据,且还会清空原有数据 - 以
>>
模式打开文件句柄时,首先会将指针指向文件的末尾以便追加数据,但无法读取该文件句柄对应的文件数据
如何以"既可写又可读"的模式打开文件句柄?在Perl中可以在模式前使用+
符号来实现。
结合"+"的模式有3种,都用来实现读写更新操作。它们的意义如下:
+<
:read-update,如open FH, "+<$file"
,可以提供读写行为。如果文件不存在,则open失败(以read为主,写为辅),如果文件存在,则文件内容保留,但IO的指针放在文件开头,也就是说无论读写操作,都从开头开始,写操作会从指针位置开始覆盖同字节数的数据。+>
:write-update,如open FH, "+>$file"
,可以提供读写行为。如果文件不存在,则创建文件(以write为主,read为辅)。如果文件存在,则截断整个文件,因此这种方式是先将文件清空然后写数据,再从中读数据。+>>
:append-update,如open FH, "+>>$file"
,提供读写行为。如果文件不存在,则创建(以append为主,read为辅),如果文件存在,则将IO指针放到文件尾部。一般情况下,每一次读操作之前都需要通过seek
将指针移动到文件的某个位置,而写操作则总是追加到文件尾部并自动移动指针到结尾。
一般来说,要同时提供读写操作,+<
是最可能需要的模式。
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循环,则会出现读、写错乱的问题,因为+<
模式打开文件句柄时IO指针默认在文件的开头:
#!/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: $!";
因为追加写模式会将指针放置在文件尾部,如果不将指针移动到文件的某个位置(可通过seek
来移动),将无法读出数据来。
例如:
#!/usr/bin/env perl
use strict;
use warnings;
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>){
print "First: ", $_; # 啥也不输出
}
seek(LOG, 0, 0); # 将读指针移动到文件开头
while(<LOG>){
print "Second: ", $_; # 正常输出
}
open打开STDOUT和STDIN
如果想要打开标准输入、标准输出,那么可以使用二参数格式的open,并将"-"指定为文件名。例如:
#
open LOG, "-"; # 打开标准输入
open LOG, "<-"; # 打开标准输入
open LOG, ">-"; # 打开标准输出
没有类似的直接打开标准错误输出的方式。如果有一个文件名就是"-",这时想要打开这个文件而不是标准输入或标准输出,那么需要将"-"文件名作为open的第三个参数。
open LOG, "<", "-";
创建临时文件
如果将open()函数打开文件句柄时的文件名指定为undef,则表示创建一个匿名文件句柄,即临时文件。这个临时文件将创建在/tmp目录下,创建完成后将立即被删除,但是却一直持有并打开这个文件句柄直到文件句柄关闭。这样,这个文件就成了看不到却仍被进程占用的临时文件。
什么时候才能用上打开就立即删除的临时文件?只读或只写的临时文件都是没有意义的,只有同时能读写的文件句柄才是有意义的,所以open的模式需要指定为+<
或+>
。显然,+<
是更为通用的读、写模式。
例如:
#!/usr/bin/perl
use strict;
use warnings;
# 创建临时文件
open my $tmp_file, '+<', undef or die "open filed: $!";
# 设置自动flush
select $tmp_file; $| = 1;;
# 这个临时文件已经被删除了
system("lsof -n -p $$ | grep 'deleted'");
# 写入一点数据
say {$tmp_file} "Hello World1";
say {$tmp_file} "Hello World2";
say {$tmp_file} "Hello World3";
say {$tmp_file} "Hello World4";
# 指针移动到临时文件的头部来读取数据
seek($tmp_file, 0, 0);
select STDOUT;
while(<$tmp_file>){
print "Reading from tmpfile: $_";
}
执行结果:
perl 22685 root 3u REG 0,2 0 108086391056997277 /tmp/PerlIO_JHnTx1 (deleted)
Reading from tmpfile: Hello World1
Reading from tmpfile: Hello World2
Reading from tmpfile: Hello World3
Reading from tmpfile: Hello World4
内存文件
如果将open()函数打开文件句柄时的文件名参数指定为一个标量变量(的引用,即下面示例中标量前加上了反斜线),也就是不再读写具体的文件,而是读写内存中的变量,这样就实现了一个内存IO的模式。
#!/usr/bin/perl
$text = "Hello World1\nHello World2\n";
# 打开内存文件以便读取操作
open MEMFILE, "<", \$text or die "open failed: $!";
print scalar <MEMFILE>;
# 提供内存文件以供写入操作
$log = ""
open MEMWRITE, ">", \$log;
pritn MEMWRITE "abcdefg\n";
pritn MEMWRITE "ABCDEFG\n";
print $log;
如果内存文件操作的是STDOUT和STDERR这两个特殊的文件句柄,如果需要重新打开它们,一定要先关闭它们再重新打开,因为内存文件不依赖于文件描述符,再次打开文件句柄不会覆盖文件句柄。例如:
close STDOUT;
open(STDOUT, ">", \$variable)
or die "Can't open STDOUT: $!";
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重定向,或者直接参考Perl的高级重定向文章:Perl IO:IO重定向。
Perl的IO操作(2):更多文件句柄模式的更多相关文章
- Perl的IO操作(1):文件句柄
文件句柄 文件句柄用来对应要操作的文件系统中的文件,这么说不太严谨,但比较容易理解.首先为要打开的文件绑定文件句柄(称为打开文件句柄),然后在后续的操作中都通过文件句柄来操作对应的文件,最后关闭文件句 ...
- python之协程与IO操作
协程 协程,又称微线程,纤程.英文名Coroutine. 协程的概念很早就提出来了,但直到最近几年才在某些语言(如Lua)中得到广泛应用. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B ...
- Pandas系列(十一)-文件IO操作
数据分析过程中经常需要进行读写操作,Pandas实现了很多 IO 操作的API,这里简单做了一个列举. 格式类型 数据描述 Reader Writer text CSV read_ csv to_cs ...
- python中的IO操作
python中的基本IO操作: 1) 键盘输入函数:raw_input(string),不作处理的显示,与返回. input(string),可以接受一个python表达式作为返回,python内部得 ...
- lua io操作(一)
最近在做可视化窗口数据配置 使用的lua 语言,免不了IO操作 通过查阅资料,做了如下总结,易于理解 lua里的文件读写模型来自C语言,分为完整模型(和C一样).简单模型. 1.简单模型 io.inp ...
- lua 的io操作,非常详细
Lua 标准库 - 输入输出处理(input and output facilities) I/O库提供两种不同的方式进行文件处理 1.io表调用方式:使用io表,io.open将返回指定文件的描述, ...
- Lua的文件IO操作
Lua 标准库 - 输入输出处理(input and output facilities) 转载于:http://blog.csdn.net/duanxuyun 文本Tag: Lua [IT168 技 ...
- Linux网络编程三、 IO操作
当从一个文件描述符进行读写操作时,accept.read.write这些函数会阻塞I/O.在这种会阻塞I/O的操作好处是不会占用cpu宝贵的时间片,但是如果需要对多个描述符操作时,阻塞会使同一时刻只能 ...
- IO操作与IO模型
目录 一 .IO操作本质 二. IO模型 BIO – 阻塞模式I/O NIO – 非阻塞模式I/O IO Multiplexing - I/O多路复用模型 AIO – 异步I/O模型 三.同步I/O与 ...
随机推荐
- python删除文件和文件夹
python中删除文件:os.remove(path) path为文件的路径 import os os.remove(path) python中删除文件夹:shutil.rmtree(path) pa ...
- Python中使用cx_Oracle调用Oracle存储过程
import cx_Oracle as cx import datetime def execute_sql(): # 声明变量 date_time = datetime.datetime.now() ...
- 基于IP的docker private registry 私有仓库的搭建
鉴于国内的网络环境,如果公司内部大量使用docker,镜像上传下载将是个非常耗时的工作,搭建一个私有仓库可以很好的解决自有镜像的存储,如果你在私有网络,不能使用域名的话,那么本文可以作为一个很好的例子 ...
- C++中_cplusplus及Extern "C"的理解
一.意义 Extern "C":告诉编译器,这部分代码按C语言的方式进行编译,而不是C++的. _cplusplus:.cpp文件默认定义的宏.用来判断是否为.cpp文件. 二 ...
- lightoj 1074
这题怎么说呢,负环上的点都不行 网上也有很多解法 我用dfs的spfa解的 我发现网上别人的代码都是用bfs的spfa写的,我就用的dfs的,快了好多 代码还看的别人的,只有中间的spfa是自己写的 ...
- Django关联数据库时报错TypeError: __init__() missing 1 required positional argument: 'on_delete'
sgrade = models.ForeignKey("Grades",) 执行python manage.py makemigrations后出现TypeError: __ini ...
- Dubbo 源码分析 - 集群容错之 Directory
1. 简介 前面文章分析了服务的导出与引用过程,从本篇文章开始,我将开始分析 Dubbo 集群容错方面的源码.这部分源码包含四个部分,分别是服务目录 Directory.服务路由 Router.集群 ...
- Chart 图表开源项目总结
在Android开发中,我们不免会遇到图表展示的需求,以下是本人之前star的悬浮窗的开源项目,供大家参考: 1. WilliamChart:创建图表的Android库 2. HelloCharts: ...
- PackageManager整理
一.PackageManager的功能 1.安装.卸载应用.2.查询permission相关信息.3.查询Application相关信息(application,activity,receiver,s ...
- npm安装webpack失败(mac和window都可能会遇到这样的情况,以下问题主要以mac为例)
问题描述:我想查看一下webpack的版本,于是输入了命令webpack -v, 结果如下图所示: 注:这里提示我们要安装webpack-cli,是因为到了webpack4, webpack 已经将 ...