用perl處理文件非常容易,perl能借助文件句柄接口處理幾乎所有形式的數(shù)據(jù)。通過文件句柄我們能完成大部分重要的任務(wù)。文件句柄還可以保存為普通的標(biāo)量變量,以便將來(lái)選擇要操作的對(duì)象
不要忽略文件測(cè)試操作符
所有文件測(cè)試默認(rèn)情況下使用變量$_
#獲取文件大小
my ($size) = (stat $filename)[7];
#對(duì)于這類的任務(wù),perl的文件測(cè)試操作符就是專為簡(jiǎn)化常見任務(wù)而設(shè)計(jì)的
my $size = -s $filename;
復(fù)用測(cè)試結(jié)果
如果想找出所有人為當(dāng)前運(yùn)行程序的用戶,并且權(quán)限為可執(zhí)行的文件,可以在grep中聯(lián)合使用多個(gè)文件測(cè)試
my @results = grep {-o and -x} glob '*';
#實(shí)際上,文件測(cè)試操作在幕后調(diào)用的是stat函數(shù),每次運(yùn)行文件測(cè)試,都會(huì)重新調(diào)用一次stat,上例中perl對(duì)$_調(diào)用了兩次stat
#因此,如果對(duì)同一個(gè)文件做多次文件測(cè)試操作,可以使用虛擬文件句柄 _
my @results = grep {-o and -x _} glob '*';
棧式文件測(cè)試
從perl 5.10 開始,已經(jīng)可以使用棧式文件測(cè)試了。即對(duì)同一個(gè)文件或文件句柄,可以同時(shí)進(jìn)行多項(xiàng)屬性測(cè)試
use 5.010;
if (-r -w $file) {
print "file is readable and writeable\n";
}
#老式寫法
if (-w $file and -r $file) {
print "file is readable and writeable\n";
}
#對(duì)于上一節(jié)的例子
my @results = grep {-o -x } glob '*';
始終以三項(xiàng)參數(shù)的形式調(diào)用open
#讀取文件
open my ($fh) ,'<',$read_file or die ... ;
#覆蓋模式
open my ($fh) ,'>',$read_file or die ... ;
#追加模式
open my ($fh) ,'>>',$read_file or die ... ;
采取不同方式讀取數(shù)據(jù)流
一般我們用行輸入操作符<>讀取數(shù)據(jù)流,如果是標(biāo)量上下文,就返回一行,如果是列表上下文,就返回?cái)?shù)據(jù)流中所有的數(shù)據(jù)
總體而言,一次讀取一行的方式在時(shí)間和內(nèi)存開銷上效率是最高的,而while (<>)這種隱式的寫法,在速度上和相對(duì)應(yīng)的顯式寫法時(shí)一樣的
open my ($fh) , '<' , $file or die;
while (<$fh>) {
... ;
}
#顯式寫法
while (defined (my $line = <$fh>)) {
... ;
}
也可以在foreach循環(huán)中使用類似的語(yǔ)法讀取整個(gè)文件內(nèi)容到內(nèi)存
foreach (<$fh>) {
... ;
}
一次全部讀入的方式自然要比一次一行的方式耗費(fèi)內(nèi)存,一次讀入也有其優(yōu)勢(shì)
print sort <$fh>; #打印排序后的每一行
如果需要同時(shí)訪問多行內(nèi)容,一次全部讀入的方式就不可或卻
#如找到包含apple的行時(shí),會(huì)將該行連同相聯(lián)的上下兩行一同打印出來(lái)
my @f = <$fh>;
foreach (0 .. $#f) {
if ($f[$_] =~ /\bapple\b/) {
my $lo = ($_ > 0) ? $_ - 1 : $_;
my $hi = ($_ < $#f) ? $_ + 1 : $_;
print map {"$_: $f[$_]"} $lo .. $hi;
}
}
#當(dāng)然也可以使用一次一行的方式
my @f;
@f[0 .. 2] = ("\n") x 3;
for ( ; ; ) {
@f[0 .. 2] = (@f[1,2],scalar(<$fh>) );
last if not defined $f[1];
if ($f[1] =~ /\bapple\b/) {
print map {($_ + $. - 1) . " :$f[$_]" } 0 .. 2;
}
}
文件slurp
有時(shí)候我們的需求很簡(jiǎn)單,只是想盡可能快地一次性讀取所有內(nèi)容,考慮將每行的分隔符都去掉后讀入
my $contents = do {
local $/;
open my ($fh1), '<',$file1 or die;
<$fh1>;
};
也可使用File::Slurp模塊替我們完成,只需一條函數(shù),便可把全部?jī)?nèi)容讀入標(biāo)量或者按行保存到數(shù)組值中
use File::Slurp;
my $text = read_file ('filename');
my @lines = read_file ('filename');
處理字符串的文件句柄
從perl 5.6開始,我們可以在字符串上打開一個(gè)文件句柄
從字符串讀
對(duì)于多行字符串,不用拿正則表達(dá)式切分各行。只要在該字串標(biāo)量的引用上打開一個(gè)文件句柄,然后像以往那樣從該句柄讀取數(shù)據(jù)即可
my $string = << 'multiline';
a
b
c
d
multiline
open my ($str_fh) , '<' ,\$string
my @results = grep /^[ae]$/, <$str_fh>;
寫入字符串
my $string = q{};
open my ($str_fh), '>',\$string;
print $str_fh "this is the last line\n";
用File::Spec或Path::Class處理文件路徑
用File::Spec提高可移植性
要構(gòu)建新的文件路徑,需要磁盤卷(有時(shí)不需要)、目錄以及文件名這些元素
my $volume = 'C:'; #卷
my $file = 'perl.exe'; #文件名
#由rootdir()開頭、逐個(gè)列出文件所在的目錄層次,然后用catdir連接,目錄間隔字符則會(huì)根據(jù)當(dāng)前系統(tǒng)決定
use File::Spec;
my $dir = catdir (rootdir(), qw (strawberry perl bin) ); #目錄名
#得到文件路徑三個(gè)部分,用catpath組合起來(lái)
my $full_path = catpath ($volume,$dir,$file);
#在linux系統(tǒng)中catpath會(huì)忽略磁盤卷的參數(shù),可以用undef代替
my $full_path = catpath (undef,$dir,$file);
盡可能選用Path::Class
基于File::Spec封裝而來(lái)的Path::Class模塊,為常見的路徑操作提供了更為便捷的方法
在windows上,直接將windows下的文件路徑給file函數(shù)即可,它會(huì)理解并做好一切
use Path::Class qw(file dir);
my $file = file ('C:/strawberry/perl/bin/perl.exe');
#該文件并不需要真實(shí)存在,$file對(duì)象不會(huì)對(duì)路徑做任何真實(shí)性驗(yàn)證,它只是按照文件系統(tǒng)規(guī)范構(gòu)造一條路徑而已
如果不是在windows系統(tǒng)上運(yùn)行程序,但還需要以windows上的路徑工作,可以選用foreign_file代替
my $file = foreign_file ('Win32', 'C:/strawberry/perl/bin/perl.exe');
轉(zhuǎn)換為其他系統(tǒng)的路徑,則可以使用as_foreign方法
my unix_path = $file -> as_foreign ('Unix');
將數(shù)據(jù)留于磁盤以節(jié)約內(nèi)存
現(xiàn)在數(shù)據(jù)集往往異常龐大,所要處理的數(shù)據(jù)總量很容易就會(huì)超過程序允許的的內(nèi)存大小
有一些對(duì)策是用以減少不必要的內(nèi)存開銷,接下來(lái)我們逐一介紹
逐行讀取文件
其實(shí)沒必要一次性加載所有文件內(nèi)容到內(nèi)存的,我們可以將整個(gè)文件讀入一個(gè)數(shù)組
open my ($fh) , '<', $file or die;
my @lines = <$fh>;
然而,你實(shí)際上并不同時(shí)需要所有數(shù)據(jù),那么對(duì)當(dāng)前行處理即可
open my ($fh) , '<', $file or die;
while (<$fh>) {
... ;
}
將大的哈希表保存到DBM文件
有這樣一種常見的情況,有時(shí)候我們需要根據(jù)某些關(guān)鍵字查找對(duì)應(yīng)關(guān)聯(lián)的一堆數(shù)據(jù),而當(dāng)這樣的關(guān)鍵字不計(jì)其數(shù)時(shí),每次查找數(shù)據(jù)都要加載內(nèi)存循環(huán)一遍。因此,我們可以在查詢之前,先把數(shù)據(jù)加載到DBM文件,這樣所做的查詢操作就從內(nèi)存搬到了硬盤上,大大節(jié)約了內(nèi)存
#運(yùn)行起來(lái)好像數(shù)據(jù)都雜內(nèi)存,但實(shí)際它們卻是保存在外部的數(shù)據(jù)庫(kù)文件,我們僅僅是將該文件綁定進(jìn)而通過哈希的方式進(jìn)行訪問而已
use Fcntl; #引入O_RDWR, O_CREAT常量
my ($lookup_file,$data_file) = @ARGV;
my $lookup = build_lookup($lookup_file);
open my ($data_fh) , '<', $data_file or die;
while (<$data_fh>) {
chomp;
my @row = split;
if (exists $lookup->{ $row[0]}) {
print "@row\n";
}
}
sub build_lookup {
my ($file) = @_;
open my ($lookup_fh), '<', $file or die;
require SDBM_File #等價(jià)于use SDBM_File,不同的地方于require Package在運(yùn)行時(shí)間加載
tie (my %lookup, 'SDBM_File', 'lookup.$$', ORDWR | O_CREAT,0666) or die "can't tie SDBM file 'filename' : $! ";
while ($lookup_fh) {
chomp;
my ($key,$value) = split;
$lookup{$key} = $value;
}
return \%lookup;
}
tie()此函數(shù)把一個(gè)變量和一個(gè)類綁定在一起,而該類提供了該變量的實(shí)現(xiàn)。它讓你創(chuàng)建一個(gè)看起來(lái)象普通變量的變量,但是在 變量的偽裝后面,它實(shí)際上是一個(gè)羽翼豐滿的 Perl 對(duì)象
你想打破變量和 類之間的關(guān)聯(lián),你可以 untie (松綁)那個(gè)變量
把文件當(dāng)作數(shù)組來(lái)讀取
如果覺得基于關(guān)鍵字查找的方式不夠靈活,可以試試Tie::File模塊,他將文件每一行當(dāng)作數(shù)組元素來(lái)處理,但并不會(huì)全部加載到內(nèi)存。我們可以在文件內(nèi)采用導(dǎo)航處理,就好像操作普通數(shù)組一樣;也可以在任何時(shí)候訪問文件中的任何一行
use Tie::File;
tie my @fortunes, 'Tie::File', $fortune_file or die "unable to tie $fortune_file";
foreach (1 .. 10) {
print $fortunes[rand @fortunes];
}
使用臨時(shí)文件和臨時(shí)目錄
如果沒有預(yù)先準(zhǔn)備好的文件,任何時(shí)候我們都可以自己寫一個(gè)臨時(shí)文件應(yīng)急。File::Temp模塊會(huì)自動(dòng)創(chuàng)建一個(gè)名字唯一的臨時(shí)文件,并在使用之后自動(dòng)清除。這種方式適合一次性使用的情況,比如對(duì)某個(gè)文件創(chuàng)建新的版本,可以先寫一個(gè)臨時(shí)文件,等全部?jī)?nèi)容更新完畢后,再重命名覆蓋原來(lái)的版本
use File::Temp qw(tempfile);
my ($fh,$file_name) = tempfile();
while(<>) {
print {$fh} uc $_;
}
$fh ->close;
rename $file_name => $final_name;
File::Temp還可以創(chuàng)建臨時(shí)目錄,存放一堆臨時(shí)文件
use File::Temp qw(tempdir);
use File::Spec::Functions;
use LWP::Simple qw(getstore);
my ($temp_dir) = tempdir(CLEANUP => 1);
my %searches = (
google => 'http://google.com/#h1',
yahho => 'http://search.yahho.com',
mircosoft => 'http://www.bing.com',
);
foreach my $research (keys %searches) {
getstore ($searches{$search},
catfile($temp_dir,$search)
)
}