Effective Perl-chapter5

用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)
        )
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí),會(huì)觸發(fā)此異常。 O...
    我想起個(gè)好名字閱讀 5,971評(píng)論 0 9
  • 官網(wǎng) 中文版本 好的網(wǎng)站 Content-type: text/htmlBASH Section: User ...
    不排版閱讀 4,712評(píng)論 0 5
  • 一、Python簡(jiǎn)介和環(huán)境搭建以及pip的安裝 4課時(shí)實(shí)驗(yàn)課主要內(nèi)容 【Python簡(jiǎn)介】: Python 是一個(gè)...
    _小老虎_閱讀 6,333評(píng)論 0 10
  • 翻譯自 perl6maven.com exit,warn,die exit die Hello World Hel...
    焉知非魚閱讀 2,640評(píng)論 2 7
  • Perl 哲學(xué) Perl 是一種能“干實(shí)事”的語(yǔ)言。它靈活、寬容、可塑。在一名編程能者的手中,它可以 完成幾乎所有...
    firefive閱讀 1,508評(píng)論 1 11

友情鏈接更多精彩內(nèi)容