open函數除了> >> <
這三種最基本的文件句柄模式,還支持更豐富的操做模式,例如管道。其實bash shell支持的重定向模式,perl都支持,即便是2>&1
這種高級重定向模式,perl也有對應的模式。html
perl程序內部也支持管道,以便和操做系統進行交互。例如,將perl的輸出在程序內部就輸出給操做系統的命令,或者將操做系統的命令執行結果輸出給perl程序內部。因此,perl有2種管道模式:句柄到管道、管道到句柄。shell
例如,將perl print語句的輸出,交給操做系統的cat -n
命令來輸出行號。也就是說,下面的perl程序和cat -n
命令的效果是同樣的。bash
#!/usr/bin/perl open LOG,"| cat -n" or die "Can't open file: $!"; while(<LOG>){ print $_; }
再例如,將操做系統命令的執行結果經過管道交給perl文件句柄:app
#!/usr/bin/perl open LOG,"cat -n test.log |" or die "Can't open file: $!"; while(<LOG>){ print "from pipe: $_"; }
雖然只有兩種管道模式,但有3種寫法:函數
-|
|-
|
寫在左邊,表示句柄到管道,等價於|-
,|
寫在右邊,等價於管道到句柄,等價於-|
,能夠認爲"-"表明的就是外部命令上面第三點|
的寫法見上面的例子即可理解。而|-
和-|
是做爲open函數的模式參數的,如下幾種寫法是等價的:測試
open LOG, "|tr '[a-z]' '[A-Z]'"; open LOG, "|-", "tr '[a-z]' '[A-Z]'"; open LOG, "|-", "tr", '[a-z]', '[A-Z]'; open LOG, "cat -n '$file'|"; open LOG, "-|", "cat -n '$file'"; open LOG, "-|", "cat", "-n", $file;
並且,管道還能夠繼續傳遞給管道:操作系統
open LOG, "|tr '[a-z]' '[A-Z]' | cat -n";
可是涉及到兩個管道的時候,輸出到終端屏幕上時可能不太合意:scala
[root@xuexi perlapp]# perl 15.plx test.log [root@xuexi perlapp]# 1 A 2 B 3 C
如何讓輸出不附加在shell提示符後,我暫時也不知道如何作。指針
可是,輸出到文件中不會出現這樣的問題:code
open LOG, "|tr '[a-z]' '[A-Z]' | cat -n >test2.log";
[root@xuexi perlapp]# perl 15.plx test.log [root@xuexi perlapp]# cat test2.log 1 A 2 B 3 C
更多關於open和管道的解釋參見:Perl進程間通訊。
默認狀況下:
>
模式打開文件句柄時,會先截斷文件,也就是說沒法今後文件句柄關聯的文件中讀取原有數據,且還會清空原有數據>>
模式打開文件句柄時,首先會將指針指向文件的末尾以便追加數據,但沒法讀取該文件句柄對應的文件數據如何以"既可寫又可讀"的模式打開文件句柄?在Perl中能夠在模式前使用+
符號來實現。
結合"+"的模式有3種,都用來實現讀寫更新操做。它們的意義以下:
+<
:read-update,如open FH, "+<$file"
,能夠提供讀寫行爲。若是文件不存在,則open失敗(以read爲主,寫爲輔),若是文件存在,則文件內容保留,但IO的指針放在文件開頭,也就是說不管讀寫操做,都從開頭開始,寫操做會從指針位置開始覆蓋同字節數的數據。+>
:write-update,如open FH, "+>$file"
,能夠提供讀寫行爲。若是文件不存在,則建立文件(以write爲主,read爲輔)。若是文件存在,則截斷整個文件,所以這種方式是先將文件清空而後寫數據,再從中讀數據。+>>
:append-update,如open FH, "+>>$file"
,提供讀寫行爲。若是文件不存在,則建立(以append爲主,read爲輔),若是文件存在,則將IO指針放到文件尾部。通常狀況下,每一次讀操做以前都須要經過seek
將指針移動到文件的某個位置,而寫操做則老是追加到文件尾部並自動移動指針到結尾。通常來講,要同時提供讀寫操做,+<
是最可能須要的模式。
1.打開可供讀、寫、更新的文件句柄,但不截斷文件
open LOG,"+<","/tmp/test.log" or die "Couldn't open file: $!";
以下面的例子,say語句會將數據寫入到test.log的尾部,由於遍歷完test.log後,指針在文件的尾部。
#!/usr/bin/perl use 5.010; open LOG,"+<","test.log" or die "Couldn't open file: $!"; while(<LOG>){ print $_; } say LOG "from hello world";
但注意,若是將上面的say語句放進while循環,則會出現讀、寫錯亂的問題,由於+<
模式打開文件句柄時IO指針默認在文件的開頭:
#!/usr/bin/perl use 5.010; open LOG,"+<","test.log" or die "Couldn't open file: $!"; while(<LOG>){ print $_; say LOG "from hello world"; }
分析下這個錯亂:當讀取了第一行後,放置好指針位置,而後賦值給$_
並被print輸出,而後再寫入"from hello world",寫入的位置是指針的後面,它會直接更新後面對應數量的字符數。數一數"from hello world"的字符數量和替換掉的字符數量,會發現正好相等。
2.打開可供讀、寫、更新的文件句柄,但首先截斷文件
open LOG,"+>","/tmp/test.log" or die "Couldn't open file: $!";
由於首先會截斷文件,沒法直接去讀取內容。因此,這種操做模式,須要首先向文件中寫入數據,再去讀取數據。
#!/usr/bin/perl use 5.010; open LOG,"+>","test.log" or die "Couldn't open file: $!"; say LOG "from hello world1"; say LOG "from hello world2"; say LOG "from hello world3"; while(<LOG>){ say $_; }
3.打開可供讀、追加寫的文件句柄。它不會截斷文件。
open LOG,"+>>","/tmp/test.log" or die "Couldn't open file: $!";
由於追加寫模式會將指針放置在文件尾部,若是不將指針移動到文件的某個位置(可經過seek
來移動),將沒法讀出數據來。
例如:
#!/usr/bin/env perl use strict; use warnings; use 5.010; open LOG,"+>>","test.log" or die "Couldn't open file: $!"; say LOG "from hello world1"; say LOG "from hello world2"; say LOG "from hello world3"; while(<LOG>){ print "First: ", $_; # 啥也不輸出 } seek(LOG, 0, 0); # 將讀指針移動到文件開頭 while(<LOG>){ print "Second: ", $_; # 正常輸出 }
若是想要打開標準輸入、標準輸出,那麼可使用二參數格式的open,並將"-"指定爲文件名。例如:
# open LOG, "-"; # 打開標準輸入 open LOG, "<-"; # 打開標準輸入 open LOG, ">-"; # 打開標準輸出
沒有相似的直接打開標準錯誤輸出的方式。若是有一個文件名就是"-",這時想要打開這個文件而不是標準輸入或標準輸出,那麼須要將"-"文件名做爲open的第三個參數。
open LOG, "<", "-";
若是將open()函數打開文件句柄時的文件名指定爲undef,則表示建立一個匿名文件句柄,即臨時文件。這個臨時文件將建立在/tmp目錄下,建立完成後將當即被刪除,可是卻一直持有並打開這個文件句柄直到文件句柄關閉。這樣,這個文件就成了看不到卻仍被進程佔用的臨時文件。
何時才能用上打開就當即刪除的臨時文件?只讀或只寫的臨時文件都是沒有意義的,只有同時能讀寫的文件句柄纔是有意義的,因此open的模式須要指定爲+<
或+>
。顯然,+<
是更爲通用的讀、寫模式。
例如:
#!/usr/bin/perl use strict; use warnings; # 建立臨時文件 open my $tmp_file, '+<', undef or die "open filed: $!"; # 設置自動flush select $tmp_file; $| = 1;; # 這個臨時文件已經被刪除了 system("lsof -n -p $$ | grep 'deleted'"); # 寫入一點數據 say {$tmp_file} "Hello World1"; say {$tmp_file} "Hello World2"; say {$tmp_file} "Hello World3"; say {$tmp_file} "Hello World4"; # 指針移動到臨時文件的頭部來讀取數據 seek($tmp_file, 0, 0); select STDOUT; while(<$tmp_file>){ print "Reading from tmpfile: $_"; }
執行結果:
perl 22685 root 3u REG 0,2 0 108086391056997277 /tmp/PerlIO_JHnTx1 (deleted) Reading from tmpfile: Hello World1 Reading from tmpfile: Hello World2 Reading from tmpfile: Hello World3 Reading from tmpfile: Hello World4
若是將open()函數打開文件句柄時的文件名參數指定爲一個標量變量(的引用,即下面示例中標量前加上了反斜線),也就是再也不讀寫具體的文件,而是讀寫內存中的變量,這樣就實現了一個內存IO的模式。
#!/usr/bin/perl $text = "Hello World1\nHello World2\n"; # 打開內存文件以便讀取操做 open MEMFILE, "<", \$text or die "open failed: $!"; print scalar <MEMFILE>; # 提供內存文件以供寫入操做 $log = "" open MEMWRITE, ">", \$log; pritn MEMWRITE "abcdefg\n"; pritn MEMWRITE "ABCDEFG\n"; print $log;
若是內存文件操做的是STDOUT和STDERR這兩個特殊的文件句柄,若是須要從新打開它們,必定要先關閉它們再從新打開,由於內存文件不依賴於文件描述符,再次打開文件句柄不會覆蓋文件句柄。例如:
close STDOUT; open(STDOUT, ">", \$variable) or die "Can't open STDOUT: $!";
在shell中能夠經過>&
和<&
實現文件描述符的複製(duplicate)從而實現更高級的重定向。在perl中也一樣能實現,符號也同樣,只不過複製對象是文件句柄。
例如:
open LOG,">&STDOUT"
表示將寫入LOG文件句柄的數據重定向到STDOUT中。
shell中很經常使用的一個符號是>&FILENAME
或>FILENAME 2>&1
,它們都表示標準錯誤和標準輸出都輸出到FILENAME中。在perl中實現這種功能的方式爲:(注意dup目標使用\*
的方式,且不加引號)
open LOG,">","/dev/null" or die "Can't open filehandle: $!"; open STDOUT,">&",\*LOG or die "Can't dup LOG:$!"; open STDERR,">&",\*STDOUT or die "Can't dup STDOUT: $!";
或者簡寫一下:
open STDOUT,">","/dev/null" or die "Can't dup LOG:$!"; open STDERR,">&",\*STDOUT or die "Can't dup STDOUT: $!";
測試下:
use 5.010; open LOG,">>","/tmp/test.log" or die "Can't open filehandle: $!"; open STDOUT,">&",\*LOG or die "Can't dup LOG: $!"; open STDERR,">&",\*STDOUT or die "Can't dup STDOUT: $!"; say "hello world stdout default"; say STDOUT "hello world stdout"; say STDERR "hello world stderr";
會發現全部到STDOUT和STDERR的內容都追加到/tmp/test.log文件中。
若是在同一perl程序中,STDOUT和STDERR有多個輸出方向,那麼dup這兩個文件句柄以前,須要先將它們保存起來。須要的時候再還原回來:
# 保存STDOUT和STDERR到$oldout和OLDERR open(my $oldout, ">&STDOUT") or die "Can't dup STDOUT: $!"; open(OLDERR, ">&", \*STDERR) or die "Can't dup STDERR: $!"; # 實現標準錯誤、標準輸出都重定向到foo.out的功能,即"&>foo.out" open(STDOUT, '>', "foo.out") or die "Can't redirect STDOUT: $!"; open(STDERR, ">&STDOUT") or die "Can't dup STDOUT: $!"; # 還原回STDOUT和STDERR open(STDOUT, ">&", $oldout) or die "Can't dup \$oldout: $!"; open(STDERR, ">&OLDERR") or die "Can't dup OLDERR: $!";
由於這種高級重定向用的不多,因此很少作解釋。如需理解,可參考個人shell關於高級重定向的文章:完全搞懂shell的高級I/O重定向,或者直接參考Perl的高級重定向文章:Perl IO:IO重定向。