2011-07-27 48 views
4

当我运行下面的代码,我得到如何将文件句柄传递给函数?

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

回答

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声明。)

所以无论如何,概括地说:

  • 当定义$fh时,不要open $fh。请将open my $fh写入 避免考虑此问题。

  • 总是检查系统调用的返回值。让自动拨号做 这个给你。

  • 请勿保持全局状态。不要写一堆功能,这些功能可以一起使用,但像打开的文件一样依赖隐式前提条件 。如果函数具有先决条件,则将它们放在一个类中,并使构造函数满足前提条件。 这样,你不会不小心写出错误的代码!

更新

OK,这里是如何使这更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 
} 

然后,我们可以使用类在我们的主要程序:

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 

驼鹿版本是更长的时间,但可以做更多的运行时间一致性 检查和更容易提高。因人而异。

+1

真不可思议!它的作品=)我真的从你的文章中学到了很多东西!我不知道我可以做'return($ obj,$ fh);''并打开我的$ fh'。我故意删除了错误处理,以使脚本缩短为post =)我使用Log4Perl的'$ logger-> error_die()'。但我不知道如何将它变成一个对象。你会怎么做? –

+0

@Sandra Schlichting:已更新。 – jrockway

+1

另外,也许你真的想要KiokuDB和文件后端,而不是这个锁定的YAML文件。 KiokuDB会将数据存储为YAML文件,但也会执行事务,因此您不必执行排它锁定。看看search.cpan。 – jrockway

2

从文档:

open FILEHANDLE,EXPR 

如果FILEHANDLE是未定义的标量变量(或阵列或散列 元件)的变量被分配给一个新的匿名 文件句柄的引用,反之FILEHANDLE是一个表达式,它的值是 用作真正文件句柄的名字。 (这被认为是 符号引用,因此“使用严格‘参’”应 生效。)

文件句柄这里是表达式(“F”),从而itsvalue被用作名称你想要的真正的文件句柄。 (一个叫做F的文件句柄)。然后...文档中提到“严格使用'ref''不应该生效,因为您使用F作为符号参考。

use strict;第1行包括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)[email protected]_; 

    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]直接,但是这是一个有点难以阅读。

+0

应该如何解决? –

+1

添加了关于如何修复它的注释。你可以传递一个未定义的标量,或者关闭严格的参考(我不建议后者)。 –

+0

如果我将'my $ fh =“F”;'改为'my $ fh;'然后我得到'不能使用一个未定义的值作为符号参考在./T.pl行32.'这就是SEEK线。 –

2

如果直接在次使用的值,它会工作:

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; 
} 
+0

如果我这样做,那么如果'seek $ fh,0,SEEK_SET;'失败''不能使用一个未定义的值作为符号参考在./T.pl第33行。' –

+0

问题出在写函数中。阅读的作品。 –

+0

我明白了..好吧,如果你的文件句柄是未定义的,那么你的读取函数实际上并不工作。或者你没有正确保存你的文件句柄。 – TLP

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打开和关闭的位置。

+0

不要这样做。只需使用词法文件句柄! – jrockway