Perl IO:IO重定向

文件句柄和文件描述符的關係

文件描述符是操做系統的資源,對於實體文件來講,每打開一次文件,操做系統都會爲該進程分配一個文件描述符來關聯(指向)這個文件,之後操做文件數據都根據這個文件描述符來操做,而不是文件名。就像對文件句柄的操做同樣。shell

實際上,文件句柄、文件描述符和實體文件的關係存在層次上的關係。文件句柄指向文件描述符,文件描述符指向實體文件結構。以下圖:bash

(圖注:fd是用戶空間的內容,圖中放在內核層是爲了歸納與之關聯的內核層的幾個結構:fd對應內核層的這幾個結構)函數

正如圖中所示,文件句柄是文件描述符的更上層封裝,文件句柄指向文件描述符,且多個文件句柄還能夠指向同一個文件描述符。一樣地,多個文件描述符能夠指向同一個實體文件。實際上,從文件到文件描述符,是採用引用計數的方式的,表示有多少個文件描述符還關聯在這個文件上。同理,文件描述符到文件句柄,也是使用引用計數方式的,表示有多少文件句柄指向這個文件描述符編碼

使用引用計數的特色之一就是只有引用數爲0以後才表示關閉/刪除/釋放行爲。例如,關閉文件句柄只是在文件描述符上引用數減一,而不是真的關閉文件描述符,直到文件描述符上的文件句柄引用數爲0以後,這個文件描述符纔會被關閉。同理,關閉文件描述符只是對文件結構的引用計數減1,直到這個文件結構的全部文件描述符都關閉了,才表示釋放這個文件結構。操作系統

由於文件句柄是文件描述符的上層封裝,因此文件句柄比文件描述符的功能多一些。實際上,從文件描述符到實體文件,中間的數據傳輸是純裸數據流,不會有緩衝行爲(固然,從操做系統的角度上看,有文件系統層的IO緩衝或操做系統層的緩衝,如page cache、buffer cache)。而文件句柄到文件描述符,中間有好幾個IO層次,例如編碼層(utf8)、換行符層(raw/crlf)、標準IO庫層(stdio/perlio)、最底層(unix)。以下圖:unix

(圖注:fd是用戶空間的內容,圖中放在內核層是爲了歸納與之關聯的內核層的幾個結構:fd對應內核層的這幾個結構。但page cache是內核空間的)rest

其中標準IO庫層用來提供IO buffer層,stdio是操做系統提供的標準IO庫,perlio是Perl提供的標準IO庫,在Perl中能夠選擇使用哪一種IO庫提供buffer。unix是perl io的最底層(就算是在win下也是unix層),它幾乎等價於直接操做文件描述符,至關因而純裸數據,沒有IO buffercode

模塊PerlIO::Layers提供了Perl在文件描述符到文件句柄的IO層次上的一些檢測功能,例如檢測文件句柄是否已打開,是否設置了autoflush,是否使用緩衝等等。對象

另外,從文件句柄到文件描述符中間的IO Buffer中的數據,能夠經過perl IO::Handle->flush()來刷,flush時,io buffer中未讀數據被丟棄,未寫數據將寫入到文件描述符從文件描述符到設備文件(如磁盤上的abc.log)中間的緩衝(如page cache),能夠經過操做系統的fsync()系統調用或perl IO::Handle->sync()來刷盤。blog

文件句柄、文件描述符的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。

相關文章
相關標籤/搜索