Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

SOLVED: Looking for suitable spells to get open to return a filehandle from a module

by talexb (Chancellor)
on Nov 28, 2016 at 04:20 UTC ( [id://1176674]=perlquestion: print w/replies, xml ) Need Help??

talexb has asked for the wisdom of the Perl Monks concerning the following question:

I'm working on what I imagined would be a simple module to provide versioned access to files (this is something that VMS provides), but it seems I've run up against a challenging (to me) situation with opening files in a module, and passing the file handle back to the caller as open does.

Here's some code that demonstrates the problem:

#!perl use strict; use warnings; use Extra; { { open ( my $fh, '>', 'normal-open.txt' ) or die "1. Open failed: $!"; defined $fh or die "1. Filehandle not defined"; print $fh "Normal open works fine.\n"; close ( $fh ); } { Extra::open ( my $fh, '>', 'extra-open.txt' ) or die "2. Open failed: $!"; defined $fh or die "2. Filehandle not defined"; print $fh "Extra open works fine.\n"; close ( $fh ); } }
And the Extra module is here:
package Extra; use Symbol; sub open { my ( $fh, $direction, $filename ) = @_; $fh = Symbol::gensym; open ( $fh, $direction, $filename ); } 1;

The first block works fine, and the second block returns from, but the filehandle is undefined.

I had a good look at the code for File::Temp, and saw where it uses the call to Symbol::gensym for Perls before 5.6, so I tried adding that in. It makes no difference -- I'm using Perl 5.22.1 anyway.

My plan is to hook the call to open and seamlessly handle open a versioned file using the requested file name as a basis -- I've also tried using sysopen, without any success. There's some magic going on that I don't understand.

Alex / talexb / Toronto

Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Replies are listed 'Best First'.
Re: Looking for suitable spells to get open to return a filehandle from a module
by Athanasius (Archbishop) on Nov 28, 2016 at 04:37 UTC

    Hello talexb,

    You are passing in $fh as a scalar, then overwriting it in the line $fh = Symbol::gensym;, so $fh works correctly inside Extra::open but remains undef in the caller. You need to pass in a reference, and dereference it accordingly within Extra::open:

    { my $fh; Extra::open ( \$fh, '>', 'extra-open.txt' ) or die "2. Open failed: $!"; defined $fh or die "2. Filehandle not defined"; print $fh "Extra open works fine.\n"; close ( $fh ); }
    package Extra; use Symbol; sub open { my ( $fh, $direction, $filename ) = @_; $$fh = Symbol::gensym; open ( $$fh, $direction, $filename ); } 1;

    In my (minimal) testing, this writes the correct messages to the two output files, and no error is generated.

    Hope that helps,

    Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

      package Extra; use Symbol; sub open { my ( $fh, $direction, $filename ) = @_; $$fh = Symbol::gensym; open ( $$fh, $direction, $filename ); } 1;

      I think this should work without explicit references:

      sub open { my (undef, $direction, $filename)=@_; $_[0]=Symbol::gensym; open ($_[0],$direction,$filename); }

      The trick is not to copy the alias in @_, but to directly use the alias. Maybe you need to extend open to have a prototype of ($$$).

      Anyway, I would prefer a modified open function to return a handle or undef instead of a boolean, so:

      sub open { my ($direction,$filename)=@_; open my $h,$direction,$filename or return; # alternatively: or die "Can't open $filename: $!"; return $h; }

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        What is the effect of the invocation of Symbol::gensym? This code seems to work just fine without it both with aliasing and with an automatic reference via a prototype (also minimally tested):
        t_extra_open.pl:

        use warnings; use strict; use Extra; { my $filename = 'extra-open.txt'; Extra::open my $fh, '>', $filename or die "opening '$filename' for write: $!"; defined $fh or die "write filehandle not defined"; print $fh "Extra open works just fine \n"; print $fh "at ", scalar(localtime), "\n"; close $fh or die "closing '$filename' after write"; undef $fh; Extra::open $fh, '<', $filename or die "opening '$filename' for read: $!"; defined $fh or die "read filehandle not defined"; print '<<', <$fh>, '>>'; close $fh or die "closing '$filename' after read"; }
        Extra.pm:
        package Extra; sub open { # works my (undef, $direction, $filename) = @_; return open $_[0], $direction, $filename; } # sub open (\$@) { # works # # my ($sr_fh, $direction, $filename) = @_; # # return open $$sr_fh, $direction, $filename; # # } 1;
        (Tested under ActiveState 5.8.9 and Strawberry 5.14.4.1.)


        Give a man a fish:  <%-{-{-{-<

        I would prefer a modified open function to return a handle
        I agree that creating the filehandle within the sub and returning it to the caller is by far the simplest and the best solution (unless I miss something from the OP requirement).

      Brilliant. Thanks!

      Alex / talexb / Toronto

      Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

Re: Looking for suitable spells to get open to return a filehandle from a module
by kikuchiyo (Hermit) on Nov 28, 2016 at 13:10 UTC

    I'm working on what I imagined would be a simple module to provide versioned access to files (this is something that VMS provides)

    May I ask why do you think you need this?

    Even more impudently, may I suggest that you actually DON'T need this?

    The reason I say this is that in a previous job I had to use a system that was similar to what you're trying to do, and it was an unmitigated disaster.

    The VMS filesystem had a (mis)feature that files were versioned, that is, every filename had the pattern NAME.EXT;VERSION. If you accessed the file as NAME.EXT, you automatically got the latest version, but you could use other versions if you used the explicit version number. The point is that this was a built-in operating system feature and it worked transparently everywhere.

    The geniuses at $previous_job must have missed this feature and implemented something like it when they ported their codebase to Unix back in the eighties - in a half-assed, imperfect way. They used NAME.EXT.VERSION filenames in a regular Unix filesystem, which meant that the mechanism to open/save the latest version only worked if you used their specially modified tools. If you wanted to use the regular unix commands (cp, mv, ln...), you had to explicitly refer to the files as whatever.1, whatever.2 etc., and the filesystem was full of garbage from the old versions.

    But even if the such a system could work perfectly, transparently, it would still be inferior to proper version control systems, of which we have several mature, highly performant, feature-rich implementations.

    The point here is that version control makes sense on the level of projects, not individual files. I'd argue that a versioning system that depends on individually versioned files (like VMS's scheme, or CVS) is actively harmful and worse that using nothing. The single biggest difference between SVN and CVS is that SVN uses a single version number for the entire project which gets bumped when any file is modified in the project, and this feature elevates it to a viable and usable VCS.

    TL;DR use a proper version control system.

      This is a fair question, and I appreciate that it has the potential to be a disaster.

      I'm running scripts that do some ETL processing, and it would be handy to be able to version the result reports so that I could go back and see how this run's results compared to the previous result's runs. The tricky part is creating something relatively lightweight that I can sneak onto the command line, rather than remember to save the previous run's results (mv results.txt results.txt-5, for example).

      I've also been meaning to contribute something to CPAN for far too long, and this is something relatively innocuous that might be useful to someone, somewhere. I could be using git for this -- but I need to stretch a little, and write something useful.

      Alex / talexb / Toronto

      Thanks PJ. We owe you so much. Groklaw -- RIP -- 2003 to 2013.

        It's good to know that you are aware of the pitfalls.

        Still, I'd recommend that you use a VCS (git or mercurial) under the hood for this. They are optimized to store and retrieve previous versions (that is their primary job after all), they give you tools to compare versions, and they give you context for each version you commit (or your module commits).

        Yes, you could say that you don't really need all that, you just need something that hides and automatizes that mv results.txt results.txt-5 step, but little projects like this tend to accrete features and use cases, and before you know, you're stuck with an underdesigned, metastatised monster of a system.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1176674]
Approved by Athanasius
Front-paged by ww
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others contemplating the Monastery: (5)
As of 2024-04-25 14:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found