When I run the code below I get
Can't use string ("F") as a symbol ref while "strict refs" in use at ./T.pl line 21.
where line 21 is
flock($fh, LOCK_EX);
What am I doing wrong?
#!/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;
}
What you’re doing wrong is using the string “F” as a filehandle. This
has never been something that’s worked; you could use a bareword as a
filehandle (
open FH, ...; print FH ...), or you could pass in anempty scalar and perl would assign a new open file object to that
variable. But if you pass in the string F, then you need to refer to
then handle as
F, not$fh. But, don’t do that.Do this instead:
We’re doing several things here. One, we’re not storing the
filehandle in a global. Global state makes your program extremely
difficult to understand — I had a hard time with your 10 line post —
and should be avoided. Just return the filehandle, if you want to
keep it around. Or, you can alias it like
opendoes:But really, this is a mess. Put this stuff in an object. Make
newopen and lock the file. Add a
writemethod. Done. Now you canreuse this code (and let others do the same) without worrying about
getting something wrong. Less stress.
The other thing we’re doing here is checking errors. Yup, disks can
fail. Files can be typo’d. If you blissfully ignore the return value
of open and flock, then your program may not be doing what you think
it’s doing. The file might not be opened. The file might not be
locked properly. One day, your program is not going to work properly
because you spelled “file” as “flie” and the file can’t be opened.
You will scratch your head for hours wondering what’s going on.
Eventually, you’ll give up, go home, and try again later. This time,
you won’t typo the file name, and it will work. Several hours will
have been wasted. You’ll die several years earlier than you should
because of the accumulated stress. So just
use autodieor writeorafter your system calls so that you get an error message whendie $!
something goes wrong!
Your script would be correct if you wrote
use autodie qw/open flockat the top. (Actually, you should also check thatseek close/
“print” worked or use
File::Slurp or
syswrite, since autodie can’t detect a failingprintstatement.)So anyway, to summarize:
Don’t
open $fhwhen$fhis defined. Writeopen my $fhtoavoid thinking about this.
Always check the return values of system calls. Make autodie do
this for you.
Don’t keep global state. Don’t write a bunch of functions that
are meant to be used together but rely on implicit preconditions
like an open file. If functions have preconditions, put them in
a class and make the constructor satisfy the preconditions.
This way, you can’t accidentally write buggy code!
Update
OK, here’s how to make this more OO. First we’ll do “pure Perl” OO
and then use Moose. Moose is
what I would use for any real work; the “pure Perl” is just for the
sake of making it easy to understand for someone new to both OO and
Perl.
Then, we can use the class in our main program:
Errors will throw exceptions, which can be handled with
Try::Tiny, and the YAML
file will stay locked as long as the instance exists. You can, of
course, have many LockedYAML objects around at once, that’s why we
made it OO.
And finally, the Moose version:
This is used similarly:
The Moose version is longer, but does a lot more runtime consistency
checking and is easier to enhance. YMMV.