Beefy Boxes and Bandwidth Generously Provided by pair Networks
Syntactic Confectionery Delight
 
PerlMonks  

Re: file testing is hard

by dpmott (Scribe)
on Dec 01, 2003 at 05:03 UTC ( [id://311165]=note: print w/replies, xml ) Need Help??


in reply to file testing is hard

I've had to deal with this from time to time. I also cover this in a PERL class that I teach at my company.

What I've learned, through various approaches, is that you want to depend on the result of open() (or sysopen()). A file might exist, for instance, because -e "file" returns true, but you still might not be able to open it (because, perhaps, you don't have read permissions for it, only list permissions on a UNIX system).

Also, if it's important to read the file, even when you can't write to it, then you should handle reading and writing separately (as shown below). If you must be able to do both, then don't bother testing for the read-only case -- open for read+update, and fail if that doesn't work.

So, when I read your problem statement, I'm left with the impression that you might be working with an INI file, which you'll create if missing, update if writable, use if read-only, and ignore if none of the above. (If that's not right, feel free to take or leave the following as you see fit).

Here's how I would go about it:
# load_ini # returns undef on error, hashref on success sub load_ini($) { my ($ini) = @_; # first, check for all of those special conditions # that you wanted to avoid, like directories, etc: return undef if ( -d $ini or ! -r $ini or ! -f $ini ); my $fh = new FileHandle($ini); return undef unless defined $fh; # read INI here, represent it in a data struct my $ini_data = parse_ini($fh); # return handy structure (could be an object) # that keeps the INI filename in it for later use return { ini => $file, data => $ini_data }; } # load_ini_safe # same as load_ini, but never "fails" sub load_ini_safe($) { my ($ini) = @_; # provide a default if load_ini() fails # This would prevent you from saving to the INI file # if we load defaults. But see below... # return load_ini($ini) # or { ini => undef, ini_data => default_ini() }; # This will allow you to SAVE the ini settings if you # ONLY have write permissions... (yeah, odd case, # that). return load_ini($ini) or { ini => $ini, ini_data => default_ini() }; } # save_ini # returns boolean 1 on success, 0 on failure sub save_ini($) { my ( $ini_struct ) = @_; # this is a hash slice, btw... my ( $ini, $ini_data ) = %{ $ini_struct }{ qw/ini ini_data/ }; # check for a valid filename # (could be undef, see load_ini_safe for details) return 0 unless $ini; # If we keep the filename around from load_ini_safe, # then we have to perform the same checks as in # load_ini(). # If we only keep the filename if we could open it, # then we don't need to perform these tests return 0 if ( -d $ini or ! -r $ini or ! -f $ini ); # Now, try to open the file for writing. my $fh = new FileHandle(">$ini"); return 0 unless defined $fh; # now write the contents out to the filehandle # presumably, this returns 1 on success, 0 on failure return write_ini( $fh, $ini_data ); } # get_default_ini_data # provide default INI hashref sub default_ini() {} # parse_ini # converts text from filehandle into INI hashref sub parse_ini($) {} # write_ini # writes in-memory INI data structure to file as text sub write_ini($\%) {}
So, depending on what you want (i.e. load_ini or load_ini_safe), you could keep those separate or combine them or get rid of load_ini_safe. I broke them up to show a division of labor, since I was unclear about just which steps were important to you. Note that this has a small advantage over opening a file in read+update mode -- if it's read-only, then you can load it. If it's not writable, then writing fails (maybe silently in your app). If you only have write permissions for the file, you could load defaults internally and write those out (not very useful, but there it is). You get to completely avoid the error handling for trying to open a file for read+update when loading and/or saving, which would fail for both the load and the save cases if the file were read-only or write-only. Hope this helps... :)

Replies are listed 'Best First'.
Re: Re: file testing is hard
by DrHyde (Prior) on Dec 01, 2003 at 09:36 UTC
    What I've learned, through various approaches, is that you want to depend on the result of open() (or sysopen()). A file might exist, for instance, because -e "file" returns true, but you still might not be able to open it
    But you then go on to use -r in your code to check if a file is readable with the current effective UID/GID. I was going to point you at exactly that operator to solve your "file exists, can I read it" problem. The various -X operators cover all of the various situations the OP mentioned.
      True, that.

      In practice, I usually dispense with such filetests and just go straight for the open(). But then, I usually program on win32, where that's okay.

      I seem to remember, though, that it's perfectly legal to open directory entries, block/char special files, etc on a UNIX filesystem. I included those specific tests to cover those cases. You (probably) don't want to read your persistent INI from a serial port, nor save it to a directory inode.

      I didn't check for read/write permissions, because open() will do that. Also, it encourages checking the return value of open(), which was most of my point -- it avoids code like this:
      if ( -r $file ) { open(FILE, $file); # hmmm... is the file open or not? my @contents = <FILE>; close FILE; # process @contents; }
Re: Re: file testing is hard
by jamgill (Acolyte) on Dec 02, 2003 at 01:50 UTC

    So, when I read your problem statement, I'm left with the impression that you might be working with an INI file, which you'll create if missing, update if writable, use if read-only, and ignore if none of the above. (If that's not right, feel free to take or leave the following as you see fit).

    close ;) it isn't an INI file and i'm in UNIX, and i want to bail if none of the above. from reading above and the Camel, sysopen limits things sufficiently that I shall not have to test for all those various possibilities. To make sure that the $file wasn't a socket or directory or link and that the script's UID can write it ... or that the script's UID can create the file if it doesn't exist was turning into a waterfall of if elsif statements that would work, if i needed to be real specific. for example, if i needed to make sure that i'm opening up and fork, or something (i've never done that, i'm not of legal age for that sort of thing in my country) ... then i would appreciate all that precise testing. But for just opening up a file, I can rely on the fact that sysopen can't open up a directory, or a pipe (yet still allows me easy access to the resulting file's permissions). Hey, that's gravvy :)

    that said, i'm still digesting the code sample and write up. Thank you so much for taking the time to explain. Thanks also to PodMaster for directing me to check out sysopen. I've learned something new :)

    jamgill

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://311165]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (2)
As of 2024-04-19 19:38 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found