2011-07-27 82 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