Perl IO:IO重定向
文件句柄和文件描述符的关系
文件描述符是操作系统的资源,对于实体文件来说,每打开一次文件,操作系统都会为该进程分配一个文件描述符来关联(指向)这个文件,以后操作文件数据都根据这个文件描述符来操作,而不是文件名。就像对文件句柄的操作一样。
实际上,文件句柄、文件描述符和实体文件的关系存在层次上的关系。文件句柄指向文件描述符,文件描述符指向实体文件结构。如下图:
(图注:fd是用户空间的内容,图中放在内核层是为了概括与之关联的内核层的几个结构:fd对应内核层的这几个结构)
正如图中所示,文件句柄是文件描述符的更上层封装,文件句柄指向文件描述符,且多个文件句柄还可以指向同一个文件描述符。同样地,多个文件描述符可以指向同一个实体文件。实际上,从文件到文件描述符,是采用引用计数的方式的,表示有多少个文件描述符还关联在这个文件上。同理,文件描述符到文件句柄,也是使用引用计数方式的,表示有多少文件句柄指向这个文件描述符。
使用引用计数的特点之一就是只有引用数为0之后才表示关闭/删除/释放行为。例如,关闭文件句柄只是在文件描述符上引用数减一,而不是真的关闭文件描述符,直到文件描述符上的文件句柄引用数为0之后,这个文件描述符才会被关闭。同理,关闭文件描述符只是对文件结构的引用计数减1,直到这个文件结构的所有文件描述符都关闭了,才表示释放这个文件结构。
因为文件句柄是文件描述符的上层封装,所以文件句柄比文件描述符的功能多一些。实际上,从文件描述符到实体文件,中间的数据传输是纯裸数据流,不会有缓冲行为(当然,从操作系统的角度上看,有文件系统层的IO缓冲或操作系统层的缓冲,如page cache、buffer cache)。而文件句柄到文件描述符,中间有好几个IO层次,例如编码层(utf8)、换行符层(raw/crlf)、标准IO库层(stdio/perlio)、最底层(unix)。如下图:
(图注:fd是用户空间的内容,图中放在内核层是为了概括与之关联的内核层的几个结构:fd对应内核层的这几个结构。但page cache是内核空间的)
其中标准IO库层用来提供IO buffer层,stdio是操作系统提供的标准IO库,perlio是Perl提供的标准IO库,在Perl中可以选择使用哪种IO库提供buffer。unix是perl io的最底层(就算是在win下也是unix层),它几乎等价于直接操作文件描述符,相当于是纯裸数据,没有IO buffer。
模块PerlIO::Layers提供了Perl在文件描述符到文件句柄的IO层次上的一些检测功能,例如检测文件句柄是否已打开,是否设置了autoflush,是否使用缓冲等等。
另外,从文件句柄到文件描述符中间的IO Buffer中的数据,可以通过perl IO::Handle->flush()
来刷,flush时,io buffer中未读数据被丢弃,未写数据将写入到文件描述符。从文件描述符到设备文件(如磁盘上的abc.log)中间的缓冲(如page cache),可以通过操作系统的fsync()系统调用或perl IO::Handle->sync()
来刷盘。
文件句柄、文件描述符的duplicate
在bash shell中经常见到的>file 2>&1
,它表示将标准错误、标准输出都重定向到file文件中。这里的过程是将标准输入重定向到file文件,然后duplicate文件描述符fd=1得到fd=2,使得fd=2也指向fd=1对应的文件(即file),从而使得标准错误、标准输出都输出到file中。
除了重定向、文件描述符的duplicate,bash shell还支持文件描述符的手动打开(分配文件描述符)、移动、关闭。
Perl当然也支持类似的重定向和duplcate,而且不仅支持文件描述符级别的,还支持更上层别文件句柄级,无论是duplicate文件句柄还是duplicate文件描述符,都会生成新的文件描述符。另外,duplicate的对象是文件句柄时,不会将IO Buffer中的内容也duplicate,也就是说新的文件句柄中没有缓冲任何数据。
在Perl中,可以在open时在>、>>、<、+>、+>>、+<
的后面加上符号&
,这就表示文件句柄或文件描述符的duplicate。给文件句柄就是文件句柄的duplicate,给数值就是文件描述符的duplicate。open可以是两参数的或三参数的,三参数时,可以是文件句柄、文件句柄的引用(即\*FILEHANDLE
格式),可以是文件描述符数值。如果需要获取文件句柄指向的文件描述符,可以使用fileno FILEHANDLE
函数来获取。
例如,下面将STDOUT文件句柄duplicate一份得到NEWOUT,使得NEWOUT也指向标准输出,即向NEWOUT写入数据时也会出现在屏幕上(默认)。
# 两参数或三参数的文件句柄duplicate
open NEWOUT, ">&STDOUT";
open NEWOUT, ">&", "STDOUT";
open NEWOUT, ">&", "\*STDOUT";
# 三参数的文件描述符duplicate
open NEWOUT, ">&", fileno STDOUT;
按照上面的duplicate过程,结果如下图:
(图注:fd是用户空间的内容,图中放在内核层是为了概括与之关联的内核层的几个结构:fd对应内核层的这几个结构)
在duplicate时,所选的模式一定要匹配源文件句柄的模式。例如STDOUT是可写不可读(write-only)的文件句柄,在duplicate STDOUT时,就必须只能选择可写不可读的>&
模式。duplicate后,新的文件句柄或文件描述符和源文件句柄/文件描述符的读、写模式是完全一样的。
下面是将STDOUT复制多份的示例:
#!/usr/bin/perl
#
use strict;
use warnings;
use 5.010;
open NEWOUT, ">&STDOUT" or die "duplicate1 failed: $!";
say NEWOUT "hello world1, fd=", fileno NEWOUT;
open NEWOUT1, ">&", "NEWOUT" or die "duplicate2 failed: $!";
say NEWOUT1 "hello world2, fd=", fileno NEWOUT1;
open NEWOUT2, ">&", "\*NEWOUT" or die "duplicate3 failed: $!";
say NEWOUT2 "hello world3, fd=", fileno NEWOUT2;
open NEWOUT3, ">&", fileno NEWOUT or die "duplicate4 failed: $!";
say NEWOUT3 "hello world4, fd=", fileno NEWOUT3;
close NEWOUT;
close NEWOUT1;
close NEWOUT2;
close NEWOUT3;
执行后输出结果:
hello world1, fd=3
hello world2, fd=4
hello world3, fd=5
hello world4, fd=6
文件描述符重用:句柄别名
duplicate文件句柄或文件描述符时,都会自动新建一个新的文件描述符,并自动新建指向这个文件描述符的文件句柄。也就是说,只要duplicate一次,就至少有2个描述符,两个句柄。
如果想要重用文件描述符,只新建文件句柄,Perl中可以使用&=
符号(<&=、>&=、>>&=、+<&=、+>&=、+>>&=
),这表示创建一个文件句柄别名,使这个文件句柄也指向同一个文件描述符。也支持直接对文件描述符设置别名句柄,它会新建一个句柄指向这个文件描述符。
例如:
open(ALIAS, ">&=HANDLE");
open ALIAS, ">&=", fileno HANDLE;
这表示创建HANDLE句柄的一个别名,使得它两指向同一个文件描述符。如下:
(图注:fd是用户空间的内容,图中放在内核层是为了概括与之关联的内核层的几个结构:fd对应内核层的这几个结构)
因为两个句柄指向同一个文件描述符,所以这两个文件句柄共享了这个文件描述符,包括这个描述符上的锁。另外,从任一句柄更改描述符状态,都会直接反映到另一个文件句柄上,比如从一个文件句柄上加一把flock锁,因为flock锁是直接文件描述符上的,所以另一个文件句柄别名也会持有这把锁。
重定向
在bash中重定向非常的简单,在Perl中重定向直接使用> < >> +> +>> +<
即可,只不过open的第一个参数是一个已存在的文件句柄,其无非是将输入自或输出到的某个文件句柄/文件描述符的数据转向另一个方向。
例如,将输出到标准输出的数据重定向到某个文件中,就像使用select FILEHANDLE
一样。
open STDOUT, "> abc.log";
say "hello world"; # 将输出到abc.log文件中
再例如输入重定向,STDIN本该是从标准输入中读取数据的,但是现在改从一个文件中读取数据。
open STDIN, "< abc.log";
while(<STDIN>){
chomp;
print "$_\n";
}
但是这样使用重定向功能会有一个问题,STDOUT或STDIN或其它重定向句柄没法还原回原始的目标了。例如STDOUT原本是输出到终端的,将其重定向到某个文件后就没法找回输出到终端的方法了。所以,在程序中使用重定向时,经常会将重定向配合duplicate使用,在重定向之前,先将重定向句柄dup保存一份,然后重定向,重定向结束后再使用保存的句柄恢复回来。
例如:
# 1.dup。OLDOUT和STDOUT都将指向同一个底层文件结构:终端设备文件
open OLDOUT, ">&", STDOUT or die "duplicate failed: $!";
# 2.redirect。OLDOUT仍然指向终端设备文件,但是STDOUT指向新文件结构
open STDOUT, "> $newfile" or die "redirect failed: $!";
... do something to STDOUT ...
# 3.restore。通过dup的方式从OLDOUT恢复STDOUT
close STDOUT or die "close failed: $!";
open STDOUT, ">&", OLDOUT or die "duplicate failed: $!";
注意上面第三步中恢复之前,记得先关闭STDOUT,如果不关闭STDOUT,在第二步过程中STDOUT中的缓冲不会flush。
Perl IO:IO重定向的更多相关文章
- Perl的IO操作(2):更多文件句柄模式
open函数除了> >> <这三种最基本的文件句柄模式,还支持更丰富的操作模式,例如管道.其实bash shell支持的重定向模式,perl都支持,即使是2>&1 ...
- IO模型--阻塞IO,非阻塞IO,IO多路复用,异步IO
IO模型介绍: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 信号驱动IO ...
- python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)
昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...
- {python之IO多路复用} IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO模型比较分析 selectors模块
python之IO多路复用 阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 ...
- Java IO,io,文件操作,删除文件,删除文件夹,获取文件父级目录
Java IO,io,文件操作,删除文件,删除文件夹,获取文件父级目录 这里先简单的贴下常用的方法: File.separator //当前系统文件分隔符 File.pathSeparator // ...
- (IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)
参考博客: https://www.cnblogs.com/xiao987334176/p/9056511.html 内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yi ...
- Perl的IO操作(1):文件句柄
文件句柄 文件句柄用来对应要操作的文件系统中的文件,这么说不太严谨,但比较容易理解.首先为要打开的文件绑定文件句柄(称为打开文件句柄),然后在后续的操作中都通过文件句柄来操作对应的文件,最后关闭文件句 ...
- Github Pages(io) + 域名重定向 (手把手教你搭建个人网站)
好歹也成为了在读phd的人,拥有个人网站是个有排面有很必要的事儿~ 在这里利用Github Pages + 域名重定向,实现个人网站的光速搭建~ 1.0 Github Repositories 首先你 ...
- 标准IO和重定向
1.标准输入/输出/错误 当shell启动,它继承三个文件:stdin.stdout.stderr,标准输入通常来自键盘,标准输出和标准错误通常是屏幕.标准输入/输出/错误的文件描述符为0.1.2 2 ...
- JAVA基础知识之IO——IO流(Stream)的概念
Java IO 流 Java将不同的设备或载体(键盘.文件.网络.管道等)的输入输出数据统称为"流"(Stream),即JAVA的IO都是基于流的. JAVA传统的所有流类型类都包 ...
随机推荐
- PHP调用微信wx_JSSDK录音并播放,
<?php require_once "jssdk.php"; $jssdk = new JSSDK("wx7a862ec806328ca2", &quo ...
- git如何避免push/pull时输入密码
今天在搭建git服务器的时候,一切顺利,但是就是在git push的时候老是要输入密码,太烦了,然后百度搜索了一下,总结了主要有如下三种方法: 方法1 git config --global cred ...
- [LeetCode] Maximize Distance to Closest Person 离最近的人的最大距离
In a row of seats, 1 represents a person sitting in that seat, and 0 represents that the seat is emp ...
- 课堂作业Complex类的实现
#include <iostream> #include <cmath> using namespace std; class Complex{ public: Complex ...
- SSIS - 10.执行过程任务
一.创建批处理文件 在SSIS包中,执行过程任务可以用来运行应用程序或批处理文件.它执行时用到的输入.输出和参数可以在任务编辑器中进行设置. 在使用执行过程任务之前,我们需要先创建一个批处理文件,实现 ...
- 安卓开发笔记(十六):'Request(okhttp3.Request.Builder)' has private access in 'okhttp3.Request
当出现了'Request(okhttp3.Request.Builder)' has private access in 'okhttp3.Request的错误的时候,实际上是我们在写代码的时候少打了 ...
- SUSE12Sp3-Supervisor 守护.net core进程
1.安装setuptools 将setuptools-0.6c11.tar.gz安装包放到服务器上 tar zxvf setuptools-0.6c11.tar.gz cd setuptools-0. ...
- [Swift]LeetCode38. 报数 | Count and Say
The count-and-say sequence is the sequence of integers with the first five terms as following: 1. 1 ...
- [Swift]LeetCode50. Pow(x, n) | Pow(x, n)
Implement pow(x, n), which calculates x raised to the power n (xn). Example 1: Input: 2.00000, 10 Ou ...
- 通信统计接口字段填充内容shell脚本
现在通信行业上班,最麻烦的就是计算各个协议接口字段填充的内容是否正确.经历了小白似的一个个字段统计在到单个接口所有字段统计,然后到现在的多个接口一次统计.在大大节省了时间的同时,让自己有更充足的时间来 ...