文件
絕大多數(shù)程序和真實(shí)世界交互都是通過讀踊淳、寫文件的方式楼入,而Perl非常擅長處理文本彰居。
輸入和輸出
文件句柄代表了當(dāng)前狀態(tài)下的一個指定的輸入或輸出通道。每個Perl程序都有3個標(biāo)準(zhǔn)文件句柄可用:STDIN(程序的標(biāo)準(zhǔn)輸入)、STDOUT(程序的標(biāo)準(zhǔn)輸出)谷炸;STDERR(程序的錯誤輸出)北专。默認(rèn)情況下,函數(shù)print和say就是向STDOUT輸出內(nèi)容旬陡;而錯誤和告警信息則會輸出到STDERR拓颓。根據(jù)需要我們可以把標(biāo)準(zhǔn)輸出信息和錯誤信息分別輸出到不同的地方---一個輸出文件和一個錯誤日志。
使用內(nèi)置函數(shù)open來得到一個文件句柄描孟。如打開一個文件用來讀:
open my $fh, '<', 'filename' or die "Cannot read '$filename': $!\n";
第一個操作數(shù)是一個詞法變量驶睦,操作成功后變量將會包含一個文件句柄;第二個操作數(shù)是文件的操作類型匿醒,如讀场航、寫、追加廉羔;最后的操作數(shù)就是目標(biāo)文件溉痢。若打開文件失敗,die語句會拋出一個異常憋他,失敗的原因會包含在$!這個變量里孩饼。
文件操作有多種類型,比較重要的文件操作有:
< 讀
> 寫
>> 追加竹挡,若文件不存在則創(chuàng)建一個新文件
+< 讀和寫
-| 打開一個管道用于讀
|- 打開一個管道用于寫
你甚至可以為普通的變量創(chuàng)建文件句柄镀娶,這樣就可以使用句柄的方式讀寫變量:
open my $read_fh, '<', \$fake_input;
open my $write_fh, '>', \$captured_output;
do_something_awesome( $read_fh, $write_fh );
my $read;
open my $fh,">",\$read;
print $fh "good";
print $read;
#good
perldoc perlopentut查看有關(guān)open更多、更奇特的用法此迅。
你還記得autodie嗎汽畴?
本章節(jié)的所有例子都假設(shè)已經(jīng)使用use autodie;這樣就能省略錯誤檢查。如果你不使用這個特性耸序,那么請記得檢查所有系統(tǒng)調(diào)用的返回值。
****Unicode編碼鲁猩,IO層和文件模式****
在文件的操作模式上可以指定IO編碼坎怪,這樣就能在文件輸入(輸出)時自動進(jìn)行解碼(編碼)。例如廓握,如果你準(zhǔn)備讀取一個UTF-8編碼的文件搅窿,可以這么做:
open my $in_fh, '<:encoding(UTF-8)', $infile;
向文件寫入UTF-8編碼的內(nèi)容:
open my $out_fh, '>:encoding(UTF-8)', $outfile;
****雙參數(shù)的open****
老代碼經(jīng)常能看到雙參數(shù)的open(),文件操作模式和文件名會寫在一起:
open my $fh, "> $some_file" or die "Cannot write to '$some_file': $!\n";
Perl會從第二個參數(shù)中提取出文件的操作模式隙券。這里存在著安全風(fēng)險男应,如果這個參數(shù)是外部輸入的,那情況就會更糟娱仔。所以使用更加安全的三參數(shù)形式吧沐飘。
Perl還有一個全局文件句柄DATA,這個句柄在寫簡短的自包含數(shù)據(jù)的程序很有用。有興趣的使用perldoc perldata來了解更多細(xì)節(jié)耐朴。
****讀文件****
對于一個已經(jīng)打開的用于讀的文件句柄借卧,可以使用readline來讀取數(shù)據(jù),它的行為和鉆石操作符<>一樣筛峭。一個常見的用法就是在while()循環(huán)中讀取數(shù)據(jù):
open my $fh, '<', 'some_file';
while (<$fh>)
{
chomp;
say "Read a line '$_'";
}
在標(biāo)量語境中铐刘,readline讀取文件中的一行,并且返回讀取的內(nèi)容影晓;如果是文件結(jié)尾就返回undef(內(nèi)部使用eof來判斷是否是結(jié)尾)镰吵。下一次就迭代到下一行。上面的例子其實(shí)是下面這個的簡化版:
open my $fh, '<', 'some_file';
while (defined($_ = <$fh>))
{
chomp;
say "Read a line '$_'";
last if eof $fh;
}
為什么是while而不是for
因為for是列表語境挂签,列表語境下readline(或鉆石操作符<>)會先讀取整個文件的內(nèi)容疤祭,然后再進(jìn)行后續(xù)的處理;而在while中每次讀取一行竹握,會更省內(nèi)存画株。
讀取每一行時都會包含行尾標(biāo)志,行尾標(biāo)志根據(jù)平臺有所不同啦辐,可能是\n谓传,或者\(yùn)r,或者\(yùn)r\n芹关⌒可以使用chomp來移除行尾標(biāo)志。
通常讀取文件的代碼會類似這樣:
open my $fh, '<', $filename;
while (my $line = <$fh>)
{
chomp $line;
...
}
默認(rèn)情況下侥衬,Perl以文本模式操作文件诗祸。如果你要操作的是2進(jìn)制數(shù)據(jù)(如媒體文件或壓縮文件),請在執(zhí)行任何IO操作前使用轴总,binmode會強(qiáng)制Perl以純數(shù)據(jù)方式處理直颅,并且不做任何轉(zhuǎn)換。(例如不會將原始換行符轉(zhuǎn)換為當(dāng)前平臺的形式)
****寫文件****
要往一個文件句柄里寫內(nèi)容怀樟,使用print或say:
open my $out_fh, '>', 'output_file.txt';
print $out_fh "Here's a line of text\n";
say $out_fh "... and here's another";
****要注意中間沒有逗號功偿,句柄和下一個操作數(shù)之間沒有逗號。****
《Perl最佳實(shí)踐》中建議:用大括號包圍文件句柄可提高可讀性往堡。
****關(guān)閉文件****
文件使用完后應(yīng)顯式地關(guān)閉文件句柄(或者讓它超出作用域)械荷,這樣Perl就會關(guān)閉文件。顯式的關(guān)閉文件句柄有助你檢查代碼和發(fā)現(xiàn)問題虑灰。
通常吨瞎,可以使用autodie來幫助你做異常檢查:
use autodie qw( open close );
open my $fh, '>', $file;
...
close $fh;
****特殊的文件句柄變量****
每讀取一行,Perl就會增加變量$.的值穆咐,所以這個這個變量可以用來計數(shù)行數(shù)颤诀。
readline以變量$/的值作為行尾標(biāo)志字旭,通常我們不需要改變這個變量。但有時候為了達(dá)到一些神奇的效果着绊,就需要改變$/的值谐算。例如,如果一個文件的內(nèi)容條目是以2個空行作為分割標(biāo)志的归露,那么我們可以將$/設(shè)置為\n\n洲脂,這樣就能每次讀取一個條目,并且還能使用chomp操作來去除尾部的雙空行剧包。
Perl的輸出默認(rèn)會有緩沖行為恐锦,只有當(dāng)數(shù)據(jù)量超過一定閥值時,才會執(zhí)行IO疆液。緩沖可以提高IO的性能一铅,然而,有時候你也會需要禁止Perl的緩沖行為堕油。變量$|控制著當(dāng)前活動的文件句柄的緩沖行為潘飘,設(shè)置為非零值時,就會禁用緩沖行為掉缺。
自動刷新
文件默認(rèn)是完全緩沖的策略卜录。當(dāng)STDOUT連接到活動終端而不是另一個程序時,使用的行緩沖策略眶明,就是說Perl在每次遇到換行符時會刷新標(biāo)準(zhǔn)輸出艰毒。
使用全局變量來控制緩沖行為可能不太合適,因為會影響到你的其他代碼搜囱,所以更好的方法是丑瞧,對詞法變量句柄使用autoflush()方法:
open my $fh, '>', 'pecan.log';
$fh->autoflush( 1 );
IO::File提供的所有方法都可以在文件句柄上調(diào)用。(原因嘛蜀肘,你知道的绊汹。)
目錄和路徑
目錄的操作和文件類似,區(qū)別只是你不能寫入目錄扮宠。使用內(nèi)置函數(shù)opendir來打開一個目錄句柄:
opendir my $dirh, '/home/monkeytamer/tasks/';
使用readdir來讀取目錄灸促,行為類似readline,可以每次迭代遍歷目錄涵卵,也可以一次性將所有內(nèi)容賦值給數(shù)組:
# iteration
while (my $file = readdir $dirh)
{
...
}
# flatten into a list, assign to array
my @files = readdir $otherdirh;
#while循環(huán)中默認(rèn)變量是$_
opendir my $dirh, 'tasks/circus/';
while (readdir $dirh)
{
next if /^\./;
say "Found a task $_!";
}
例子中那個正則表達(dá)式是用來跳過unix類系統(tǒng)中以點(diǎn)開頭的文件和目錄。(隱藏文件荒叼,當(dāng)前和上級目錄)
readdir返回的名字是相對目錄自身來說的轿偎。也就是如果tasks/目錄下有3個文件,eat被廓、drink坏晦、be_monkey,那么readdir返回的就是eat、drink和be_monkey昆婿,而不是tasks/eat球碉, tasks/drink和task/be_monkey。
關(guān)閉目錄句柄仓蛆,可以使用closedir睁冬,或者讓它超出作用域。
****操作路徑****
Perl提供和解析unix風(fēng)格的路徑看疙,同時也會自動為你的操作系統(tǒng)或文件系統(tǒng)做轉(zhuǎn)換豆拨。比如你使用的是windows,你就可以使用路徑C:/My Documents/Robots/Bender/能庆,效果和C:/My Documents/Robots/Bender/是一樣的施禾。
盡管Perl使用是unix的路徑風(fēng)格,但是也可以很容易通過使用模塊來實(shí)現(xiàn)跨平臺搁胆。核心模塊File::Spec可以讓你安全弥搞、可移植性的操作文件路徑,而且還有著完善的文檔渠旁。
CPAN的Path::Class模塊有更友好的接口攀例,dir()函數(shù)用來創(chuàng)建一個目錄對象,file()函數(shù)用來創(chuàng)建一個文件對象:
use Path::Class;
my $meals = dir( 'tasks', 'cooking' );
my $file = file( 'tasks', 'health', 'robots.txt' );
my $lunch = $meals->file( 'veggie_calzone' );
my $robots_dir = $robot_list->dir;
my $dir_fh = $dir->open;
my $robots_fh = $robot_list->open( 'r' ) or die "Open failed: $!";
有興趣的可以看看模塊Path::Class和Path::Tiny一死。
文件操作
除了讀肛度、寫文件,你還可以進(jìn)行其他操作投慈。文件測試操作符統(tǒng)稱-X操作符承耿,用來檢測文件和目錄的屬性。要測試一個文件是否存在:
say 'Present!' if -e $filename;
-e操作符只有一個操作數(shù)伪煤,文件名或句柄(文件句柄加袋,目錄句柄)。如果文件存在抱既,表達(dá)式為真职烧,否則為假。perldoc -f -X會列出所有的測試操作符防泵,常見的有:
-f 若操作數(shù)是純文本文件則返回真
-d 若操作數(shù)是目錄件則返回真
-r 若操作數(shù)文件對應(yīng)當(dāng)前用戶可讀則返回真
-s 若操作數(shù)不是一個空文件則返回真
可以查看操作符對應(yīng)的文檔蚀之,比如perldoc -f -r。
內(nèi)置函數(shù)rename可以重命名一個文件或移動一個文件捷泞,它有2個操作數(shù)足删,文件舊的路徑和新路徑:
rename 'death_star.txt', 'carbon_sink.txt';
# or if you're stylish:
rename 'death_star.txt' => 'carbon_sink.txt';
Perl沒有復(fù)制文件的內(nèi)置函數(shù),但是 File::Copy提供了copy() move()函數(shù)锁右。內(nèi)置函數(shù)unlink可以刪除一個或多個文件失受。(注意:內(nèi)置delete函數(shù)是用來刪除一個哈希元素的讶泰,而不是用在文件系統(tǒng)上)這些函數(shù)都會在操作成功返回真值,出現(xiàn)錯誤時設(shè)置$!變量拂到。
Perl會跟蹤當(dāng)前的工作目錄痪署,默認(rèn)的是你啟動程序的活動目錄。核心模塊Cwd模塊的cwd()函數(shù)會返回當(dāng)前工作目錄兄旬。內(nèi)置函數(shù)chdir可以改變當(dāng)前工作目錄狼犯。搞清楚當(dāng)前工作目錄是使用相對路徑的基礎(chǔ)。