perlopentut - Perl 中開啟檔案和管道的簡單範例
每當您在 Perl 中對檔案執行 I/O 時,您會透過 Perl 中所謂的檔案控制代碼來執行。檔案控制代碼是外部檔案的內部名稱。open
函式的任務是建立內部名稱和外部名稱之間的關聯,而 close
函式的任務是中斷該關聯。
為了您的方便,Perl 會在您執行時設定幾個已經開啟的特殊檔案控制代碼。這些檔案控制代碼包括 STDIN
、STDOUT
、STDERR
和 ARGV
。由於這些檔案控制代碼是預先開啟的,因此您可以立即使用它們,而無需自行開啟它們
print STDERR "This is a debugging message.\n";
print STDOUT "Please enter something: ";
$response = <STDIN> // die "how come no input?";
print STDOUT "Thank you!\n";
while (<ARGV>) { ... }
從這些範例中可以看到,STDOUT
和 STDERR
是輸出控制代碼,而 STDIN
和 ARGV
是輸入控制代碼。它們全部使用大寫字母,因為它們是 Perl 保留的,就像 @ARGV
陣列和 %ENV
hash 一樣。它們的外部關聯是由您的 shell 設定的。
您需要自行開啟其他每個檔案控制代碼。儘管有很多變體,但呼叫 Perl 的 open() 函式的最常見方式是使用三個參數和一個傳回值
OK = open(HANDLE, MODE, PATHNAME)
其中
如果開啟成功,將會是某個已定義的值,但如果失敗,則為 undef
;
如果 open
函式成功,應該是一個未定義的標量變數,由該函式填入;
是存取模式和用於開啟檔案的編碼格式;
是您要開啟的檔案的外部名稱。
open
函式的複雜性主要在於 MODE 參數可以採用許多可能的值。
在向您展示如何開啟檔案之前,最後一件事:開啟檔案(通常)不會自動在 Perl 中鎖定它們。請參閱 perlfaq5 以了解如何鎖定。
如果您要從文字檔案中讀取,請先以唯讀模式開啟它,如下所示
my $filename = "/some/path/to/a/textfile/goes/here";
my $encoding = ":encoding(UTF-8)";
my $handle = undef; # this will be filled in on success
open($handle, "< $encoding", $filename)
|| die "$0: can't open $filename for reading: $!";
與 shell 一樣,在 Perl 中使用 "<"
以唯讀模式開啟檔案。如果成功,Perl 會為您配置一個全新的檔案句柄,並在先前未定義的 $handle
參數中填入對應句柄的參照。
現在,您可以在該句柄上使用 readline
、read
、getc
和 sysread
等函式。最常見的輸入函式可能是看起來像運算子的函式
$line = readline($handle);
$line = <$handle>; # same thing
由於 readline
函式會在檔案結尾或發生錯誤時傳回 undef
,因此您有時會看到它以這種方式使用
$line = <$handle>;
if (defined $line) {
# do something with $line
}
else {
# $line is not valid, so skip it
}
您也可以直接對未定義的值使用 die
$line = <$handle> // die "no input found";
但是,如果遇到檔案結尾是預期且正常的事件,您不希望僅因為輸入用盡就結束。相反地,您可能只想退出輸入迴圈。然後,您可以測試實際錯誤是否導致迴圈終止,並採取相應的措施
while (<$handle>) {
# do something with data in $_
}
if ($!) {
die "unexpected error while reading from $filename: $!";
}
關於編碼的注意事項:每次都必須指定文字編碼可能有點麻煩。若要設定 open
的預設編碼,以便您不必每次都提供編碼,您可以使用 open
實用程式
use open qw< :encoding(UTF-8) >;
執行此操作後,您可以安全地省略開啟模式的編碼部分
open($handle, "<", $filename)
|| die "$0: can't open $filename for reading: $!";
但切勿在未先設定預設編碼的情況下使用單獨的 "<"
。否則,Perl 無法知道您擁有眾多文字檔案風味中的哪一種,而且 Perl 將不知道如何正確地將檔案中的資料對應到它可以使用的實際字元。其他常見的編碼格式包括 "ASCII"
、"ISO-8859-1"
、"ISO-8859-15"
、"Windows-1252"
、"MacRoman"
,甚至 "UTF-16LE"
。有關編碼的更多資訊,請參閱 perlunitut。
當您想要寫入檔案時,您必須先決定如何處理該檔案的任何現有內容。您在此處有兩個基本選擇:保留或覆寫。
如果您想要保留任何現有內容,那麼您想要以附加模式開啟檔案。就像在 shell 中一樣,在 Perl 中您使用 ">>"
以附加模式開啟現有檔案。">>"
會在檔案不存在時建立檔案。
my $handle = undef;
my $filename = "/some/path/to/a/textfile/goes/here";
my $encoding = ":encoding(UTF-8)";
open($handle, ">> $encoding", $filename)
|| die "$0: can't open $filename for appending: $!";
現在您可以使用 print
、printf
、say
、write
或 syswrite
中的任何一個寫入該檔案處理常式。
如上所述,如果檔案不存在,則附加模式開啟將為您建立檔案。但是,如果檔案已存在,則其內容是安全的,因為您將把新文字新增到舊文字的結尾。
另一方面,有時您想要覆蓋可能已存在在那裡的任何東西。在開始寫入檔案之前,您可以以唯寫模式開啟檔案,以清空檔案
my $handle = undef;
my $filename = "/some/path/to/a/textfile/goes/here";
my $encoding = ":encoding(UTF-8)";
open($handle, "> $encoding", $filename)
|| die "$0: can't open $filename in write-open mode: $!";
在這裡,Perl 的運作方式與 shell 相同,因為 ">"
會覆蓋現有檔案。
與附加模式一樣,當您以唯寫模式開啟檔案時,您現在可以使用 print
、printf
、say
、write
或 syswrite
中的任何一個寫入該檔案處理常式。
讀寫模式呢?您可能應該假裝它不存在,因為以讀寫模式開啟文字檔案不太可能執行您想要執行的動作。有關詳細資訊,請參閱 perlfaq5。
如果要開啟的檔案包含二進位資料而非文字字元,則 open
的 MODE
參數會稍有不同。您不是指定編碼,而是告訴 Perl 您的資料是以原始位元組表示。
my $filename = "/some/path/to/a/binary/file/goes/here";
my $encoding = ":raw :bytes"
my $handle = undef; # this will be filled in on success
然後像之前一樣開啟,根據需要選擇 "<"
、">>"
或 ">"
open($handle, "< $encoding", $filename)
|| die "$0: can't open $filename for reading: $!";
open($handle, ">> $encoding", $filename)
|| die "$0: can't open $filename for appending: $!";
open($handle, "> $encoding", $filename)
|| die "$0: can't open $filename in write-open mode: $!";
或者,你可以用這種方式將現有句柄變更為二進制模式
binmode($handle) || die "cannot binmode handle";
這對於 Perl 已為你開啟的句柄特別方便。
binmode(STDIN) || die "cannot binmode STDIN";
binmode(STDOUT) || die "cannot binmode STDOUT";
你也可以傳遞 binmode
一個明確的編碼,以便在執行中變更它。這不完全是「二進制」模式,但我們仍然使用 binmode
來執行它
binmode(STDIN, ":encoding(MacRoman)") || die "cannot binmode STDIN";
binmode(STDOUT, ":encoding(UTF-8)") || die "cannot binmode STDOUT";
一旦你已在正確的模式中正確開啟二進制檔案,你就可以使用與在文字檔案中使用的所有相同的 Perl I/O 函數。但是,你可能希望對輸入使用固定大小的 read
,而不是可變大小的 readline
。
以下是複製二進制檔案的範例
my $BUFSIZ = 64 * (2 ** 10);
my $name_in = "/some/input/file";
my $name_out = "/some/output/flie";
my($in_fh, $out_fh, $buffer);
open($in_fh, "<", $name_in)
|| die "$0: cannot open $name_in for reading: $!";
open($out_fh, ">", $name_out)
|| die "$0: cannot open $name_out for writing: $!";
for my $fh ($in_fh, $out_fh) {
binmode($fh) || die "binmode failed";
}
while (read($in_fh, $buffer, $BUFSIZ)) {
unless (print $out_fh $buffer) {
die "couldn't write to $name_out: $!";
}
}
close($in_fh) || die "couldn't close $name_in: $!";
close($out_fh) || die "couldn't close $name_out: $!";
Perl 也讓你將檔案句柄開啟到外部程式或 shell 指令,而不是檔案。你可以這麼做,以便將資料從你的 Perl 程式傳遞到外部指令進行進一步處理,或從另一個程式接收資料,以便你的 Perl 程式處理。
進入指令的檔案句柄也稱為 管道,因為它們使用與 Unix 管道類似的進程間通訊原則。此類檔案句柄在其外部端有一個作用中的程式,而不是靜態檔案,但在其他各方面,它就像一個更典型的基於檔案的檔案句柄一樣運作,本文前面討論的所有技術都同樣適用。
因此,您可以使用與開啟檔案相同的 open
呼叫來開啟一個管道,將第二個 (MODE
) 參數設定為特殊字元,表示輸入或輸出管道。對於讓 Perl 程式從外部程式讀取資料的檔案代號,請使用 "-|"
;對於讓檔案代號將資料傳送至該程式的檔案代號,請使用 "|-"
。
假設您希望 Perl 程式處理儲存在稱為 unsorted
的鄰近目錄中的資料,其中包含多個文字檔。您還希望程式在開始處理這些檔案之前,將所有這些檔案的內容排序成一個單一的、依字母順序排序的獨特行清單。
您可以透過開啟一個普通檔案代號到每個檔案中,逐漸建立一個包含您以這種方式載入的所有檔案內容的記憶體陣列,最後在載入完所有檔案後,對該陣列進行排序和篩選來執行此操作。或者,您可以透過直接開啟一個管道到其輸出,將所有合併和排序卸載到作業系統自己的 sort
指令,並更快地開始工作。
以下是它的外觀
open(my $sort_fh, '-|', 'sort -u unsorted/*.txt')
or die "Couldn't open a pipe into sort: $!";
# And right away, we can start reading sorted lines:
while (my $line = <$sort_fh>) {
#
# ... Do something interesting with each $line here ...
#
}
open
的第二個參數 "-|"
使其成為一個讀取管道到一個獨立程式,而不是一個檔案的普通檔案代號。
請注意,open
的第三個參數是一個包含程式名稱 (sort
) 及其所有參數的字串:在本例中,-u
指定唯一的排序,然後是一個指定要排序的檔案的檔案萬用字元。產生的檔案代號 $sort_fh
的作用就像一個唯讀 ("<"
) 檔案代號,您的程式可以從中讀取資料,就像它開啟到一個普通的單一檔案一樣。
繼續前面的範例,假設您的程式已完成處理,而結果會儲存在稱為 @processed
的陣列中。您希望將這些行印到一個稱為 numbered.txt
的檔案中,並附上格式整齊的行號欄。
當然,你可以自行撰寫程式碼來執行此動作,或者,你可以再次將這項工作交給另一個程式。在此情況下,執行自訂選項 -n
以啟用行號的 cat
應可解決問題
open(my $cat_fh, '|-', 'cat -n > numbered.txt')
or die "Couldn't open a pipe into cat: $!";
for my $line (@processed) {
print $cat_fh $line;
}
在此,我們使用第二個 open
參數 "|-"
,表示指派給 $cat_fh
的檔案控點應為寫入管線。接著,我們可以使用它,就像使用僅寫入的普通檔案控點一樣,包括 print
資料的基本功能。
請注意,指定我們希望傳送管線的命令的第三個參數,會設定 cat
透過 ">"
符號將其輸出重新導向到檔案 numbered.txt
。這可能會開始看起來有點棘手,因為如果在 open
的第二個參數中顯示相同的符號,則會表示完全不同的意思!但在此第三個參數中,它只是 Perl 將開啟管線的 shell 命令的一部分,而 Perl 本身並未賦予其任何特殊意義。
對於開啟管線,Perl 提供呼叫 open
的選項,其中包含一個清單,包含所需的命令及其所有參數作為個別元素,而不是將它們組合成單一字串,如上述範例所示。例如,我們可以像這樣表述第一個範例中的 open
呼叫
open(my $sort_fh, '-|', 'sort', '-u', glob('unsorted/*.txt'))
or die "Couldn't open a pipe into sort: $!";
當你以這種方式呼叫 open
時,Perl 會直接呼叫指定的命令,繞過 shell。因此,shell 不會嘗試詮釋命令參數清單中的任何特殊字元,這可能會產生不必要的影響。這可以讓 open
呼叫更安全、更不容易出錯,在將變數作為參數傳入,或僅僅參照其中包含空格的檔案名稱等情況下非常有用。
但是,當你確實想要將有意義的元字元傳遞給 shell 時,例如此處最後一個 unsorted/*.txt
參數中的 "*"
,你無法使用這個替代語法。在此情況下,我們透過 Perl 方便的內建函數 glob
來解決這個問題,它會將其參數評估為檔案名稱清單,而且我們可以安全地將結果清單直接傳遞到 open
,如上所示。
另請注意,以這種清單形式表示管線命令參數並非在所有平台上都可行。它將可在提供真實 fork
函數的任何基於 Unix 的作業系統(例如 macOS 或 Linux)上運行,以及在執行 Perl 5.22 或更高版本時在 Windows 上運行。
針對 open
的完整文件提供了此函數的完整參考,超出此處涵蓋的最佳實務基礎。
版權所有 2013 年 Tom Christiansen;現由 Perl5 Porters 維護
此文件是免費的;您可以在與 Perl 本身相同的條款下重新分發和/或修改它。