Perl的IO操做(1):文件句柄

文件句柄

文件句柄用來對應要操做的文件系統中的文件,這麼說不太嚴謹,但比較容易理解。首先爲要打開的文件綁定文件句柄(稱爲打開文件句柄),而後在後續的操做中都經過文件句柄來操做對應的文件,最後關閉文件句柄。html

如不理解文件句柄的概念,可將文件句柄看做Linux中文件描述符的概念(固然,它們是不一樣的,Perl的文件句柄在層次上對應於Linux中的標準IO流)。例如特殊的STDIN、STDOUT、STDERR就是perl中預約義好的文件句柄,分別表示標準輸入、標準輸出、標準錯誤,要將它們對應到Linux上的話,它們是默認的文件描述符fd=0、fd=1和fd=2的字符串描述形式,而這幾個文件描述符分別對應文件系統中的/dev/stdin、/dev/stdout和/dev/stderr設備文件。也就是說,Linux上的perl中的文件句柄STDIN、STDOUT和STDERR默認關聯的文件是/dev/stdin、/dev/stdout和/dev/stderr。java

若是還不理解文件句柄,就把它想象成通道:perl程序中IO操做和磁盤上文件的通道。例如,print語句將數據經過某個通道輸出到對應的文件中。shell

文件句柄和文件描述符函數

實際上,文件句柄和文件描述符是有區別的。文件描述符是一個數值,表明操做系統所使用的裸數據流,文件描述符是文件句柄的核心,文件句柄能夠看做是文件描述符的更高一層次的封裝,好比提供了和描述符有關數據流的輸入、輸出的buffer緩衝。也就是說,文件句柄比文件描述符多一些額外的功能。編碼

咱們能夠爲任意要操做的文件定義一個文件句柄。一般,使用大寫字母做爲文件句柄的名稱。操作系統

例如,下面打開一個文件句柄LOG,這個文件句柄對應的文件是/tmp/a.log,操做模式是追加寫入(和shell中的追加劇定向是相同的意思)。而後向LOG文件句柄中輸入一段數據,最後關閉文件句柄。命令行

open LOG,">>/tmp/a.log";
print LOG "haha, hello world";
close LOG;

通常來講,打開了文件句柄後,在操做完成後要關閉文件句柄以便節省操做系統"打開文件數量限制"的資源。但perl有時候比較智能,會在某些時候自動幫咱們關掉文件句柄。並且,當打開一個文件句柄後,再次去打開這個文件句柄時,perl會先關閉這個文件句柄再打開這個文件句柄,這稱爲"文件句柄的reopen",它只是隱式地關閉並從新打開,perl並不認爲中間涉及了關閉操做(例如reopen時行號不會重置)。rest

打開文件句柄

要打開文件句柄,使用open函數。open函數的功能其實很豐富,若有須要,可去官方手冊查看:http://perldoc.perl.org/functions/open.htmlcode

打開(open)文件是有目的的:爲了讀取?爲了寫入?爲了追加寫入?這是操做模式。在open文件時,須要指明操做模式。此外,還要給定要關聯的文件路徑,路徑能夠是絕對路徑,也能夠是相對當前perl程序的相對路徑。htm

另外注意,文件句柄需惟一,不能出現重名。

例如:

open LOG1,">","/tmp/a.log";   # 以覆蓋寫入的方式打開文件/tmp/a.log
open LOG2,">>","/tmp/a.log";  # 以追加寫入的方式打開文件/tmp/a.log
open LOG3,"<","/tmp/a.log";   # 打開/tmp/a.log文件,以提供輸入源
open LOG4,"/tmp/a.log";       # 等價於上面的輸入,默認的模式就是輸入

另外一種寫法是將模式符號和目標文件放在一塊兒:

open LOG1,">/tmp/a.log"; 
open LOG2,">>/tmp/a.log";
open LOG3,"</tmp/a.log";

中間還能夠有空格:

open LOG1,"> /tmp/a.log"; 
open LOG2,">> /tmp/a.log";
open LOG3,"< /tmp/a.log";

能夠將目標文件賦值給一個變量,而後在open函數中使用變量名替換。

my $tmp_file = "/tmp/a.log";
open LOG1,">","$tmp_file";

若是要指明輸入、輸出的文件編碼,則使用上面將"模式和路徑分開"的方式。例如:

open LOG1,">:encoding(UTF-8)","/tmp/a.log";   # 以UTF-8編碼方式寫入數據
open LOG1,">>:encoding(UTF-8)","/tmp/a.log";
open LOG1,"<:encoding(UTF-8)","/tmp/a.log";   # 以UTF-8編碼方式讀入數據

須要注意,perl自身是沒法打開外部文件的,它須要請求操做系統內核,讓操做系統來打開文件。因此,打開文件正確、錯誤時,操做系統都會有相應的回饋信息。對於perl來講,open函數的返回值就表示正確、錯誤打開文件。因此,經過如下方式能夠判斷是否正確打開:

my $success = open LOG,">","/tmp/a.log";
if(!success){
    exit 1;
}

更好、更經常使用的方式是使用die:

open LOG,">","/tmp/a.log"
    or die "open file wrong: $!";

或者使用autodie功能,當捕獲到某些錯誤時,會自動調用die結束程序:

use autodie;
open LOG,">","/tmp/a.log";

沒法打開文件的可能緣由有不少,好比讀取時文件不存在,好比上級目錄不存在,好比無權限等等。操做系統會向perl報告這些錯誤,使用"$!"能夠引用操做系統向perl報告的錯誤,例如die "can't open file: $!";被觸發時的消息以下:

can't open file: No such file or directory at myperl.plx line 5.

上面的"No such file or directory at myperl.plx line 5."就是$!收集和整理後的錯誤信息。

同理,關閉文件句柄錯誤也能夠捕捉:

close DATA
    or die "Couldn't close file properly";

使用文件句柄:讀取文件數據

例如,要從test.log文件中讀取全部數據行,通常的流程以下:

#!/usr/bin/perl
use 5.010;

open LOG,"<","test.log"
    or die "open file wrong: $!"
while(<LOG>){
    chomp;
    say $_;
}

另外,從特殊文件句柄<STDIN><><ARGV>中讀取數據時,因爲它們是預約義好的,因此不須要先open。

使用文件句柄:寫入數據到文件

要向文件中寫入數據,可使用輸出語句,如print/say和printf,要寫多行的時候,可使用heredoc的方式。

在使用print/say/printf的時候,在這幾個關鍵字後面接上文件句柄即表示本輸出語句寫入到此文件句柄中。其實,當它們不指定文件句柄的時候,所採用的就是默認的文件句柄STDOUT。

例如,以追加模式寫入一行數據到test.log中。

#!/usr/bin/perl

use 5.010;

open LOG,">>","test.log"
    or die "Can't open file: $!";

say LOG "NEW LINE!";

再例如,向標準輸出、標準錯誤中輸出信息:

say STDOUT "NEW LINE!";
say STDERR "NEW LINE!";

選一個默認的輸出文件句柄

注意是選擇默認的輸出文件句柄,不適用於輸入的文件句柄。

默認狀況下的默認輸出文件句柄就是STDOUT,可是可使用select關鍵字本身選一個默認的輸出文件句柄。只是須要注意的是,再將內容輸出到自選的默認輸出文件句柄結束後,應該從新選回STDOUT。

例如,讀取某個文件的內容,追加劇定向輸出到另外一個文件中:

#!/usr/bin/perl

open LOG,">>","test1.log" or die "Can't open file: $!";

select LOG;
while(<>){
    print "Line $. from $ARGV: $_";
}
select STDOUT;
print "restored default filehandler: STDOUT\n";

而後執行該perl程序(程序名:15.plx),並傳遞a.log做爲命令行參數。做爲運行結果,會將a.log中的數據追加到test1.log文件中,並輸出一行內容到終端屏幕上。

$ perl 15.plx a.log
restored default filehandler: STDOUT

選擇默認的文件句柄後,上面while循環中的print,等價於print LOG ...

一般,選擇默認的文件句柄更經常使用於設置文件句柄是否要緩衝。例如,輸出到下面三個文件句柄(LOG/STDERR/STDOUT)的數據不會緩衝,而是直接輸出到文件句柄。

select    LOG; $| = 1;  # make unbuffered
select STDERR; $| = 1;  # make unbuffered
select STDOUT; $| = 1;  # make unbuffered

其中,控制輸出的緩衝變量爲$|,一般在使用管道、套接字的時候,可能不須要甚至不該該對數據進行緩衝,而是直接暴露給其它進程。

通常來講,在超過一個文件句柄須要關閉緩衝時,不會使用這種select XXX; $|=1的方式,而是導入IO::Handle模塊,而後使用它的autoflush(1)函數來實現關閉IO緩衝。

use IO::Handle;
FH1->autoflush(1);
FH2->autoflush(2);
FH3->autoflush(3);

autoflush(1)的功能等價於:

select( (select(NEWOUT), $| = 1 )[0] );

由於select()的返回值是當前標準輸出的文件句柄,而後內層的select的結果和$| = 1的結果構成一個匿名列表,選擇列表的第一個元素即以前標準輸出的文件句柄,將其做爲外層select的參數,即表示恢復了以前的文件句柄。(若是目前看不懂,請忽略)

關於IO Buffer

分爲兩種IO緩衝模式:block buffer、line buffer
block buffer是表示先積累必定數據量(好比一般是幾K大小)以後再輸出。line buffer是表示只有讀取到了換行符的時候才輸出。$|=1或者autoflush(1)都表示從block buffer(默認)切換成line buffer,而不是禁用(或關閉)buffer。
若是真要禁用buffer,可使用syswrite(),它會直接繞過buffer,也就是禁用buffer。

文件句柄變量

除了使用大寫字母(通常狀況下文件句柄都如此命名,稱爲裸句柄)的文件句柄,還可使用變量來命名文件句柄。

例如,使用變量表明的文件句柄:

my $rock_fh;
open $rock_fh,"<","/tmp/a.log"
    or die "Can't open file: $!";

或者:

open my $rock_fh,"<","/tmp/a.log"
    or die "Can't open file: $!";

使用變量文件句柄時:

while(<$rock_fh>){
    chomp;
    ...
}

不過有時候,使用文件句柄變量會產生歧義。例以下面的語句:

print $rock_fh;

perl並不知道$rock_fh是要輸出的列表數據仍是輸出的目標文件句柄。若是是目標文件句柄,這意味着print將$_寫入到文件句柄$rock_fh中。若是是要輸出的列表數據,因爲$rock_fh是一個已定義好的文件句柄,print將輸出它的引用(相似於:GLOB(oxABCDEF12))。

這時可使用大括號包圍文件句柄變量。

print {$rock_fh};
print {$rock_fh} "hello world";

最後須要注意的是,裸句柄是包變量,在整個文件內都是有效的,而變量方式的文件句柄只在代碼塊範圍內有效,出了本身的做用域範圍就失效(自動關閉)

目錄句柄

目錄句柄和文件句柄相似,能夠打開它,並讀取其中的文件列表。只不過須要使用:

  • opendir替換open函數
  • 使用readdir來替換readline,readdir返回一個列表
    • readdir不會遞歸到子目錄中
  • 使用closedir來替代close
  • 注意每一個Unix的目錄下都包含兩個特殊目錄...
opendir JAVAHOME,"/usr/local/java"
    or die "Can't open dir handler: $!";
foreach $file (readdir JAVAHOME){
    print "Filename: $file \n";
}
closedir JAVAHOME;
相關文章
相關標籤/搜索