目錄

名稱

perlopentut - Perl 中開啟檔案和管道的簡單範例

說明

每當您在 Perl 中對檔案執行 I/O 時,您會透過 Perl 中所謂的檔案控制代碼來執行。檔案控制代碼是外部檔案的內部名稱。open 函式的任務是建立內部名稱和外部名稱之間的關聯,而 close 函式的任務是中斷該關聯。

為了您的方便,Perl 會在您執行時設定幾個已經開啟的特殊檔案控制代碼。這些檔案控制代碼包括 STDINSTDOUTSTDERRARGV。由於這些檔案控制代碼是預先開啟的,因此您可以立即使用它們,而無需自行開啟它們

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>) { ... }

從這些範例中可以看到,STDOUTSTDERR 是輸出控制代碼,而 STDINARGV 是輸入控制代碼。它們全部使用大寫字母,因為它們是 Perl 保留的,就像 @ARGV 陣列和 %ENV hash 一樣。它們的外部關聯是由您的 shell 設定的。

您需要自行開啟其他每個檔案控制代碼。儘管有很多變體,但呼叫 Perl 的 open() 函式的最常見方式是使用三個參數和一個傳回值

OK = open(HANDLE, MODE, PATHNAME)

其中

OK

如果開啟成功,將會是某個已定義的值,但如果失敗,則為 undef

HANDLE

如果 open 函式成功,應該是一個未定義的標量變數,由該函式填入;

MODE

是存取模式和用於開啟檔案的編碼格式;

PATHNAME

是您要開啟的檔案的外部名稱。

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 參數中填入對應句柄的參照。

現在,您可以在該句柄上使用 readlinereadgetcsysread 等函式。最常見的輸入函式可能是看起來像運算子的函式

$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: $!";

現在您可以使用 printprintfsaywritesyswrite 中的任何一個寫入該檔案處理常式。

如上所述,如果檔案不存在,則附加模式開啟將為您建立檔案。但是,如果檔案已存在,則其內容是安全的,因為您將把新文字新增到舊文字的結尾。

另一方面,有時您想要覆蓋可能已存在在那裡的任何東西。在開始寫入檔案之前,您可以以唯寫模式開啟檔案,以清空檔案

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 相同,因為 ">" 會覆蓋現有檔案。

與附加模式一樣,當您以唯寫模式開啟檔案時,您現在可以使用 printprintfsaywritesyswrite 中的任何一個寫入該檔案處理常式。

讀寫模式呢?您可能應該假裝它不存在,因為以讀寫模式開啟文字檔案不太可能執行您想要執行的動作。有關詳細資訊,請參閱 perlfaq5

開啟二進位檔案

如果要開啟的檔案包含二進位資料而非文字字元,則 openMODE 參數會稍有不同。您不是指定編碼,而是告訴 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 本身相同的條款下重新分發和/或修改它。