zoukankan      html  css  js  c++  java
  • 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 "$_
    ";
    }
    

    但是这样使用重定向功能会有一个问题,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。

  • 相关阅读:
    [转] Immutable 常用API简介
    [转] 组件库按需加载 借助babel-plugin-import实现
    [转] react-router4 + webpack Code Splitting
    [转] React Hot Loader 3 beta 升级指南
    [转] 如何写好.babelrc?Babel的presets和plugins配置解析
    [转] webpack热更新配置小结
    [转] 学会fetch的用法
    [转] webpack3.0踩坑:postcss-loader的使用
    [转] 详解webpack-dev-server的使用
    webpack 使用环境变量
  • 原文地址:https://www.cnblogs.com/f-ck-need-u/p/10450299.html
Copyright © 2011-2022 走看看