第九章 管理真實(shí)的程序(三) -文件

文件

絕大多數(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ǔ)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辖试,一起剝皮案震驚了整個濱河市辜王,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌罐孝,老刑警劉巖呐馆,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異莲兢,居然都是意外死亡汹来,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門改艇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來收班,“玉大人,你說我怎么就攤上這事谒兄∷よ耄” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵承疲,是天一觀的道長邻耕。 經(jīng)常有香客問我,道長燕鸽,這世上最難降的妖魔是什么兄世? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮啊研,結(jié)果婚禮上御滩,老公的妹妹穿的比我還像新娘。我一直安慰自己党远,他們只是感情好削解,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沟娱,像睡著了一般钠绍。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上花沉,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天柳爽,我揣著相機(jī)與錄音,去河邊找鬼碱屁。 笑死磷脯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的娩脾。 我是一名探鬼主播赵誓,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼柿赊!你這毒婦竟也來了俩功?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤碰声,失蹤者是張志新(化名)和其女友劉穎诡蜓,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胰挑,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蔓罚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞻颂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片豺谈。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贡这,靈堂內(nèi)的尸體忽然破棺而出茬末,到底是詐尸還是另有隱情,我是刑警寧澤盖矫,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布丽惭,位于F島的核電站,受9級特大地震影響炼彪,放射性物質(zhì)發(fā)生泄漏吐根。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一辐马、第九天 我趴在偏房一處隱蔽的房頂上張望拷橘。 院中可真熱鬧,春花似錦喜爷、人聲如沸冗疮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽术幔。三九已至,卻和暖如春湃密,著一層夾襖步出監(jiān)牢的瞬間诅挑,已是汗流浹背四敞。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拔妥,地道東北人忿危。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像没龙,于是被迫代替她去往敵國和親铺厨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容