Effective Perl-chapter5

用perl处理文件非常容易,perl能借助文件句柄接口处理几乎所有形式的数据。通过文件句柄我们能完成大部分重要的任务。文件句柄还可以保存为普通的标量变量,以便将来选择要操作的对象

不要忽略文件测试操作符

所有文件测试默认情况下使用变量$_

#获取文件大小
my ($size) = (stat $filename)[7];
#对于这类的任务,perl的文件测试操作符就是专为简化常见任务而设计的
my $size = -s $filename;

复用测试结果
如果想找出所有人为当前运行程序的用户,并且权限为可执行的文件,可以在grep中联合使用多个文件测试

my @results = grep {-o and -x} glob '*';
#实际上,文件测试操作在幕后调用的是stat函数,每次运行文件测试,都会重新调用一次stat,上例中perl对$_调用了两次stat

#因此,如果对同一个文件做多次文件测试操作,可以使用虚拟文件句柄 _
my @results = grep {-o and -x _} glob '*';

栈式文件测试
从perl 5.10 开始,已经可以使用栈式文件测试了。即对同一个文件或文件句柄,可以同时进行多项属性测试

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";
}

#对于上一节的例子
my @results = grep {-o -x } glob '*';

始终以三项参数的形式调用open

#读取文件
open my ($fh) ,'<',$read_file or die ... ;

#覆盖模式
open my ($fh) ,'>',$read_file or die ... ;

#追加模式
open my ($fh) ,'>>',$read_file or die ... ;

采取不同方式读取数据流

一般我们用行输入操作符<>读取数据流,如果是标量上下文,就返回一行,如果是列表上下文,就返回数据流中所有的数据
总体而言,一次读取一行的方式在时间和内存开销上效率是最高的,而while (<>)这种隐式的写法,在速度上和相对应的显式写法时一样的

open my ($fh) , '<' , $file or die;
while (<$fh>) {
        ... ;
}

#显式写法
while (defined (my $line = <$fh>)) {
        ... ;
}

也可以在foreach循环中使用类似的语法读取整个文件内容到内存

foreach (<$fh>) {
        ... ;
}

一次全部读入的方式自然要比一次一行的方式耗费内存,一次读入也有其优势

print sort <$fh>;        #打印排序后的每一行

如果需要同时访问多行内容,一次全部读入的方式就不可或却

#如找到包含apple的行时,会将该行连同相联的上下两行一同打印出来
my @f = <$fh>;
foreach (0 .. $#f) {
        if ($f[$_] =~ /\bapple\b/) {
                my $lo = ($_ > 0) ? $_ - 1 : $_;
                my $hi = ($_ < $#f) ? $_ + 1 : $_;
                print map {"$_: $f[$_]"} $lo .. $hi;
        }
}

#当然也可以使用一次一行的方式
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
有时候我们的需求很简单,只是想尽可能快地一次性读取所有内容,考虑将每行的分隔符都去掉后读入

my $contents = do {
        local $/;
        open my ($fh1), '<',$file1 or die;
        <$fh1>;
};

也可使用File::Slurp模块替我们完成,只需一条函数,便可把全部内容读入标量或者按行保存到数组值中

use File::Slurp;
my $text = read_file ('filename');
my @lines = read_file ('filename');

处理字符串的文件句柄

从perl 5.6开始,我们可以在字符串上打开一个文件句柄

从字符串读
对于多行字符串,不用拿正则表达式切分各行。只要在该字串标量的引用上打开一个文件句柄,然后像以往那样从该句柄读取数据即可

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提高可移植性
要构建新的文件路径,需要磁盘卷(有时不需要)、目录以及文件名这些元素

my $volume = 'C:';      #卷
my $file = 'perl.exe';    #文件名
#由rootdir()开头、逐个列出文件所在的目录层次,然后用catdir连接,目录间隔字符则会根据当前系统决定
use File::Spec;
my $dir = catdir (rootdir(), qw (strawberry perl bin) );  #目录名

#得到文件路径三个部分,用catpath组合起来
my $full_path = catpath ($volume,$dir,$file);

#在linux系统中catpath会忽略磁盘卷的参数,可以用undef代替
my $full_path = catpath (undef,$dir,$file);

尽可能选用Path::Class
基于File::Spec封装而来的Path::Class模块,为常见的路径操作提供了更为便捷的方法

在windows上,直接将windows下的文件路径给file函数即可,它会理解并做好一切

use Path::Class qw(file dir);
my $file = file ('C:/strawberry/perl/bin/perl.exe');
#该文件并不需要真实存在,$file对象不会对路径做任何真实性验证,它只是按照文件系统规范构造一条路径而已

如果不是在windows系统上运行程序,但还需要以windows上的路径工作,可以选用foreign_file代替

my $file = foreign_file ('Win32', 'C:/strawberry/perl/bin/perl.exe');

转换为其他系统的路径,则可以使用as_foreign方法

my unix_path = $file -> as_foreign ('Unix');

将数据留于磁盘以节约内存

现在数据集往往异常庞大,所要处理的数据总量很容易就会超过程序允许的的内存大小
有一些对策是用以减少不必要的内存开销,接下来我们逐一介绍

逐行读取文件
其实没必要一次性加载所有文件内容到内存的,我们可以将整个文件读入一个数组

open my ($fh) , '<', $file or die;
my @lines = <$fh>;  

然而,你实际上并不同时需要所有数据,那么对当前行处理即可

open my ($fh) , '<', $file or die;
while (<$fh>) {
        ... ;
}

将大的哈希表保存到DBM文件
有这样一种常见的情况,有时候我们需要根据某些关键字查找对应关联的一堆数据,而当这样的关键字不计其数时,每次查找数据都要加载内存循环一遍。因此,我们可以在查询之前,先把数据加载到DBM文件,这样所做的查询操作就从内存搬到了硬盘上,大大节约了内存

#运行起来好像数据都杂内存,但实际它们却是保存在外部的数据库文件,我们仅仅是将该文件绑定进而通过哈希的方式进行访问而已
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        #等价于use SDBM_File,不同的地方于require Package在运行时间加载
        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()此函数把一个变量和一个类绑定在一起,而该类提供了该变量的实现。它让你创建一个看起来象普通变量的变量,但是在 变量的伪装后面,它实际上是一个羽翼丰满的 Perl 对象
你想打破变量和 类之间的关联,你可以 untie (松绑)那个变量

把文件当作数组来读取
如果觉得基于关键字查找的方式不够灵活,可以试试Tie::File模块,他将文件每一行当作数组元素来处理,但并不会全部加载到内存。我们可以在文件内采用导航处理,就好像操作普通数组一样;也可以在任何时候访问文件中的任何一行

use Tie::File;
tie my @fortunes, 'Tie::File', $fortune_file or die "unable to tie $fortune_file";

foreach (1 .. 10) {
        print $fortunes[rand @fortunes];
}

使用临时文件和临时目录
如果没有预先准备好的文件,任何时候我们都可以自己写一个临时文件应急。File::Temp模块会自动创建一个名字唯一的临时文件,并在使用之后自动清除。这种方式适合一次性使用的情况,比如对某个文件创建新的版本,可以先写一个临时文件,等全部内容更新完毕后,再重命名覆盖原来的版本

use File::Temp qw(tempfile);

my ($fh,$file_name) = tempfile();
while(<>) {
        print {$fh} uc $_;
}
$fh ->close;
rename $file_name => $final_name;

File::Temp还可以创建临时目录,存放一堆临时文件

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)
        )
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • ORA-00001: 违反唯一约束条件 (.) 错误说明:当在唯一索引所对应的列上键入重复值时,会触发此异常。 O...
    我想起个好名字阅读 5,169评论 0 9
  • 官网 中文版本 好的网站 Content-type: text/htmlBASH Section: User ...
    不排版阅读 4,364评论 0 5
  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,719评论 0 10
  • 翻译自 perl6maven.com exit,warn,die exit die Hello World Hel...
    焉知非鱼阅读 2,462评论 2 7
  • Perl 哲学 Perl 是一种能“干实事”的语言。它灵活、宽容、可塑。在一名编程能者的手中,它可以 完成几乎所有...
    firefive阅读 1,357评论 1 11