Как передать дескриптор файла в функцию? - PullRequest
4 голосов
/ 27 июля 2011

Когда я запускаю код ниже, я получаю

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

, где строка 21

flock($fh, LOCK_EX);

Что я делаю неправильно?

Ответы [ 4 ]

7 голосов
/ 27 июля 2011

То, что вы делаете неправильно, использует строку "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. Готово. Теперь вы можете использовать этот код (и позволить другим делать то же самое), не беспокоясь о что-то не так. Меньше стресса.

Другая вещь, которую мы здесь делаем, это проверка ошибок. Да, диски могут потерпеть поражение. Файлы могут быть опечатаны. Если вы блаженно игнорируете возвращаемое значение из открытых и стадных, то ваша программа может не делать то, что вы думаете это делает. Файл не может быть открыт. Файл может не быть заблокирован правильно. Однажды ваша программа не будет работать должным образом потому что вы записали «file» как «flie» и файл не может быть открыт. Вы будете часами чесать голову, задаваясь вопросом, что происходит. В конце концов, вы сдадитесь, пойдете домой и повторите попытку позже. Этот раз, Вы не будете опечатывать имя файла, и оно будет работать. Несколько часов будет были потрачены впустую. Ты умрешь на несколько лет раньше, чем должна из-за накопленного стресса. Так что просто use autodie или напишите or die $! после системных вызовов, чтобы вы получили сообщение об ошибке, когда что-то идет не так!

Ваш сценарий будет правильным, если вы напишите use autodie qw/open flock seek close/ вверху. (На самом деле, вы также должны проверить, что "печать" работал или использовать Файл :: Slurp или syswrite, поскольку autodie не может обнаружить ошибочный оператор print.

Так или иначе, чтобы подвести итог:

  • Не open $fh, если определено $fh. Напишите open my $fh в не думай об этом.

  • Всегда проверяйте возвращаемые значения системных вызовов. Заставь автодие сделать это для вас.

  • Не сохранять глобальное состояние. Не пишите кучу функций, которые предназначены для совместного использования, но полагаются на неявные предварительные условия как открытый файл. Если у функций есть предварительные условия, поместите их в класс и заставить конструктор удовлетворить предварительные условия. Таким образом, вы не можете случайно написать код ошибки!

Обновление

Хорошо, вот как это сделать больше ОО. Сначала мы сделаем "чистый Perl" OO и затем используйте Moose . Лось что бы я использовал для любой реальной работы; «чистый Perl» только для ради облегчения понимания для кого-то нового для ОО и 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' });

Ошибки будут вызывать исключения, которые могут быть обработаны с помощью Попробуйте :: Tiny и YAML файл останется заблокированным, пока экземпляр существует. Вы можете, из Конечно, у нас есть много объектов LockedYAML одновременно, поэтому мы сделал это ОО.

И, наконец, версия Moose:

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 длиннее, но обеспечивает гораздо большую согласованность во время выполнения проверка и его легче улучшить. YMMV.

2 голосов
/ 27 июля 2011

Если вы используете значение непосредственно в подпрограмме, оно будет работать:

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;
}
2 голосов
/ 27 июля 2011

Из документации:

 open FILEHANDLE,EXPR

Если FILEHANDLE - неопределенная скалярная переменная (или массив, или элемент хеша), переменной присваивается ссылка на новый анонимный дескриптор файла, в противном случае, если FILEHANDLE являетсяВыражение, его значение используется как имя требуемого реального дескриптора файла.(Это считается символьной ссылкой, поэтому «использовать строгие« ссылки »» должно , а не .) *

Файловый дескриптор здесь - это выражение ("F"), поэтому его значениеиспользуется как имя реального дескриптора файла, который вы хотите.(Файловый дескриптор называется F).И затем ... документация говорит, что "использовать строгие 'refs'" не должно быть в действительности, потому что вы используете 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)=@_;

    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], но это немного сложнее для чтения.

1 голос
/ 27 июля 2011

Заменить

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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...