如何将文件句柄传递给函数?

San*_*ing 4 perl

当我运行下面的代码时,我得到了

Can't use string ("F") as a symbol ref while "strict refs" in use at ./T.pl line 21.
Run Code Online (Sandbox Code Playgroud)

第21行是

flock($fh, LOCK_EX);
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

#!/usr/bin/perl

use strict;
use warnings;
use Fcntl ':flock', 'SEEK_SET'; # file locking
use Data::Dumper;
# use xx;

my $file = "T.yaml";
my $fh = "F";
my $obj = open_yaml_with_lock($file, $fh);

$obj->{a} = 1;

write_yaml_with_lock($obj, $fh);

sub open_yaml_with_lock {
    my ($file, $fh) = @_;

    open $fh, '+<', $file;
    flock($fh, LOCK_EX);
    my $obj = YAML::Syck::LoadFile($fh);

    return $obj;
}

sub write_yaml_with_lock {
    my ($obj, $fh) = @_;

    my $yaml = YAML::Syck::Dump($obj);
    $YAML::Syck::ImplicitUnicode = 1;
    seek $fh,0, SEEK_SET;   # seek back to the beginning of file

    print $fh $yaml . "---\n";
    close $fh;
}
Run Code Online (Sandbox Code Playgroud)

jro*_*way 7

你做错了是使用字符串"F"作为文件句柄.这从来都不是有用的东西; 你可以使用一个裸字作为文件句柄(open FH, ...; print FH ...),或者你可以传入一个空标量,perl会为该变量分配一个新的打开文件对象.但是如果你传入字符串F,那么你需要引用然后处理as F,而不是$fh.但是,不要这样做.

改为:

sub open_yaml_with_lock {
    my ($file) = @_;

    open my $fh, '+<', $file or die $!;
    flock($fh, LOCK_EX) or die $!;

    my $obj = YAML::Syck::LoadFile($fh); # this dies on failure
    return ($obj, $fh);
}
Run Code Online (Sandbox Code Playgroud)

我们在这里做了几件事.一,我们不是将文件句柄存储在全局中.全球状态使你的程序非常难以理解 - 我的10行帖子很难 - 应该避免.如果你想保留它,只需返回文件句柄即可.或者,你可以像它一样别名open:

sub open_yaml_with_lock {
    open $_[0], '+<', $_[1] or die $!;
    ...
}

open_yaml_with_lock(my $fh, 'filename');
write_yaml_with_lock($fh);
Run Code Online (Sandbox Code Playgroud)

但实际上,这是一团糟.把这些东西放在一个物体里.请new 打开并锁定该文件.添加write方法.完成.现在您可以重用此代码(并让其他人也这样做),而不必担心出错.更少的压力.

我们在这里做的另一件事是检查错误.是的,磁盘可能会失败.文件可能被拼写错误.如果你幸福地忽略了open和flock的返回值,那么你的程序可能没有做你认为它正在做的事情.该文件可能无法打开.该文件可能未正确锁定.有一天,您的程序无法正常工作,因为您将"文件"拼写为"flie"并且无法打开该文件.几个小时你会抓挠头脑,想知道发生了什么.最终,你会放弃,回家,稍后再试.这一次,你不会错误地输入文件名,它会起作用.几个小时就会被浪费掉.由于累积的压力,你会比你应该早几年死去.所以只是在您的系统调用之后use autodie写入or die $!,以便在出现问题时收到错误消息!

如果你use autodie qw/open flock seek close/在顶部写的话,你的脚本是正确的.(实际上,您还应检查"print"是否有效或使用 File ::syswrite Slurp,或者 因为autodie无法检测到失败的print语句.)

无论如何,总结一下:

  • 不要open $fh$fh定义时.写,open my $fh以避免考虑这一点.

  • 始终检查系统调用的返回值.让autodie为您完成此操作.

  • 不要保持全球状态.不要编写一堆意味着要一起使用但依赖于隐式前提条件(如打开文件)的函数.如果函数具有前置条件,则将它们放在类中并使构造函数满足前提条件.这样,你就不会意外地写出错误的代码!

更新

好的,这是如何使这更多的OO.首先,我们将做"纯Perl"OO,然后使用Moose.穆斯是我用于任何实际工作的东西; "纯Perl"只是为了让对于OO和Perl都不熟悉的人容易理解.

package LockedYAML;
use strict;
use warnings;

use Fcntl ':flock', 'SEEK_SET';
use YAML::Syck;

use autodie qw/open flock sysseek syswrite/;

sub new {
    my ($class, $filename) = @_;
    open my $fh, '+<', $filename;
    flock $fh, LOCK_EX;

    my $self = { obj => YAML::Syck::LoadFile($fh), fh => $fh };
    bless $self, $class;
    return $self;
}

sub object { $_[0]->{obj} }

sub write {
    my ($self, $obj) = @_;
    my $yaml = YAML::Syck::Dump($obj);

    local $YAML::Syck::ImplicitUnicode = 1; # ensure that this is
                                            # set for us only

    my $fh = $self->{fh};

    # use system seek/write to ensure this really does what we
    # mean.  optional.
    sysseek $fh, 0, SEEK_SET;
    syswrite $fh, $yaml;

    $self->{obj} = $obj; # to keep things consistent
}
Run Code Online (Sandbox Code Playgroud)

然后,我们可以在主程序中使用该类:

use LockedYAML;

my $resource = LockedYAML->new('filename');
print "Our object looks like: ". Dumper($resource->object);

$resource->write({ new => 'stuff' });
Run Code Online (Sandbox Code Playgroud)

错误将抛出异常,可以使用Try :: Tiny处理 ,并且只要实例存在,YAML文件就会保持锁定状态.当然,你可以同时拥有许多LockedYAML对象,这就是为什么我们把它做成OO.

最后,穆斯版本:

package LockedYAML;
use Moose;

use autodie qw/flock sysseek syswrite/;

use MooseX::Types::Path::Class qw(File);

has 'file' => (
    is       => 'ro',
    isa      => File,
    handles  => ['open'],
    required => 1,
    coerce   => 1,
);

has 'fh' => (
    is         => 'ro',
    isa        => 'GlobRef',
    lazy_build => 1,
);

has 'obj' => (
    is         => 'rw',
    isa        => 'HashRef', # or ArrayRef or ArrayRef|HashRef, or whatever
    lazy_build => 1,
    trigger    => sub { shift->_update_obj(@_) },
);

sub _build_fh {
    my $self = shift;
    my $fh = $self->open('rw');
    flock $fh, LOCK_EX;
    return $fh;
}

sub _build_obj {
    my $self = shift;
    return YAML::Syck::LoadFile($self->fh);
}

sub _update_obj {
    my ($self, $new, $old) = @_;
    return unless $old; # only run if we are replacing something

    my $yaml = YAML::Syck::Dump($new);

    local $YAML::Syck::ImplicitUnicode = 1;

    my $fh = $self->fh;
    sysseek $fh, 0, SEEK_SET;
    syswrite $fh, $yaml;

    return;
}
Run Code Online (Sandbox Code Playgroud)

这类似地使用:

 use LockedYAML;

 my $resource = LockedYAML->new( file => 'filename' );
 $resource->obj; # the object
 $resource->obj( { new => 'object' }); # automatically saved to disk
Run Code Online (Sandbox Code Playgroud)

Moose版本更长,但运行时一致性检查更多,并且更容易增强.因人而异.