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

Hi monks,

I have the code below to check if an image file exists:
my $dir = "F:/images"; my $image = 'swim.jpg'; #my $image = ''; #my $image = undef; my $file = "$dir/$image"; if (-e $file) { # printed even if $image is '' or undef print "Yes image exists\n"; } else { print "No image doesn't exist\n"; }
How is -e $file true when $image is the empty string '' or even undef? How do I check if a file really exists i.e. true only if the file is in the directory? I could do the following but I'm not sure it's the right way:
if ($image and -e $file) { # printed even if $image is '' or undef print "Yes image exists\n"; } else { print "No image doesn't exist\n"; }

Replies are listed 'Best First'.
Re: Check if file exists
by roboticus (Chancellor) on May 06, 2018 at 14:33 UTC

    If $image is '' or undefined, then $file would be "F:/images/". If the directory F:/images exists, then -e would be true. If you used "use warnings;" you'd at least get a warning in the undefined case.

    I'd suggest also using -f instead of -e, as -f would be true only if it exists and is a file rather than a directory:

    $ perl u.pl Use of uninitialized value $file in concatenation (.) or string at u.p +l line 16. Use of uninitialized value $file in concatenation (.) or string at u.p +l line 21. OK, missing OK, missing OK, OK $ cat u.pl use strict; use warnings; my $f1; my $f2=''; my $f3='README'; my $dir='./xyz'; print chk($dir, $f1), ", ", chk2($dir,$f1),"\n"; print chk($dir, $f2), ", ", chk2($dir,$f2),"\n"; print chk($dir, $f3), ", ", chk2($dir,$f3),"\n"; sub chk { my ($dir, $file) = @_; return -e "$dir/$file" ? "OK" : "missing"; } sub chk2 { my ($dir, $file) = @_; return -f "$dir/$file" ? "OK" : "missing"; }

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

      Thanks!

      Tried and rhe results are as expected.

      I'm curious why is -e is what I usually see for file existence checking and not -f?
        I'm curious why is -e is what I usually see for file existence checking and not -f?

        I'd say it's probably because -f can be a little too restrictive:

        $ perl -le 'print -f "/dev/null" ? "IS" : "is NOT", " a file"' is NOT a file

        but it's also true that sometimes -e gets overused when a more restrictive check would have been better, for example at least checking ! -d.

        Update: Just to clarify what I meant with the above example: If a user is supposed to supply a filename to a program, then in a lot of cases the program shouldn't reject /dev/null and just work with it as if it was a regular file. Of course there might be cases where a program would want to accept only regular files - but then again, -f also returns true if what's being tested is a symbolic link to a file, so one would have to check for that specially too.

Re: Check if file exists
by Athanasius (Archbishop) on May 06, 2018 at 14:31 UTC
    How is -e $file true when $image is the empty string '' or even undef?

    -e (or any of the other filetest functions documented here) accepts either a filehandle or a directory handle as its argument. When $image is empty or undefined, the assignment my $file = "$dir/$image"; initialises $file to just $dir/, which -e accepts as a directory handle — and since that directory exists, -e returns true.

    (Note that if warnings are enabled, the concatenation of a string with undef will result in a warning like this:

    Use of uninitialized value $image in concatenation (.) or string at -e + line...

    which is a useful indicator that something is wrong.)

    Your suggested fix — checking that $image has a true value — looks good to me. Another possibility is to confirm that $file is not a directory:

    if (!-d $image && -e $image) { print "Yes image exists\n"; } else { print "No image doesn't exist\n"; }

    Hope that helps,

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

      ... accepts either a filehandle or a directory handle ...

      Except that in this case, it's either a file name or a directory name. To use a handle, one would use the first argument of a successful open or opendir:

      #!/usr/bin/perl use v5.12; use warnings; my $fn='/etc/passwd'; open my $handle,'<',$fn or die "opening $fn failed: $!"; if (-T $handle) { say "$fn looks like text"; } else { say "$fn does not look like text"; } close $handle;

      Of course, to use a handle instead of a name, you first have to open a file or a directory. That won't work if it does not exist, or worse, a previously non-existing file might be created. So using handles instead of names for the -e, -f, -d, and -l tests is very uncommon.


      A related problem, especially with the -e and -f tests is TOCTOU: You may get a race condition that may be a security problem. Your program is not the only software that runs on the computer. The operating system may switch to another program, practically at any time. So between -e/-f and a following open, things may change drastically in the filesystem, and your program decides on obsolete information. The best way to avoid this problem is to simply allow open to fail, and check $! in case open fails.

      Alexander

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