经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Perl » 查看文章
Perl IO:IO重定向
来源:cnblogs  作者:骏马金龙  时间:2019/3/1 9:17:07  对本文有异议

文件句柄和文件描述符的关系

文件描述符是操作系统的资源,对于实体文件来说,每打开一次文件,操作系统都会为该进程分配一个文件描述符来关联(指向)这个文件,以后操作文件数据都根据这个文件描述符来操作,而不是文件名。就像对文件句柄的操作一样。

实际上,文件句柄、文件描述符和实体文件的关系存在层次上的关系。文件句柄指向文件描述符,文件描述符指向实体文件结构。如下图:

正如图中所示,文件句柄是文件描述符的更上层封装,文件句柄指向文件描述符,且多个文件句柄还可以指向同一个文件描述符。同样地,多个文件描述符可以指向同一个实体文件。实际上,从文件到文件描述符,是采用引用计数的方式的,表示有多少个文件描述符还关联在这个文件上。同理,文件描述符到文件句柄,也是使用引用计数方式的,表示有多少文件句柄指向这个文件描述符

使用引用计数的特点之一就是只有引用数为0之后才表示关闭/删除/释放行为。例如,关闭文件句柄只是在文件描述符上引用数减一,而不是真的关闭文件描述符,直到文件描述符上的文件句柄引用数为0之后,这个文件描述符才会被关闭。同理,关闭文件描述符只是对文件结构的引用计数减1,直到这个文件结构的所有文件描述符都关闭了,才表示释放这个文件结构。

因为文件句柄是文件描述符的上层封装,所以文件句柄比文件描述符的功能多一些。实际上,从文件描述符到实体文件,中间的数据传输是纯裸数据流,不会有缓冲行为(当然,有非IO的缓冲)。而文件句柄到文件描述符,中间有好几个IO层次,例如编码层(utf8)、换行符层(raw/crlf)、标准IO库层(stdio/perlio)、最底层(unix)。如下图:

其中标准IO库层用来提供IO buffer层,stdio是操作系统提供的标准IO库,perlio是Perl提供的标准IO库,在Perl中可以选择使用哪种IO库提供buffer。unix是最底层(就算是在win下也是unix层),它是最接近文件描述符的底层,几乎是纯裸数据,没有IO buffer

模块PerlIO::Layers提供了Perl在文件描述符到文件句柄的IO层次上的一些检测功能,例如检测文件句柄是否已打开,是否设置了autoflush,是否使用缓冲等等。

文件句柄、文件描述符的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写入数据时也会出现在屏幕上(默认)。

  1. # 两参数或三参数的文件句柄duplicate
  2. open NEWOUT, ">&STDOUT";
  3. open NEWOUT, ">&", "STDOUT";
  4. open NEWOUT, ">&", "\*STDOUT";
  5. # 三参数的文件描述符duplicate
  6. open NEWOUT, ">&", fileno STDOUT;

按照上面的duplicate过程,结果如下图:

在duplicate时,所选的模式一定要匹配源文件句柄的模式。例如STDOUT是可写不可读(write-only)的文件句柄,在duplicate STDOUT时,就必须只能选择可写不可读的>&模式。duplicate后,新的文件句柄或文件描述符和源文件句柄/文件描述符的读、写模式是完全一样的

下面是将STDOUT复制多份的示例:

  1. #!/usr/bin/perl
  2. #
  3. use strict;
  4. use warnings;
  5. use 5.010;
  6. open NEWOUT, ">&STDOUT" or die "duplicate1 failed: $!";
  7. say NEWOUT "hello world1, fd=", fileno NEWOUT;
  8. open NEWOUT1, ">&", "NEWOUT" or die "duplicate2 failed: $!";
  9. say NEWOUT1 "hello world2, fd=", fileno NEWOUT1;
  10. open NEWOUT2, ">&", "\*NEWOUT" or die "duplicate3 failed: $!";
  11. say NEWOUT2 "hello world3, fd=", fileno NEWOUT2;
  12. open NEWOUT3, ">&", fileno NEWOUT or die "duplicate4 failed: $!";
  13. say NEWOUT3 "hello world4, fd=", fileno NEWOUT3;
  14. close NEWOUT;
  15. close NEWOUT1;
  16. close NEWOUT2;
  17. close NEWOUT3;

执行后输出结果:

  1. hello world1, fd=3
  2. hello world2, fd=4
  3. hello world3, fd=5
  4. hello world4, fd=6

文件描述符重用:句柄别名

duplicate文件句柄或文件描述符时,都会自动新建一个新的文件描述符,并自动新建指向这个文件描述符的文件句柄。也就是说,只要duplicate一次,就至少有2个描述符,两个句柄。

如果想要重用文件描述符,只新建文件句柄,Perl中可以使用&=符号(<&=、>&=、>>&=、+<&=、+>&=、+>>&=),这表示创建一个文件句柄别名,使这个文件句柄也指向同一个文件描述符。也支持直接对文件描述符设置别名句柄,它会新建一个句柄指向这个文件描述符。

例如:

  1. open(ALIAS, ">&=HANDLE");
  2. open ALIAS, ">&=", fileno HANDLE;

这表示创建HANDLE句柄的一个别名,使得它两指向同一个文件描述符。如下:

因为两个句柄指向同一个文件描述符,所以这两个文件句柄共享了这个文件描述符,包括这个描述符上的锁。另外,从任一句柄更改描述符状态,都会直接反映到另一个文件句柄上,比如从一个文件句柄上加一把flock锁,因为flock锁是直接文件描述符上的,所以另一个文件句柄别名也会持有这把锁。

重定向

在bash中重定向非常的简单,在Perl中重定向直接使用> < >> +> +>> +<即可,只不过open的第一个参数是一个已存在的文件句柄,其无非是将输入自或输出到的某个文件句柄/文件描述符的数据转向另一个方向。

例如,将输出到标准输出的数据重定向到某个文件中,就像使用select FILEHANDLE一样。

  1. open STDOUT, "> abc.log";
  2. say "hello world"; # 将输出到abc.log文件中

再例如输入重定向,STDIN本该是从标准输入中读取数据的,但是现在改从一个文件中读取数据。

  1. open STDIN, "< abc.log";
  2. while(<STDIN>){
  3. chomp;
  4. print "$_\n";
  5. }

但是这样使用重定向功能会有一个问题,STDOUT或STDIN或其它重定向句柄没法还原回原始的目标了。例如STDOUT原本是输出到终端的,将其重定向到某个文件后就没法找回输出到终端的方法了。所以,在程序中使用重定向时,经常会将重定向配合duplicate使用,在重定向之前,先将重定向句柄dup保存一份,然后重定向,重定向结束后再使用保存的句柄恢复回来

例如:

  1. # 1.dup。OLDOUT和STDOUT都将指向同一个底层文件结构:终端设备文件
  2. open OLDOUT, ">&", STDOUT or die "duplicate failed: $!";
  3. # 2.redirect。OLDOUT仍然指向终端设备文件,但是STDOUT指向新文件结构
  4. open STDOUT, "> $newfile" or die "redirect failed: $!";
  5. ... do something to STDOUT ...
  6. # 3.restore。通过dup的方式从OLDOUT恢复STDOUT
  7. close STDOUT or die "close failed: $!";
  8. open STDOUT, ">&", OLDOUT or die "duplicate failed: $!";

注意上面第三步中恢复之前,记得先关闭STDOUT,如果不关闭STDOUT,在第二步过程中STDOUT中的缓冲不会flush。

原文链接:http://www.cnblogs.com/f-ck-need-u/p/10450299.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号