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

时间:2011-07-27 13:44:07

标签: perl

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

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

第21行是

flock($fh, LOCK_EX);

我做错了什么?

#!/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;
}

4 个答案:

答案 0 :(得分:7)

你做错了是使用字符串“F”作为文件句柄。这个 从来没有像这样的东西;你可以用一个裸字作为 文件句柄(open FH, ...; print FH ...),或者你可以传入 空标量和perl会为其分配一个新的打开文件对象 变量。但是如果你传入字符串F,那么你需要参考 然后处理为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);
}

我们在这里做了几件事。一,我们不存储 全局中的文件句柄。全球州使您的计划非常出色 难以理解 - 我的10行帖子很难 - 应该避免。如果你愿意,只需返回文件句柄即可 保持它。或者,您可以像open那样对其进行别名:

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

open_yaml_with_lock(my $fh, 'filename');
write_yaml_with_lock($fh);

但实际上,这是一团糟。把这些东西放在一个物体里。制作new 打开并锁定文件。添加write方法。完成。现在你可以 重用这段代码(并让其他人做同样的事情)而不用担心 弄错了。减轻压力。

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

如果您在顶部写了use autodie qw/open flock seek close/,那么您的脚本将是正确的。 (实际上,你也应该检查一下 工作或使用“打印” File::Slurpsyswrite,因为autodie无法检测到失败的print语句。)

无论如何,总结一下:

  • 定义open $fh时不要$fh。写open my $fh给   避免考虑这个。

  • 始终检查系统调用的返回值。让autodie做   这适合你。

  • 不要保持全球状态。不要写一堆函数   意图是一起使用,但依赖于隐含的先决条件   像一个打开的文件。如果函数有前置条件,请将它们放入   一个类并使构造函数满足前提条件。   这样,您就不会意外地编写错误的代码!

<强>更新

好的,这是如何使这更多的OO。首先,我们将做“纯Perl”OO 然后使用Moose。麋是 我将用于任何实际工作; “纯粹的Perl”只是为了 为了让那些对OO和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
}

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

use LockedYAML;

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

$resource->write({ new => 'stuff' });

错误将抛出异常,可以使用 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;
}

类似地使用:

 use LockedYAML;

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

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

答案 1 :(得分:2)

来自文档:

 open FILEHANDLE,EXPR
  

如果FILEHANDLE是未定义的标量变量(或数组或散列)   element)为变量分配对新匿名的引用   filehandle,否则如果FILEHANDLE是表达式,则其值为   用作真正的文件句柄的名称。 (这被认为是   符号引用,所以“use strict'refs'”应该   生效。)

这里的Filehandle是一个表达式(“F”)所以它的值被用作你想要的真实文件句柄的名称。 (一个名为F的文件句柄)。然后......文档说“use strict'refs'”不应该有效,因为你使用F作为符号引用。

(第1行use strict;包含strict 'refs'。)

你刚才开始说:

  my $fh;

这样做会有效,因为$ fh会成为对新匿名文件句柄的引用,而Perl不会尝试将它用作符号引用。

这有效:

#!/usr/bin/perl

my $global_fh;

open_filehandle(\$global_fh);
use_filehandle(\$global_fh);

sub open_filehandle {
    my ($fh)=@_;

    open($$fh, ">c:\\temp\\testfile") || die;
}

sub use_filehandle {
    my($fh) = @_;

    # Print is pecular that it expects the next token to be the filehandle
    # or a simple scalar.  Thus, print $$fh "Hello, world!" will not work.
    my $lfh = $$fh;
    print $lfh "Hello, world!";   

    close($$fh);
}

或者你可以做其他海报建议的内容并直接使用$ _ [1],但这有点难以阅读。

答案 2 :(得分:2)

如果直接在sub中使用该值,它将起作用:

use strict;
use warnings;
use autodie;

my $fh;
yada($fh);
print $fh "testing, testing";

sub yada {
    open $_[0], '>', 'yada.gg';
}

或作为参考:

yada(\$fh);

sub yada {
    my $handle = shift;
    open $$handle, '>', 'yada.gg';
}

或者更好的是,返回一个文件句柄:

my $fh = yada($file);

sub yada {
    my $inputfile = shift;
    open my $gg, '>', $inputfile;
    return $gg;
}

答案 3 :(得分:1)

替换

my $fh = "F"; # text and also a ref in nonstrict mode

my $fh = \*F; # a reference, period

当然,最好还是使用词法文件句柄,就像open my $fd, ... or die ...一样,但这并不总是可行的,例如你有预定义的STDIN。在这种情况下,请在\*FD适合的任何地方使用$fd

还有一个旧脚本的情况,您必须注意全局FD的打开和关闭位置。